# reflex-django — LLM and agent guide

This file orients coding agents and LLMs: how reflex-django fits Reflex + Django, what to import, how to configure it safely, and common mistakes. For tutorials and longer examples, read `README.md` in this directory.

---

## Metadata

| Item | Value |
|------|--------|
| Package | `reflex-django` (import: `reflex_django`) |
| Purpose | Run a Django ASGI app (ORM, admin, sessions, auth, i18n) alongside a Reflex app in **one process** under `reflex run`. |
| Python | `>=3.12,<4.0` |
| Django | `>=6.0,<7.0` |
| Reflex | `>=0.9.2,<1.0` |
| License | Apache-2.0 (see `LICENSE`) |

---

## What it is / what it is not

**Is:** A Reflex **plugin** (`ReflexDjangoPlugin`) that (1) initializes Django early from `rxconfig.py`, (2) wraps Reflex’s ASGI stack with a **path-prefix dispatcher** so selected HTTP paths go to Django and the rest to Reflex, and (3) optionally installs **Reflex event middleware** (`DjangoEventBridge`) that builds a synthetic `HttpRequest` per Socket.IO event and binds it in **context variables** so helpers like `current_user()` work inside `@rx.event` handlers.

**Is not:** A single merged URL router. Reflex UI traffic and `/_event/…` are **not** ordinary Django views. Django HTTP middleware does **not** run for Reflex events unless you rely on the **event bridge** (default on), which reconstructs session, user, and optional locale for that event only.

---

## Agent setup checklist

Do these in order:

1. Add dependencies: `uv add reflex reflex-django` (or equivalent).
2. Scaffold Reflex: `uv run reflex init frontend` (app name/layout per Reflex CLI).
3. Create a Django project (e.g. `uv run django-admin startproject backend .`) so you have `manage.py` and a settings module (e.g. `backend.settings`).
4. In **`INSTALLED_APPS`**, include Django contrib apps you need (`auth`, `sessions`, `admin`, `staticfiles`, …) and **`reflex_django`** when you use bundled helpers (states, admin registration, etc.).
5. In **`rxconfig.py`**, add the plugin with your settings module:

   ```python
   import reflex as rx
   from reflex_django import ReflexDjangoPlugin

   config = rx.Config(
       app_name="myapp",
       plugins=[ReflexDjangoPlugin(settings_module="backend.settings")],
   )
   ```

6. Align **`ROOT_URLCONF`** (and `urlpatterns`) with plugin prefixes: `backend_prefix`, `admin_prefix` (default `"/admin"`), and `extra_prefixes`. Mismatched prefixes are a frequent bug.
7. Run: `uv run reflex run`.

---

## Architecture (compact)

```text
Browser
  │ HTTP matching admin / api / static path prefixes…
  ├──────────────────────────────► Django ASGI (same process)
  │
  │ Reflex pages, assets, Socket.IO /_event/…
  └──────────────────────────────► Reflex ASGI
                                         │
                                         ▼
                             Reflex event → DjangoEventBridge
                                         → contextvars (current_request, …)
                                         → your @rx.event handlers
```

- **HTTP bridge:** Implemented via `ReflexDjangoPlugin.post_compile` appending an `api_transformer` that wraps the inner app (`src/reflex_django/plugin.py`, `src/reflex_django/asgi.py`).
- **Event bridge:** `DjangoEventBridge` Reflex middleware (`src/reflex_django/middleware.py`) calls `begin_event_request` / `end_event_request` (`src/reflex_django/context.py`).
- **ASGI lifespan:** Owned by Reflex, not duplicated for Django in the same way as a standalone Django-only deploy—see README “Architecture” for nuance.

---

## Public API (package `reflex_django`)

These names match `__all__` in `src/reflex_django/__init__.py`. Many are **lazy-loaded** (see next section).

