# mizan-django Django backend adapter for the Mizan protocol. One decorator on a server function. Typed React client generated. Invalidation automatic. ## Install ```bash uv add "mizan[channels]" # or with allauth integration: uv add "mizan[channels,allauth]" ``` ## Setup ```python # settings.py INSTALLED_APPS = ["mizan", "myapp", ...] MIZAN_CACHE_SECRET = "..." # 32-byte HMAC signing key MIZAN_CACHE_REDIS_URL = "redis://localhost:6379/0" MIZAN_MWT_SECRET = "..." # MWT signing key (separate from cache + JWT) ``` ```python # urls.py from django.urls import include, path urlpatterns = [ path("api/mizan/", include("mizan.urls")), ] ``` ```python # asgi.py — for WebSocket / Channels support from django.core.asgi import get_asgi_application from mizan import wrap_asgi application = wrap_asgi(get_asgi_application()) ``` ## Define server functions ```python # myapp/clients.py from mizan.client import client from mizan.setup import register from pydantic import BaseModel class EchoOutput(BaseModel): message: str @client def echo(request, text: str) -> EchoOutput: return EchoOutput(message=text) register(echo, "echo") ``` Auto-discover `clients.py` modules from each Django app: ```python # myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): name = "myapp" def ready(self) -> None: from mizan.setup import mizan_clients mizan_clients("myapp") # imports myapp/clients.py — triggers @client side effects ``` ## `@client` parameters ```python @client # plain RPC function @client(context="global") # singleton context — fetched once, SSR-hydrated @client(context="user") # named context — fetched per provider mount @client(affects="user") # mutation — invalidates the user context @client(affects=user_profile) # mutation — invalidates a specific function @client(websocket=True) # WebSocket transport (requires channels) @client(auth=True) # requires authentication @client(auth="staff") # requires is_staff @client(auth="superuser") # requires is_superuser @client(auth=lambda req: ...) # custom predicate @client(route="/profile//") # view-path function (returns HttpResponse) @client(rev=2) # cache revision (busts on bump) ``` ## Forms Django Forms become server functions + typed React hooks with Zod validation: ```python from django import forms from mizan.forms import mizanFormMixin, mizanFormMeta class ContactForm(mizanFormMixin, forms.Form): mizan = mizanFormMeta(name="contact", title="Contact Us", submit_label="Send") name = forms.CharField() email = forms.EmailField() message = forms.CharField(widget=forms.Textarea) def on_submit_success(self, request): send_email(self.cleaned_data) return {"sent": True} ``` Auto-registers `contact.schema`, `contact.validate`, `contact.submit`. Frontend gets `useContactForm()`. ## Channels WebSocket-native RPC via a flag flip: ```python from pydantic import BaseModel from mizan.channels import ReactChannel class ChatChannel(ReactChannel): class Params(BaseModel): room: str class DjangoMessage(BaseModel): text: str user: str def authorize(self, params): return self.user.is_authenticated def group(self, params): return f"chat_{params.room}" ``` Frontend gets `useChatChannel({ room })`. ## Generate the frontend The codegen lives in `backends/mizan-django/generate/`. From your frontend project, point a config at the Django backend and run the CLI: ```js // frontend/django.config.mjs import path from "path" import { fileURLToPath } from "url" const __dirname = path.dirname(fileURLToPath(import.meta.url)) const root = path.resolve(__dirname, "..") export default { source: { django: { managePath: path.join(root, "backend/manage.py"), command: ["uv", "run", "python"], env: { PYTHONPATH: path.join(root, "backend"), DJANGO_SETTINGS_MODULE: "myproject.settings", }, }, }, output: "src/api", } ``` ```bash node path/to/mizan-django/generate/generator/cli.mjs --config django.config.mjs ``` The codegen drives Django's management command (`export_mizan_schema`) under the hood, then emits Stage 1 (typed `callXxx`/`fetchXxx` over the runtime kernel) + Stage 2 (`` provider, per-context providers, `use{Hook}()` hooks) into `src/api/`. ```tsx // app.tsx import { MizanContext } from "./api" export default function App({ children }) { return {children} } ``` ```tsx // any component import { useEcho, useCurrentUser } from "./api" const echo = useEcho() echo.mutate({ text: "hi" }).then(r => console.log(r.message)) const user = useCurrentUser() // global context — auto-fetched, auto-refreshed on mutation ``` ## Running tests ```bash uv sync --extra dev --extra channels uv run pytest ``` ## Architecture mizan-django is one of two reference backend adapters (the other is `backends/mizan-fastapi`). Both implement the same Mizan protocol on top of the shared `cores/mizan-python` core (`@client`, registry, MWT, HMAC cache keys). See `docs/AFI_ARCHITECTURE.md`.