Live updates¶
LiveListMixin keeps a ModelState list var in sync with Django model changes from other requests or browser tabs. It listens to Django post_save and post_delete signals, broadcasts a small change event inside the process, and reuses the same incremental list patch helpers as local CRUD mutations.
Basic usage¶
from reflex_django.live import LiveListMixin
from reflex_django.states import ModelState
class ProductState(LiveListMixin, ModelState):
model = Product
fields = ["name", "price", "active"]
incremental_updates = True
Start the background subscription from page load:
app.add_page(
products_page,
route="/products",
on_load=[ProductState.load, ProductState.live_subscribe],
)
live_subscribe registers model signals if needed, subscribes to the model's change stream, and patches the current list as changes arrive.
Patch behavior¶
| Change | Behavior |
|---|---|
| Create | Inserts the serialized row when pagination is disabled |
| Update | Replaces the row if it is on the current page |
| Delete | Removes the row from the current list |
| Out of scope | Removes the row if the scoped queryset no longer returns it |
If pagination makes a correct insert ambiguous, use refresh_list or reload after a create.
Scope and permissions¶
Live updates call get_scoped_queryset() before serializing changed rows. User-scoped states only show rows the current state is allowed to see.
If your queryset requires a fully bound request object, prefer a periodic refresh or an explicit event handler. Background live subscriptions run for the connection lifetime and may not have the same request context assumptions as a foreground event.
Public API¶
from reflex_django.live import (
ACTION_CREATED,
ACTION_DELETED,
ACTION_UPDATED,
LiveBroadcaster,
LiveListMixin,
ModelChange,
is_live_model,
live_broadcaster,
model_label,
register_live_model,
unregister_live_model,
)
| API | Purpose |
|---|---|
register_live_model(Model) |
Connect post_save / post_delete signals |
unregister_live_model(Model) |
Disconnect signals, mainly tests/teardown |
is_live_model(Model) |
Check registration |
model_label(Model) |
Return app_label.model_name |
ModelChange |
Broadcast payload: model_label, action, pk |
live_broadcaster() |
Process-wide broadcaster singleton |
Customizing changes¶
Override apply_live_change when a state needs custom serialization, denormalized rows, or a different refresh strategy:
class ProductState(LiveListMixin, ModelState):
model = Product
fields = ["name", "price"]
async def apply_live_change(self, change):
if change.is_delete:
self.remove_list_row(self.get_options(), change.pk)
return
await self.refresh()
Broadcaster extension¶
The built-in LiveBroadcaster is process-local. It is useful for single-worker apps, development, and tests. Multi-process deployments need a shared fan-out layer.
A Redis or Postgres listener can translate shared events into local publishes:
from reflex_django.live import ModelChange, live_broadcaster
live_broadcaster().publish(
ModelChange(model_label="shop.product", action="updated", pk=42)
)
subscribe(model_label) returns an asyncio.Queue; publish(change) delivers to matching subscribers on their event loops; unsubscribe(model_label, queue) cleans up. live_subscribe unsubscribes when Reflex cancels the background task on disconnect.
Deployment note¶
For multiple workers, add Redis pub/sub, Postgres LISTEN/NOTIFY, or another shared transport. Each worker should receive the shared change and call live_broadcaster().publish(...) locally.
Next: Model state, Scaling, and Security.