| Area | Symbols |
|------|---------|
| Plugin | `ReflexDjangoPlugin` |
| Django init | `configure_django` |
| ASGI | `build_django_asgi`, `make_dispatcher` |
| Per-event context | **`request`** (`request.user`, `request.session`, `request.GET`, `request.headers`), `current_request`, `current_user`, `current_session`, `current_language`, `begin_event_request`, `end_event_request` |
| Event middleware | `DjangoEventBridge` |
| Reflex state helpers | `AppState`, `ModelState`, `DjangoUserState`, `DjangoI18nState`, `DjangoContextState` |
| Reflex context dicts | `collect_reflex_context`, `builtin_user_context`, `builtin_i18n_context` |
| Auth shortcuts | `require_login_user`, `auser_has_perm`, `ReflexDjangoAuthError` (`reflex_django.auth.shortcuts`) |
| Auth decorators | `login_required`, `permission_required` (pages and event handlers; `reflex_django.auth.decorators`) |
| AppState auth API | `self.user`, `self.session`, `login`, `logout`, `has_perm`, `has_group`, `on_auth_failed`, `on_permission_denied` on `AppState` / `DjangoUserState` |
| Canned auth UI | `add_auth_pages`, `register_login_page`, `register_register_page`, `register_password_reset_page`, `register_password_reset_confirm_page`, `LoginPage`, `RegisterPage`, `PasswordResetPage`, `PasswordResetConfirmPage`, `autoload`, `DjangoAuthState`, `get_auth_settings`, `AuthSettings`, `auth_pages`, `auth_routes` (subpackage `reflex_django.auth`) |
| User JSON snapshot | `user_snapshot` (dict for display, processors, tests—not authorization) |
| ORM helper | `Model` (abstract Django model base + Reflex serializer) |
| Model serializers | `ReflexDjangoModelSerializer` (`Meta.fields` / `exclude` / `read_only_fields`, `.data`, `.adata`, queryset `many=True`) |
| Declarative model CRUD state | `ModelCRUDView` / `ModelState`, `ModelListView` (`reflex_django.state`; see **ModelCRUDView** section below) |
| Admin | `register_admin` |
| Session / cookies (JS helpers) | `session_cookie_set_js`, `session_cookie_clear_js`, `session_cookie_name_and_suffix` |
| CLI (programmatic) | `django_cli` |

## Request proxy in event handlers

**Import:** `from reflex_django import request` (lazy on package root; **not** `reflex_django.state.request`)

Requires `DjangoEventBridge` / `install_event_bridge=True`.

```python
@rx.event
async def handler(self):
    request.user
    request.session
    request.GET.get("page")
    request.headers.get("cookie")
    request.path
    request.COOKIES
```

Populated from `event.router_data`: `pathname` (+ `?query`), `query` dict, `headers`, cookies. Outside an event: `request.user` → anonymous, `request.GET` → empty `QueryDict`.

**Never** put `request.user` in `rx.text()` / components (Django model is not a Reflex child). UI: `DjangoAuthState.username` / `AppState.username`. Logout / `@login_required` page shell: **`DjangoAuthState.is_authenticated`** (`@rx.var` → `current_user()`, not inherited snapshot). Handlers: `request.user.is_authenticated` or `request.username` (str).

**ModelState:** `self.request` during `dispatch` is `DjangoStateRequest` (adds context-processor keys). Module `request` is the raw `HttpRequest`.

---

**Submodule extras (not re-exported on the package root):** `reflex_django.reflex_context` also exposes `template_context_processor_paths`, `reflex_context_processor_paths`, and `reflex_context_processors_use_template_sanitization` for tooling or advanced use (`src/reflex_django/reflex_context.py`). **`reflex_django.mixins`** exposes **`SessionAuthConfig` / `session_auth_mixin`** (Django session login/logout in Reflex events); see `README.md`. **`reflex_django.auth`** provides canned pages as **`BaseAuthPage`** subclasses (**`LoginPage`**, **`RegisterPage`**, …). Override hook methods (`heading_text`, `form_fields`, …), **`state_cls`**, or **`render()`**; copy via **`MESSAGES`** in **`REFLEX_DJANGO_AUTH`**. Register with **`app.add_page`**, **`register_*_page`** (uses **`default_on_load`**), or **`add_auth_pages(app)`**.

---

## Critical rule: lazy imports on `reflex_django`

Many public attributes are resolved via **PEP 562** `__getattr__` so submodules that touch the Django ORM, admin, or HTTP stack are not imported until **after** `django.setup()`-compatible initialization.

**Do:** Import `from reflex_django import …` in application code after `rxconfig` / plugin construction in normal Reflex startup. Prefer the package root for documented names.

