Skip to main content

Realtime browser events for Django + PostgreSQL

Project description

DjangoRealtime

Add realtime capabilities to your Django web app in no time. No WebSockets, no Channels, no processes to run, no Redis. Just set up and go!

Django + PostgreSQL = Realtime Updates in Browser

Basic Usage

from djangorealtime import publish

publish(user_id=user.id, event_type='task_complete', detail={'task_id': 123, 'status': 'success'})

Browsers receive it instantly ✨:

window.addEventListener('djr:task_complete', (e) => {
    console.log(e.detail);  // {task_id: 123, status: 'success'}
});

How it works

Built on HTTP Server-Sent Events (SSE) and PostgreSQL pub/sub. Everything is auto-configured:

  • Secure by default - events are user-scoped by default, not broadcast to everyone
  • Works everywhere - SSE is your standard HTTP, no WebSocket complexity
  • Scales across workers - multiple Django processes can communicate via PostgreSQL
  • Zero fluff - runs on your existing Django + PostgreSQL stack
  • Automatic reconnection - handles network interruptions transparently
  • Event persistence - events stored in database for reliability and replay
  • Django admin integration - view and replay events from the admin panel

Table of Contents

Installation

pip install djrealtime

Add to Django:

INSTALLED_APPS = [
    # ...
    'djangorealtime',
]

Include URLs for automatic endpoint setup:

urlpatterns = [
    path('realtime/', include('djangorealtime.urls')),
    # ...
]

Database Migration

To create the necessary table, run:

python manage.py migrate djangorealtime

You don't need this step if you disable event storage in Settings.

Frontend Setup

Add this in your base HTML template in <head>. This will add the necessary JavaScript to automatically connect and listen for events:

{% load djangorealtime_tags %}
{% djangorealtime_js %}

Usage

Publishing Events

User-Scoped Events

from djangorealtime import publish

publish(user_id=user.id, event_type='task_complete', detail={'task_id': 123, 'status': 'success'})

These events are only sent to the specified user who is logged in using Django's authentication system. user_id is the primary key of your user model. It can be string or integer.

Global Events

from djangorealtime import publish_global
publish_global(event_type='turn_off_lights', detail={'time': '10:00 PM'})

These events are broadcast to all connected clients or browsers, regardless of authentication.

System Events

from djangorealtime import publish_system
publish_system(event_type='server_restart', detail={'reason': 'maintenance'})

These events are sent to internal system processes only, not to browsers. Like another Django instance or a Django management command listening for events.

Listening to Events

In your JavaScript code, listen to events using DOM events or the helper method. Just listen on window using the djr: prefix before your event type.

window.addEventListener('djr:task_complete', (e) => {
    console.log(e.detail);  // {task_id: 123, status: 'success'}
});

Advanced Features

Filtering events for entity

You can filter events by entity using a special :id field in the detail dictionary. This allows you to listen to both general and specific events. Nifty!

publish(user_id=user.id, event_type='page_imported', detail={':id': 42}) # say, page with ID 42
// Listen to specific page_imported event for page ID 42
window.addEventListener('djr:page_imported:42', (e) => {
    console.log('Page 42 was imported', e.detail);
});
// djr:page_imported will also be fired

Listening from Backend

You can also listen to events from other backend processes, like Django management commands. You can subscribe to all events using the subscribe decorator.

from djangorealtime import subscribe, Event

@subscribe
def on_event(event: Event):
    print(f"Received {event.scope} event: {event.type} with detail: {event.detail}")

Event Storage

PostgreSQL pub/sub is not persistent. But we built on top of it to provide reliable event storage out of the box.

All events are efficiently stored in your Django database by default.

There is a limit of 8kB payload per event due to PostgreSQL NOTIFY limitations. We do not think you should even be passing a fraction of that in normal usage. Use references or IDs in the event detail to keep it light.

Events including detail are stored in the database, so make sure not to pass sensitive information directly.

Set 'ENABLE_EVENT_STORAGE': False in settings to disable event storage if you don't need it.

Django Admin

DjangoRealtime seamlessly integrates with Django admin to provide a simple interface to view events and activities. You can filter events by type, scope etc. And wait, there's more! You can even replay events directly from the admin interface.

Replaying Events

Event model of DjangoRealtime has a replay() method to resend the event.

from djangorealtime.models import Event
event = Event.objects.get(id=1)
event.replay()  # Resends the event

Or you can replay from Django admin by selecting events and choosing "Replay selected events" action.

Hooks

You can define custom callback functions to be executed on certain events.

ON_RECEIVE_HOOK

Called when an event is received by the listener, before any processing. Returning None aborts further processing.

from djangorealtime import Event
from datetime import datetime

def on_receive_hook(event: Event) -> Event | None:
    print(f"[ON_RECEIVE_HOOK] Event received: {event.type}")
    event.detail['received_at'] = datetime.now().isoformat()
    return event

