Home Features Philosophy Docs Blog Errors Security Examples FAQ
Releases - Part 5 of 5

djust 0.3.0 — "Phoenix Rising" 🔥

djust Team | | 9 min read
post.title

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 binding
  • dj-confirm="Are you sure?" — Confirmation dialogs
  • dj-loading — Show/hide/disable during events
  • dj-transition — CSS enter/leave animations
  • dj-optimistic — Instant UI updates with auto-rollback
  • dj-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 use django.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 when daphne is in INSTALLED_APPS but whitenoise middleware is missing (a common deployment gotcha).
  • Simplified root elementdj-view is now the only required attribute. Both client and server auto-infer dj-root from dj-view at init time — no explicit root attribute needed.
  • Auto-build client.js — Pre-commit hook automatically rebuilds client.js from src/ 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 json for 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 Serverpython manage.py djust_mcp starts a Model Context Protocol server providing framework introspection, system checks, scaffolding, and validation. Works with Claude Code, Cursor, and Windsurf.
  • One-command setupdjust mcp install auto-configures your editor's MCP settings.
  • Project scaffoldingpython -m djust new myapp creates a full project with --with-auth, --with-db, --with-presence, --with-streaming, and --from-schema options.
  • Focused AI docsdocs/ai/ contains guides optimized for LLM consumption, plus docs/llms.txt for 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 17 json.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 resolutionresolve_context_processors() cached per settings config, invalidated on setting_changed signal.
  • 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 gatingpost() now enforces the same security model as the WebSocket path: only @event_handler-decorated methods can be invoked. Event names validated with is_safe_event_name() to block dunders and private methods.
  • Auto-escaping in Rust engineSafeString values propagated to Rust for proper auto-escaping. urlize and unordered_list filters escape output to prevent XSS.
  • Template tag hardening — All PWA template tags use format_html() and escape() instead of mark_safe() with f-strings.
  • Sync endpoint hardening — Removed @csrf_exempt from sync_endpoint_view. Added authentication requirement, payload validation, and safe field extraction.
  • Production JS hardened — All console.log calls guarded behind djustDebug flag.
  • 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

  • @event decorator removed — Use @event_handler instead (deprecated since v0.2.2)
  • _allowed_events removed — 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 strippingdata-dj-preset="dark" now sends {preset: "dark"} instead of {dj_preset: "dark"}. Update handler parameter names: dj_foo to foo.
  • 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

Share this post

Related Posts