**Avoid:** Import patterns that pull Django models or heavy `reflex_django` submodules at **import time before** settings and `configure_django()` have run (e.g. circular imports from models imported at the top of modules that load before `rxconfig`). If tests need a request binding, use `begin_event_request` / `end_event_request` carefully (see tests under `reflex_django_tests/`).

---

## `ReflexDjangoPlugin` configuration

| Argument | Role |
|----------|------|
| `settings_module` | Dotted path (e.g. `"backend.settings"`). Sets `DJANGO_SETTINGS_MODULE` via `setdefault` and runs `configure_django`. |
| `backend_prefix` | Optional prefix for **your** Django HTTP routes (e.g. `"/api"`). Exported to env as `REFLEX_DJANGO_API_PREFIX` when non-empty. |
| `admin_prefix` | Django admin mount (default `"/admin"`). Sets `REFLEX_DJANGO_ADMIN_PREFIX`. |
| `extra_prefixes` | Additional path prefixes forwarded to Django. |
| `install_event_bridge` | Default `True`: register `DjangoEventBridge`. Set `False` if you do not need session/user on Reflex events (then `current_user()` etc. will not be populated by the bridge). |
| `install_auth_pages` | Default `False`. When `True`, attempts `reflex_django.auth.autoload()` (prefer explicit `add_auth_pages(app)` in the app module). |

**Static files:** If `django.contrib.staticfiles` is in `INSTALLED_APPS` and `STATIC_URL` is a **path** (not `://` CDN URL), the plugin adds that URL as a forwarded prefix so Django can serve statics (`_static_prefixes` in `plugin.py`).

---

## Django settings (`REFLEX_DJANGO_*` and related)

Consolidated from `src/reflex_django/default_settings.py` and related modules:

| Setting | Meaning |
|---------|---------|
| `REFLEX_DJANGO_AUTO_SETTINGS` | `True` in bundled defaults; plugin **warns** that you should use your own settings + stable `SECRET_KEY` for production. |
| `REFLEX_DJANGO_ADMIN_PREFIX` | Admin URL prefix; kept in sync with plugin env (`default_settings`, `urls.py`). |
| `REFLEX_DJANGO_CONTEXT_PROCESSORS` | Non-empty tuple of dotted callables `(request) -> dict` or async; used **exclusively** by `collect_reflex_context`. You must return **JSON-serializable** dicts. |
| `REFLEX_DJANGO_USE_TEMPLATE_CONTEXT_PROCESSORS` | When `REFLEX_DJANGO_CONTEXT_PROCESSORS` is empty and this is `True`, run `TEMPLATES[*].OPTIONS["context_processors"]` with sanitization (see `reflex_context.py`). |
| `REFLEX_DJANGO_LOGIN_URL` | Redirect target for `login_required` on event handlers when anonymous. Legacy fallback for `REFLEX_DJANGO_AUTH["LOGIN_URL"]`. |
| `REFLEX_DJANGO_AUTH` | Dict: `ENABLED`, `SIGNUP_ENABLED`, `PASSWORD_RESET_ENABLED`, route URLs, redirects, `EMAIL_REQUIRED`, `PASSWORD_MIN_LENGTH`, `MESSAGES`. See `reflex_django.auth.settings`. |
| `EMAIL_BACKEND` / `DEFAULT_FROM_EMAIL` | Required for password-reset emails (console backend OK in dev). |
| `REFLEX_DJANGO_SITE_ORIGIN` | Optional absolute origin for reset links when no request is bound. |
| `REFLEX_DJANGO_USER_SNAPSHOT_INCLUDE_GROUPS` | When `True`, user snapshots / `DjangoUserState` may include group names. |
| `REFLEX_DJANGO_AUTH_AUTO_SYNC` | When `True` (default), `DjangoEventBridge` refreshes `AppState` auth snapshot vars on every event. |
| `REFLEX_DJANGO_I18N_EVENT_BRIDGE` | When `True` and `USE_I18N`, event bridge aligns locale with Django-style negotiation on the synthetic request (`middleware.py`). |
| `REFLEX_DJANGO_DATABASE_URL` | Optional env override for DB URL when using defaults (`default_settings._resolve_db_url`). |