BEFORE_SEND_HOOK Called before sending an event to clients. You can modify or abort the event here.

from djangorealtime import Event
from django.http import HttpRequest

def before_send_hook(event: Event, request: HttpRequest) -> Event | None:
    user_info = "anonymous"
    if hasattr(request, 'user') and request.user.is_authenticated:
        user_info = request.user.email

    print(f"[BEFORE_SEND_HOOK] Sending {event.type} to {user_info}")
    return event

Hooks can be set in DJANGOREALTIME settings.


Configuration

Performance and Scalability

DjangoRealtime is designed to be lightweight and efficient. We have production apps using DjangoRealtime at scale.

You can use multiple Django instances behind a load balancer. Each instance will have its own listener process. All Django instances communicate via your PostgreSQL instance.

Please note, a listener will always maintain a single persistent database connection to PostgreSQL. It is optimised for low database connection count, so it closes the connection after operations.

We've seen very low latency with all features enabled. If you want even lower latency, you can disable event storage by having 'ENABLE_EVENT_STORAGE': False in settings.

All events use a single PostgreSQL channel. Then we demultiplex events in the listener process based on type.

Settings

All of the settings are optional. Add to your Django settings.py:

DJANGOREALTIME = {
    'AUTO_LISTEN': True,  # Auto-start a non-blocking listener thread with web server(default: True)
    'EVENT_MODEL': 'djangorealtime.models.Event',  # If you want to use a custom event model
    'ENABLE_EVENT_STORAGE': True,  # Enable/disable event storage in DB (default: True)

    'ON_RECEIVE_HOOK': callback_function,  # Custom callback on receiving an event
    'BEFORE_SEND_HOOK': callback_function,  # Custom callback before sending an event to clients
}

Note: AUTO_LISTEN, only, by choice, starts to listen when a web server is running. It does not start automatically when running management commands. This is to avoid unnecessary connections when not needed.

If you have a long-running management like a queue worker that needs to listen to system events, you can start the listener manually:

from djangorealtime import Listener
listener = Listener()
listener.start()  # Non-blocking

Note: you don't need to start an additional listener for publishing an event from another process. You only need a listener if you want to listen to events on that process.

Manual JavaScript Connection

By default, JavaScript connection is auto-established when you include the JS snippet using {% djangorealtime_js %} tag.

If you want to manually connect, use: {% djangorealtime_js auto_connect=False %} and then:

DjangoRealtime.connect({
    endpoint: '/realtime/sse/',  // Default
});

By default, your HTTP session cookie is used for authentication, like any other AJAX request. If you need token-based auth etc. pass as query parameters with endpoint.

DjangoRealtime.connect({
    endpoint: `/realtime/sse/?id_token=${jwt}`,
});

Custom headers are not supported in official EventSource SSE helper.


Local Development

For local development, DjangoRealtime supports both ASGI and WSGI servers:

DjangoRealtime works seamlessly with Django's built-in development server (runserver), which runs in WSGI mode. The library automatically detects when you're using runserver with WSGI and uses makeshift adapter to support SSE.

Requirements

  • Django >= 5.0
    • With ASGI server (like Hypercorn, Daphne, Uvicorn etc.)
    • psycopg3
  • Python >= 3.10
  • PostgreSQL >= 14

Licence

DjangoRealtime is released under the MIT Licence.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

djrealtime-1.0.0.tar.gz (23.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

djrealtime-1.0.0-py3-none-any.whl (22.8 kB view details)

Uploaded Python 3

File details

Details for the file djrealtime-1.0.0.tar.gz.

File metadata

  • Download URL: djrealtime-1.0.0.tar.gz
  • Upload date:
  • Size: 23.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for djrealtime-1.0.0.tar.gz
Algorithm Hash digest
SHA256 572e0e90ae1ec3c36418572c72a3204fe560343b68624b0f27c1b30e651f21dd
MD5 91f942b3906612470ddaad704639f96b
BLAKE2b-256 acc24e5e0974f627a0aa311d71f8803c6a9d66fbf3aa758fd454dd49d1f6a5f2

See more details on using hashes here.

Provenance

The following attestation bundles were made for djrealtime-1.0.0.tar.gz:

Publisher: ci.yml on usmanhalalit/DjangoRealtime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file djrealtime-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: djrealtime-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 22.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for djrealtime-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a2d87cf9be8c4458ed39d08f405ed685582d69c91e4ea5e69edfeaa55f1e83d4
MD5 5cc60f3577f3ad47ae56879467305f9f
BLAKE2b-256 500d2703d2f6fe4c950ca03e1fd3280cf21fb91a55a7ab2f99e8a07f0af31850

See more details on using hashes here.

Provenance

The following attestation bundles were made for djrealtime-1.0.0-py3-none-any.whl:

Publisher: ci.yml on usmanhalalit/DjangoRealtime

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page