djust 0.3.0 — "Phoenix Rising" 🔥
The biggest djust release yet. With over 20 major features, this release brings djust to full parity with Phoenix LiveView — and then goes further. Authentication, server-push, multi-tenancy, PWA support, AI tooling, and serious performance and security work make 0.3 the release where djust becomes production-ready for real applications.
Highlights
1. Authentication & Authorization
Framework-enforced auth that runs server-side before mount() and before every handler dispatch — no client-side bypass possible:
class AdminDashboardView(LiveView):
login_required = True
permission_required = "dashboard.view_analytics"
@permission_required("dashboard.delete_record")
@event_handler
def delete_record(self, record_id: int, **kwargs):
Record.objects.filter(id=record_id).delete()
Includes LoginRequiredMixin/PermissionRequiredMixin for developers who prefer the Django mixin pattern, plus a check_permissions() hook for custom auth logic. The djust_audit command shows auth posture per view, and system check djust.S005 warns on unprotected views with exposed state.
2. Full Navigation & URL State Management
No more page refreshes for filters, pagination, or tabs. The new navigation primitives let you update URLs while keeping your WebSocket connection alive:
class ProductListView(LiveView):
def handle_params(self, params):
self.page = int(params.get('page', 1))
self.category = params.get('category')
self.products = Product.objects.filter(category=self.category)[self.page*20:(self.page+1)*20]
@event_handler
def filter_category(self, category, **kwargs):
self.live_patch(f"?category={category}&page=1")
<a dj-patch="?page=2">Next Page</a>
<a dj-navigate="/products/{{ product.id }}/">View Details</a>
3. Real-Time Presence Tracking
Build collaborative features with built-in presence tracking. Know who's viewing a page, share live cursors, and broadcast to all connected users:
class DocumentView(LiveView, PresenceMixin, LiveCursorMixin):
def mount(self):
self.track_presence(user=self.request.user, color=self.random_color())
def handle_presence_join(self, user, meta):
self.push_event("user_joined", {"name": user.username})
@event_handler
def cursor_move(self, x, y, **kwargs):
self.broadcast_cursor(x, y)
Pluggable backends (in-memory and Redis) let you scale from development to production without changing your view code.
4. Streaming Responses
Stream partial updates during long-running operations. Perfect for AI responses, file processing, or any async workflow:
@event_handler
async def generate_report(self, **kwargs):
self.stream_start("report-output")
async for chunk in generate_report_chunks():
self.stream_text("report-output", chunk, mode="append")
self.stream_done("report-output")
Batched at ~60fps to prevent flooding the client.
5. Server-Push API
Background tasks can now push state updates to connected LiveView clients — no polling required:
from djust.push import push_to_view
@shared_task
def process_upload(upload_id):
upload = Upload.objects.get(id=upload_id)
upload.status = "complete"
upload.save()
push_to_view(
"uploads",
UploadDashboardView,
{"latest_upload": upload.name, "status": "complete"},
)
Works with Celery, management commands, cron jobs — anything that runs outside the request cycle. Also includes handle_tick() for self-updating views with periodic server-side refresh.
6. Automatic Change Tracking
Phoenix-style render optimization — the framework now automatically detects which context values changed between renders and only sends those to the Rust engine. No manual annotation needed:
class DashboardView(LiveView):
def mount(self, request, **kwargs):
self.count = 0
self.user_name = request.user.username # Won't re-send if unchanged
@event_handler
def increment(self, **kwargs):
self.count += 1
# Only `count` is sent to Rust — `user_name` is skipped automatically
Two-layer detection: snapshot comparison for instance attributes, id() reference comparison for computed values (e.g., @lru_cache results). Immutable types (str, int, float, bool, None, bytes, tuple, frozenset) skip deepcopy in snapshots for zero overhead. This replaces the manual static_assigns API which has been removed.
7. CSS Framework Support
Comprehensive Tailwind CSS integration with zero-config development and production-ready builds:
python manage.py djust_setup_css tailwind
The setup command creates input.css with Tailwind v4 syntax, auto-detects template directories, finds the Tailwind CLI, and builds CSS with --watch and --minify flags. In development, djust automatically falls back to the Tailwind CDN when compiled CSS is missing. Three new system checks (C010, C011, C012) catch common CSS misconfigurations at startup.
Rust Template Engine: Full Django Parity
The Rust template engine now supports all 57 Django built-in filters — complete parity with Django's template language, at Rust speed. The final 24 filters added in this release include urlize, urlizetrunc, truncatechars_html, truncatewords_html, unordered_list, json_script, wordwrap, striptags, escapejs, linenumbers, and more.
Also new: {{ model.pk }} now works in Rust-rendered templates — model serialization includes a pk key with the native primary key value.
Technical Details
LiveForm — Validation Without Django Forms
A new standalone form validation system that works directly with LiveView state:
class ContactForm(LiveForm):
fields = {
'email': {'validators': ['required', 'email']},
'message': {'validators': ['required', ('min_length', 10)]},
'phone': {'validators': [validate_phone_number]}, # Custom callable
}
File Uploads Over WebSocket
Chunked uploads with progress tracking, magic byte validation (not just extensions), and drag-and-drop support:
class ImageUploadView(LiveView, UploadMixin):
def mount(self):
self.allow_upload("photos", accept=["image/*"], max_entries=5, max_size=10_000_000)
@event_handler
def save_photos(self, **kwargs):
for entry in self.consume_uploaded_entries("photos"):
Photo.objects.create(image=entry.file, name=entry.client_name)
JavaScript Hooks
Integrate third-party libraries (charts, maps, rich editors) with the hook system:
window.DjustHooks.ChartJS = {
mounted() {
this.chart = new Chart(this.el, JSON.parse(this.el.dataset.config));
},
updated() {
this.chart.data = JSON.parse(this.el.dataset.config).data;
this.chart.update();
},
destroyed() {
this.chart.destroy();
}
};
<canvas dj-hook="ChartJS" data-config="{{ chart_config|json }}"></canvas>
Client-Side Directives
Template directives for common patterns:
dj-model/dj-model.lazy/dj-model.debounce-300— Two-way bindingdj-confirm="Are you sure?"— Confirmation dialogsdj-loading— Show/hide/disable during eventsdj-transition— CSS enter/leave animationsdj-optimistic— Instant UI updates with auto-rollbackdj-poll="refresh"— Declarative polling with configurable interval (dj-poll-interval). Auto-pauses on hidden tabs and resumes on visibility change.
Progressive Web App (PWA)
Complete offline-first PWA support with service worker integration and storage abstraction:
{% load djust_pwa %}
<!DOCTYPE html>
<html>
<head>
{% djust_pwa_head %}
{% djust_pwa_manifest %}
</head>
class OfflineDashboardView(LiveView, PWAMixin, OfflineMixin, SyncMixin):
def mount(self):
self.enable_offline_support()
Includes IndexedDB/LocalStorage abstraction, optimistic UI updates during offline operation, and automatic synchronization when connectivity returns.
Multi-Tenant SaaS
Production-ready multi-tenant architecture with flexible tenant resolution:
# settings.py
DJUST_TENANT_RESOLVER = "djust.tenants.resolvers.SubdomainResolver"
# views.py
class TenantDashboardView(LiveView, TenantMixin, TenantScopedMixin):
def mount(self):
# self.tenant is automatically resolved
self.projects = Project.objects.filter(tenant=self.tenant)
Resolution strategies: subdomain, path prefix, header, session, custom callable, or chained (try multiple in order). Includes tenant-aware state backends (TenantAwareRedisBackend, TenantAwareMemoryBackend) for automatic data isolation.
Developer Experience
DjustMiddlewareStack— New ASGI middleware for apps that don't usedjango.contrib.auth. Wraps WebSocket routes with session middleware only — no more requiring auth just to use LiveView.- Better error messages — Clearer messages when you forget
@event_handler, use the wrong method signature, or misconfigure your view. - System check
C006— Warns whendaphneis inINSTALLED_APPSbutwhitenoisemiddleware is missing (a common deployment gotcha). - Simplified root element —
dj-viewis now the only required attribute. Both client and server auto-inferdj-rootfromdj-viewat init time — no explicit root attribute needed. - Auto-build client.js — Pre-commit hook automatically rebuilds
client.jsfromsrc/modules when source files change.
Management Commands
djust_audit— Security audit showing auth posture, exposed state, and handler signatures per view.djust_check --fix— Django system checks with auto-fix for safe issues and--format jsonfor CI integration.djust_schema— Generate Django models from JSON schema files.djust_ai_context— Generate CLAUDE.md / .cursorrules files tailored to your project.
AI-Ready Tooling
djust 0.3 ships with first-class AI assistant integration:
- MCP Server —
python manage.py djust_mcpstarts a Model Context Protocol server providing framework introspection, system checks, scaffolding, and validation. Works with Claude Code, Cursor, and Windsurf. - One-command setup —
djust mcp installauto-configures your editor's MCP settings. - Project scaffolding —
python -m djust new myappcreates a full project with--with-auth,--with-db,--with-presence,--with-streaming, and--from-schemaoptions. - Focused AI docs —
docs/ai/contains guides optimized for LLM consumption, plusdocs/llms.txtfor one-shot context.
Performance
Targeted optimizations that compound across every event:
- Batched
sync_to_async— Event handler processing now uses 2 thread hops instead of 4, saving ~1-4ms per event. - Eliminated JSON roundtrip — Direct
normalize_django_value()replaces 17json.loads(json.dumps(...))patterns, saving 2-5ms per event for views with database objects. - Cached template variable extraction — Rust
extract_template_variables()results cached by content hash (SHA-256), capped at 256 entries. - Cached context processor resolution —
resolve_context_processors()cached per settings config, invalidated onsetting_changedsignal. - JIT short-circuit — Views without QuerySets or Models in context skip the entire JIT serialization pipeline (~0.5ms saved).
- Slimmer debug payloads — Event responses send only state variables; handler metadata moved to initial mount. ~68% smaller (~25KB to ~8KB per event).
Security Hardening
- HTTP POST handler gating —
post()now enforces the same security model as the WebSocket path: only@event_handler-decorated methods can be invoked. Event names validated withis_safe_event_name()to block dunders and private methods. - Auto-escaping in Rust engine —
SafeStringvalues propagated to Rust for proper auto-escaping.urlizeandunordered_listfilters escape output to prevent XSS. - Template tag hardening — All PWA template tags use
format_html()andescape()instead ofmark_safe()with f-strings. - Sync endpoint hardening — Removed
@csrf_exemptfromsync_endpoint_view. Added authentication requirement, payload validation, and safe field extraction. - Production JS hardened — All
console.logcalls guarded behinddjustDebugflag. - Pre-release security audit process — 259 new security tests (Python + Rust) covering parameter injection, file upload attacks, URL injection, and XSS. Three GitHub workflows provide automated scanning (bandit, safety, cargo-audit, npm audit, CodeQL), hot spot detection, and CI security gates requiring 85% coverage on security-sensitive modules.
Testing Improvements
The new test utilities make LiveView testing a breeze:
def test_todo_toggle(live_view_client):
client = live_view_client(TodoListView)
client.mount()
client.send_event("toggle_todo", id=1)
assert client.state["todos"][0]["completed"] == True
assert "line-through" in client.rendered_html
The LiveViewSmokeTest mixin provides automated smoke and fuzz testing — add it to any test class and it will exercise your handlers with randomized inputs, catching crashes and permission leaks automatically.
Under the hood, the VDOM diff engine now has a keyed-mutation fuzz test generator (proptest, 1000 cases) that produces tree mutations exercising keyed diff paths more effectively.
Migration Notes
@eventdecorator removed — Use@event_handlerinstead (deprecated since v0.2.2)_allowed_eventsremoved — The backwards-compatibility escape hatch that allowed undecorated methods to be called via WebSocket or HTTP POST is gone. All event handlers must use@event_handler.data-dj-*prefix stripping —data-dj-preset="dark"now sends{preset: "dark"}instead of{dj_preset: "dark"}. Update handler parameter names:dj_footofoo.- Keyed diffing improvements — If you had workarounds for keyed list issues, test removing them.
What's Next?
Version 0.3 establishes djust as a complete, production-ready LiveView solution for Django. With auth, multi-tenancy, PWA, server-push, and AI tooling built in, the foundation covers real-world application needs. Future releases will focus on ecosystem growth, production hardening, and advanced patterns.
Upgrade with: pip install djust>=0.3.0
djust — Phoenix LiveView for Django, powered by Rust
Related Posts
djust 0.2.2: The Debug Panel Gets Real
djust 0.2.2 transforms the debug panel from a static inspector into a live development companion. Event filtering, replay, network inspection, VDOM patch tracing, and live state updates via WebSocket — all wired up and working out of the box.
djust 0.2.1: WebSocket Security Hardening with Three Layers of Defense
djust 0.2.1 locks down WebSocket event dispatch with an event name guard, @event_handler decorator allowlist, server-side rate limiting, and per-IP connection tracking. A security-focused release with zero new features to break.
djust 0.2.0: Template Operators, VDOM Fixes, and a Cleaner Event Syntax
djust 0.2.0 ships template and/or/in operators, critical VDOM diffing and whitespace fixes, a new dj- event prefix, and major dependency upgrades. Here's what changed and how to upgrade.