Default settings also honor env vars such as `REFLEX_DJANGO_STATIC_URL`, `REFLEX_DJANGO_STATIC_ROOT`, `REFLEX_DJANGO_SECRET_KEY`, `REFLEX_DJANGO_DEBUG`, `REFLEX_DJANGO_ALLOWED_HOSTS`, `REFLEX_DJANGO_URLCONF`. Database URL can fall back from Reflex config `db_url` to a local SQLite file.

---

## Security and best practices

1. **Authorize on the server inside event handlers** using `self.user` / `current_user()`, `require_login_user()`, `await self.has_perm(...)`, or `await auser_has_perm(user, "app.codename")`. Never trust snapshot fields alone for permission checks—they are a **UI snapshot** synchronized to the client.
2. Use **`@login_required`** and **`@permission_required`** on event handlers to redirect anonymous or unauthorized users.
3. **`user_snapshot(user)`** (`from reflex_django import user_snapshot`) is for display, logging, or custom processors—not a substitute for live `User` checks on mutations.
4. Anything assigned to Reflex **`rx.State`** fields that sync to the browser must be **JSON-serializable**. Custom `REFLEX_DJANGO_CONTEXT_PROCESSORS` must not leak secrets or non-JSON objects.
5. Prefer **`async def`** event handlers when calling Django’s async APIs (`aget_user` is already used in the bridge for `request.user`).

---

## AppState authentication bridge (agent reference)

**Import:** `from reflex_django.state import AppState`  
**Also:** `from reflex_django.auth import login_required, permission_required`

`AppState` subclasses `DjangoUserState` + `AuthBridgeMixin`. Use for dashboards, auth-aware features, and `ModelCRUDView`.

### Two layers

| Layer | API | Use |
|-------|-----|-----|
| Live (handlers) | **`self.request`**, **`self.request.user`**, `self.user`, `self.session` | Authorization, ORM scoping, `GET`/`path`/cookies |
| Snapshot (UI) | `is_authenticated`, `username`, `email`, `group_names`, … on **AppState** / **DjangoUserState** | `rx.cond`, bindings — **not** `self.request.user` in components |
| Live UI (canned auth) | **`DjangoAuthState.is_authenticated`** (`@rx.var`) | Sidebar logout, `@login_required` page gate — **not** for handler authorization |

### `self.request` (handlers)

- **`self.request`** → `DjangoStateRequest` (`.user`, `.GET`, `.path`, `.context`, processor keys).
- **`self.django_request`** → raw `HttpRequest`.
- **`ModelCRUDView.dispatch`** → `bind_request_context()` merges context processors onto `self.request`.
- Plain `rx.State` → `from reflex_django import request` instead.

Human docs: `docs/authentication.md#accessing-the-django-request-on-appstate`.

### Handler examples

```python
class S(AppState):
    @rx.event
    async def action(self):
        if not self.request.user.is_authenticated:
            return rx.redirect("/login")
        tab = self.request.GET.get("tab", "home")
        self.session["theme"] = "dark"
        if await self.has_perm("app.change_model"):
            ...
        if await self.has_group("admins"):
            ...

    @rx.event
    async def sign_in(self):
        if not await self.login(username, password):
            return await self.on_auth_failed()
        await self.logout()  # clears server session + refreshes snapshot
```

### Decorators

```python
@rx.event
@login_required(login_url="/login")
async def private(self): ...

@rx.event
@permission_required("products.view_product", redirect="/login", on_denied=...)
async def catalog(self): ...
```

### Cookie sync after login/logout

Reflex events skip `SessionMiddleware`. After `login()` / `logout()`, call `_sync_session_cookie_then_nav` from `reflex_django.mixins.session_auth` so the browser `sessionid` matches the server row.

**Django `sessionid` vs Reflex `token`:** Django auth uses the **`sessionid` cookie** (session table + `request.user`). Reflex stores a separate per-tab UUID in **`sessionStorage` under key `token`** (`router.session.client_token`) for WebSocket/state identity—not login. Logout must clear cookies, `router_data` cookie mirrors, and client storage (`browser_auth_logout_clear_js`) or stale `token` can cause `/` ↔ `/login` loops. See `docs/authentication.md#browser-storage-django-sessionid-vs-reflex-token-for-django-developers`.

### Settings

- `REFLEX_DJANGO_AUTH_AUTO_SYNC` (default True): bridge calls `refresh_django_user_fields()` on each event for `AppState` instances.
- `REFLEX_DJANGO_USER_SNAPSHOT_INCLUDE_GROUPS`: load `group_names` in snapshot.

Human docs: `docs/authentication.md`.

---

## Reflex + Django ORM

- Optional abstract base **`Model`** (`src/reflex_django/model.py`): subclass for tables; run migrations through the CLI below. Importing `reflex_django.model` calls `configure_django()`.
- Registered **serializer** turns Django `Model` instances into JSON-friendly dicts (includes PK) for Reflex wire format.

---

## ModelState — reactive ORM state (preferred)

**Import:** `from reflex_django.state import ModelState`  
**Full doc:** `docs/reactive_model_state.md` (examples: Product CRUD page, BlogPost + UserScopedMixin, pagination, forms, hooks)  
**Comparison (ModelState vs ModelCRUDView, migration, side-by-side UI):** `docs/model_state_and_crud_view.md`  
**Requires:** event bridge on; includes **`AppState`** (auth).

### Pattern (any Django model)

```python
class ProductState(ModelState):
    model = Product
    fields = ["name", "price", "is_active"]
    ordering = ("-created_at",)
# Generated: data, error, editing_id, field vars, load, save, refresh, filter, …
```

Auto-builds serializer from `model` + `fields` unless `serializer_class` / `Meta.serializer` is set. Invalid field names → `ImproperlyConfigured` at import.

### UI wiring

```python
rx.button("Save", on_click=ProductState.save)
rx.button("Edit", on_click=ProductState.load(row["id"]))
on_mount=ProductState.refresh
```

### Canonical API (stable names)

`load(pk)`, `save()`, `create()`, `delete(pk=None)`, `refresh()`, `filter(**kwargs)`, `clear_filter()`, `paginate(page=…, page_size=…)`, `cancel_edit()`, `get_row(pk)`.

`editing_id == -1` → create on save; `>= 0` → update. `filter(**kwargs)` sets `_queryset_filter` then refresh.

Legacy names (`save_{model}`, `start_edit`, `on_load_data`) still generated. `Meta.use_canonical_api = False` skips canonical handlers.

**UI:** `rx.cond(State.data.length() > 0, …)` — not `len(State.data)`. **`Meta.run_model_validation`** only (not a class-body var). **`paginate_by`** seeds `page_size`.

### User scope

```python
class NoteState(ModelState, UserScopedMixin):
    model = Note
    fields = ["title", "body"]
    scope_field = "user_id"
```

---

## ModelCRUDView — explicit serializer CRUD (legacy / advanced)

**Import:** `from reflex_django.state import AppState, ModelCRUDView, ModelListView`  
**Note:** `ModelState` is **not** an alias; it extends `AppState` + `ModelCRUDView` with `model` + `fields` shorthand.  
**Comparison guide:** `docs/model_state_and_crud_view.md`  
**Requires:** event bridge on; `@login_required` on most generated handlers by default.

### Pattern

```python
class MyState(AppState, ModelCRUDView):
    serializer_class = MySerializer
    paginate_by = 20                  # class attrs: best IDE autocomplete
    search_fields = ("title",)

    class Meta(ModelCRUDMeta):          # or inner Meta; inherit ModelCRUDMeta for hints
        list_var = "notes"
```

Import: `from reflex_django.state import ModelCRUDMeta`

Assembly (via `AppStateMeta`) declares vars + handlers unless the subclass already defines the same name in its class body.

### Default reactive vars (`ModelState`)

| Member | Role |
|--------|------|
| `data` | `list[dict]` rows (`Meta.list_var`, default `"data"`) |
| `error` | validation message (`Meta.error_var`, default `"error"`) |
| `search`, `total_count`, `page_count` | when search / pagination enabled |
| `editing_id` | `int`, `-1` = create mode, `>=0` = editing pk |
| `title`, `content`, … | flat editable state vars (not `form_*` prefixes) |
| `set_title`, … | `@rx.event` setters |
| `_load_data` | internal reload |
| `on_load_data` | page `on_load` target |
| `save_note` | create or update from flat vars + `editing_id` |
| `start_edit(item_id)` | populate vars, set `editing_id` |
| `delete_note(item_id)` | delete + refresh list |
| `cancel_edit` | `reset_state_fields()` |
| `reset_state_fields` | clear editable vars, `editing_id=-1`, bump `form_reset_key` |
| `bump_form_reset_key` | increment `form_reset_key` only (custom remount without full reset) |
| `form_reset_key` | int; bind to `rx.form(..., key=...)` — bumps on save reset, cancel, and `start_edit` |
| `save_{model}_form` | optional when `Meta.use_form_submit = True`; reads `form_data` then `dispatch(save)` |

Canonical handlers on `ModelState`: `load`, `save`, `create`, `delete`, `refresh`, `filter`, `paginate`, …

### `ModelCRUDView` list/search names (legacy explicit serializer)

Default `list_var` = plural of model (`notes`, `products`, …) → `notes_error`, `notes_search`, `on_load_notes`, etc. Override with `Meta.list_var` or use `ModelState` for generic `data` / `error` / `search`.

**List features (opt-in via Meta):**

| Meta | Injects |
|------|---------|
| `paginate_by = 20` | `page`, `page_size`, `total_count`, `page_count`, `next_page`, `prev_page`, `go_to_page`, `set_page_size` |
| `search_fields = ("title",)` | `search`, `set_search`, `clear_search` |
| `allow_dynamic_ordering = True` | `ordering`, `set_ordering` |

Default `paginate_by = None` → full list (no page vars). Hooks: `apply_search`, `paginate_queryset`, `get_ordering`, `on_page_change`.

### Event flow

```text
on_load_data  → dispatch("load_list")  → get_queryset → filter_queryset → serialize → data
save_note      → dispatch("save")       → validate_state → create|update → on_save_success → reset_state_fields? → refresh list
start_edit(id) → dispatch("start_edit") → get_object → populate state vars → bump form_reset_key
```

### Per-event `self.request` / `self.django_request`

Bound at the start of each **`dispatch`** (and list-only **`_load_*`**):

| Attr | Value |
|------|--------|
| `self.django_request` | `current_request()` — synthetic `HttpRequest` |
| `self.request` | `DjangoStateRequest` wrapper |
| `self.request.user` | Live `request.user` (use for ORM filters) |
| `self.request.SOME_KEY` | Merged context-processor output |
| `self.request.context` | Full processor dict |

```python
def get_queryset(self):
    return Note.objects.filter(user=self.request.user)
```

`Meta.load_context_processors = False` skips `collect_reflex_context` but still binds the HTTP request.

### Scoping (no default owner field)

Framework does **not** auto-filter by user. Prefer **`self.request.user`** in hooks (available after bind):

```python
def get_queryset(self):
    return Note.objects.filter(user=self.request.user)

def get_object_lookup(self, pk: int) -> dict:
    return {"pk": pk, "user": self.request.user}

def get_create_kwargs(self, state_data: dict) -> dict:
    return {**state_data, "user": self.request.user}
```

**Shortcut:** `class NotesState(AppState, ModelCRUDView, UserScopedMixin):` with `scope_field = "user_id"`. Assembly injects the three hooks (Reflex MRO cannot prioritize plain mixins).

### Page wiring example

```python
def notes_page() -> rx.Component:
    return rx.vstack(
        rx.cond(NotesState.error != "", rx.callout(NotesState.error)),
        rx.form(
            rx.vstack(
                rx.input(value=NotesState.title, on_change=NotesState.set_title),
                rx.text_area(value=NotesState.content, on_change=NotesState.set_content),
                spacing="3",
                width="100%",
            ),
            key=NotesState.form_reset_key,
            width="100%",
        ),
        rx.button("Save", on_click=NotesState.save_note),
        rx.foreach(
            NotesState.data,
            lambda n: rx.button("Edit", on_click=NotesState.start_edit(n["id"])),
        ),
    )

# app.add_page(notes_page, route="/notes", on_load=NotesState.on_load_data)
```

### Validation hooks (state-centric names — not `form_*`)

| Hook | When |
|------|------|
| `get_state_data()` | Read vars → dict |
| `validate_state(ctx, data)` | Cross-field errors → `dict[str, str]` |
| `clean_<field>(value)` | Per-field; return error string |
| `clean_state(data)` | Normalize before save |
| `on_state_invalid(ctx, errors)` | Set `error_var` / `field_errors` |
| `on_state_valid(ctx, state_data)` | Before create/update |
| `perform_create(ctx, state_data)` | ORM create |
| `perform_update(ctx, instance, state_data)` | ORM update |

Set `run_model_validation = True` to call Django `full_clean()`. Set `structured_errors = True` for `field_errors` (`Meta.field_errors_var`).

### Read-only lists

```python
class LogState(AppState, ModelListView):
    serializer_class = LogSerializer
```

### Permissions

```python
from reflex_django.state.mixins import IsAuthenticated, PermissionMixin

permission_classes = (IsAuthenticated,)  # on ModelCRUDView subclass Meta or class attr
```

### Do not use

- **`crud_mixin` / `ModelCRUDConfig`** — removed; use `ModelCRUDView`.
- **`owner_field`** on serializer — removed; use `get_queryset` / `UserScopedMixin`.
- **`Meta.form_fields`** — use **`Meta.state_fields`**.
- **`_clear_form`** — use **`reset_state_fields`** (alias **`_reset_state_fields`**).
- After save/update/cancel and when entering edit mode, wrap fields in **`rx.form(..., key=State.form_reset_key)`** so the DOM remounts (needed for controlled **`value=`** / **`on_change=`** too, not only **`name=`** inputs).
- **`bump_form_reset_key()`** — remount only; **`reset_state_fields()`** — clear vars + remount.

### Submodule map

`reflex_django/state/` — `views/crud.py` (`ModelCRUDView`), `assembly.py`, `options.py`, `mixins/` (`dispatch`, `state_fields`, `queryset`, `scoping`, …), `backends/django.py`.

---

## CLI

- **Via Reflex:** `uv run reflex django <subcommand>` — e.g. `migrate`, `makemigrations`, `createsuperuser`, `shell`, `collectstatic`, `help`. Loads `rxconfig` first so `ReflexDjangoPlugin` sets `DJANGO_SETTINGS_MODULE` and prefixes, then `configure_django()`.
- **Standalone:** `uv run reflex-django <subcommand>` — forwards to Django’s `execute_from_command_line` the same way (see `src/reflex_django/cli.py`).

---

## Testing and advanced use

- Use **`begin_event_request(request)`** / **`end_event_request()`** to bind a synthetic request in tests or advanced flows (normally the bridge does this). Examples: `reflex_django_tests/`.
- **`collect_reflex_context(request)`** is async; pass `current_request()` inside a bridged event, or `None` for an empty dict.

---

## Common mistakes

| Mistake | Why it fails |
|---------|----------------|
| Plugin prefixes do not match `ROOT_URLCONF` | Django returns 404 or Reflex handles paths unexpectedly. |
| Using `DjangoUserState` for authorization | Client-visible; can be stale or tampered with—use `current_user()` / permissions on the server. |
| Putting non-JSON values into Reflex state or explicit context processors | Serialization errors or subtle runtime failures. |
| `install_event_bridge=False` but still expecting `current_user()` from sessions | No synthetic request / session binding from the bridge. |
| Heavy imports before Django setup / `rxconfig` | Breaks lazy-loading assumptions; circular import issues. |
| Relying on Django HTTP middleware for Reflex-only actions | Events are not full HTTP requests; use the event bridge and explicit checks. |
| Expecting `crud_mixin` or `owner_field` on ModelState | Removed; use `ModelCRUDView` + `get_queryset` / `UserScopedMixin`. |
| Using `form_title` / `Meta.form_fields` | Use flat `title` vars and `Meta.state_fields`. |

---

## Pointers for deeper reading

- `README.md` — full architecture, examples for states, bridges, context processors.
- `CHANGELOG.md` — version-to-version changes.
- `RELEASING.md` — maintainer release process.
- Source: `src/reflex_django/` — especially `plugin.py`, `middleware.py`, `context.py`, `conf.py`, `asgi.py`, `auth/`, `auth_state.py`, `i18n_state.py`, `reflex_context.py`, `model.py`, `state/` (ModelCRUDView), `cli.py`.
