Skip to main content

A scalable, multi-backend Django notification package supporting FCM, Email, and custom channels with signal-based auto-notifications and a clean public API.

Project description

django-notify-hub

Reusable Django notifications with pluggable delivery backends, template-backed persistence, REST APIs, admin tooling, signals, and optional async dispatch.

Features

  • reusable Django app with src/ package layout
  • clean facade API via notify.send() and notify.send_many()
  • scalable persistence model using shared NotificationTemplate rows plus per-user Notification rows
  • one-off custom notifications with per-record override fields
  • localized title/body content with language-aware resolution helpers
  • pluggable backends: Dummy, Email, FCM, and WebSocket
  • optional Celery async dispatch with retry/backoff
  • DRF API for listing, counting, marking read, and deleting notifications
  • Django admin with filters, stats, and bulk actions
  • model event hooks via notify.on_create() / notify.on_update()
  • architecture based on Facade, Repository, Factory, Strategy, and Observer patterns

Installation

Install the package with uv:

uv add django-notify-hub

Optional extras currently exposed by the package:

  • FCM: uv add 'django-notify-hub[fcm]'
  • Celery: uv add 'django-notify-hub[celery]'
  • WebSocket / Django Channels: uv add 'django-notify-hub[websocket]'
  • all packaged extras: uv add 'django-notify-hub[all]'

Quick start

Add the app and run migrations:

INSTALLED_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "rest_framework",
    "django_notify_hub",
]

DJANGO_NOTIFY = {
    "BACKENDS": ["django_notify_hub.backends.dummy.DummyBackend"],
    "ASYNC": False,
}
uv run python manage.py migrate

Send a notification:

from django_notify_hub import notify
from django_notify_hub.types import NotificationChannel

notify.send(
    title={"en": "Hello", "ar": "مرحبا"},
    body={"en": "Welcome to django-notify-hub."},
    recipient=user,
    channels=(NotificationChannel.DUMMY,),
)

Django setup

Required apps

Minimum:

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "rest_framework",
    "django_notify_hub",
]

Minimal settings

DJANGO_NOTIFY = {
    "BACKENDS": ["django_notify_hub.backends.dummy.DummyBackend"],
    "ASYNC": False,
    "PAGINATION_PAGE_SIZE": 20,
    "PAGINATION_MAX_PAGE_SIZE": 100,
}

API URLs

If you want the REST API, include the package URLs:

from django.urls import include, path

urlpatterns = [
    path("api/notifications/", include("django_notify_hub.api.urls")),
]

Public API

Import the facade singleton:

from django_notify_hub import notify

notify.send(...)

Send one notification to one recipient.

  • title / body: localized dicts such as {"en": "Hello", "ar": "مرحبا"} or callables
  • recipient: Django user instance
  • related_object: object used to resolve callable title/body/data
  • data: JSON-serializable dict or callable
  • priority: LOW | NORMAL | HIGH | CRITICAL
  • channels: tuple of NotificationChannel values

Example:

from django_notify_hub.types import NotificationChannel, NotificationPriority

notify.send(
    title=lambda order: {"en": f"Order #{order.pk} received"},
    body={"en": "We are processing your order."},
    recipient=order.customer,
    related_object=order,
    data=lambda order: {"order_id": str(order.pk), "status": order.status},
    priority=NotificationPriority.HIGH,
    channels=(NotificationChannel.EMAIL,),
)

notify.send_many(...)

Broadcast one payload to many users.

This is where the shared-template persistence model shines: one NotificationTemplate row is created, then one lightweight Notification row per recipient.

notify.send_many(
    title={"en": "Maintenance window", "ar": "صيانة مجدولة"},
    body={"en": "The service will be unavailable at 02:00 UTC."},
    recipients=User.objects.filter(is_staff=True),
    channels=(NotificationChannel.WEBSOCKET,),
)

notify.on_create(...)

Register an automatic notification whenever a model instance is created.

notify.on_create(
    app_label="orders",
    model_name="Order",
    title=lambda order: {"en": f"Order #{order.pk} created"},
    body={"en": "Your order has been received."},
    recipients="customer",
)

notify.on_update(...)

Register an automatic notification when a model is updated. The condition callback receives (old_instance, new_instance).

notify.on_update(
    app_label="orders",
    model_name="Order",
    title={"en": "Order updated"},
    body=lambda order: {"en": f"Order is now {order.status}"},
    recipients="customer",
    condition=lambda old, new: old.status != new.status,
)

Recipient resolution in signal hooks

For notify.on_create() and notify.on_update():

  • pass a concrete user object,
  • or pass a dotted attribute path like "customer" or "invoice.order.customer".

Storage model: template + override fields

The package now uses a mixed persistence model optimized for broadcast and per-user customization.

NotificationTemplate

Stores shared content and shared metadata:

  • title JSON
  • body JSON
  • data
  • priority
  • channel

Notification

Stores per-user state and optional overrides:

  • to_user
  • nullable template
  • lang
  • override_title
  • override_body
  • custom_data
  • custom_priority
  • custom_channel
  • status, is_read, timestamps

How persistence works

Broadcast/shared notifications

  • created through NotificationRepository.bulk_create(...)
  • one shared NotificationTemplate
  • many per-user Notification rows
  • each recipient row stores only user-specific state plus lang

One-off/custom notifications

  • created through NotificationRepository.create(...)
  • no template required
  • values are stored directly in override/custom fields on the Notification row

Language resolution

When a notification is persisted for a recipient, the repository resolves the recipient language from the first available attribute in this order:

  • language
  • lang
  • locale
  • preferred_language

If nothing is found, it falls back to "en".

Compatibility layer

The Notification model still exposes resolved properties so existing integrations can continue reading:

  • notification.title
  • notification.body
  • notification.data
  • notification.priority
  • notification.channel

Language-aware helpers are also available:

  • notification.get_title(lang="en")
  • notification.get_body(lang="en")

REST API

The package ships with DRF endpoints for admins and authenticated users.

Endpoints

  • GET /api/notifications/ — admin list of all notifications
  • GET /api/notifications/my/ — current user's notifications
  • GET /api/notifications/unread-count/ — current user's unread count
  • PATCH /api/notifications/{id}/mark-read/ — mark one as read
  • POST /api/notifications/mark-many-read/ — bulk mark read
  • DELETE /api/notifications/{id}/ — delete one of the current user's notifications
  • POST /api/notifications/delete-many/ — bulk delete

Permissions

  • admin list endpoint requires IsAdminUser
  • self-service endpoints require IsAuthenticated

List filtering and ordering

  • my/ accepts ?is_read=true or ?is_read=false
  • ordering fields: created_at, is_read
  • page size comes from DJANGO_NOTIFY["PAGINATION_PAGE_SIZE"]

Serializer fields

Responses include:

  • id
  • title and body as full localized JSON
  • title_display and body_display resolved for the active request language
  • data, channel, priority, status, is_read, created_at, updated_at

Admin integration

The Django admin includes:

  • read/unread filters
  • channel and priority filters
  • recipient/title preview columns
  • pretty-printed notification data
  • bulk actions to mark read/unread
  • bulk deletion of old read notifications
  • changelist stats from NotificationRepository.get_stats()

Backends

Backends are loaded from DJANGO_NOTIFY["BACKENDS"] using the BackendFactory.

Dummy backend

Best for tests and local development.

DJANGO_NOTIFY = {
    "BACKENDS": ["django_notify_hub.backends.dummy.DummyBackend"],
    "ASYNC": False,
}

Email backend

Uses Django send_mail and requires recipients to have an email attribute. The backend falls back to Django's DEFAULT_FROM_EMAIL and renders localized content using English by default.

DJANGO_NOTIFY = {
    "BACKENDS": ["django_notify_hub.backends.email.EmailBackend"],
    "ASYNC": False,
}

FCM backend

Install the extra:

uv add 'django-notify-hub[fcm]'

Configure credentials with either:

  • DJANGO_NOTIFY["FCM_CREDENTIALS_FILE"], or
  • GOOGLE_APPLICATION_CREDENTIALS
DJANGO_NOTIFY = {
    "BACKENDS": ["django_notify_hub.backends.fcm.FCMBackend"],
    "FCM_CREDENTIALS_FILE": BASE_DIR / "firebase.json",
}

WebSocket backend

Install the packaged websocket extra in the host project:

uv add 'django-notify-hub[websocket]'

Configure the backend:

DJANGO_NOTIFY = {
    "BACKENDS": ["django_notify_hub.backends.websocket.WebSocketBackend"],
    "WEBSOCKET_LAYER_ALIAS": "default",
    "WEBSOCKET_EVENT_TYPE": "notify.message",
    "WEBSOCKET_USER_GROUP_TEMPLATE": "notify.user.{user_id}",
}

Optional custom group resolver:

DJANGO_NOTIFY = {
    "WEBSOCKET_GROUP_RESOLVER": "myapp.notifications.websocket_groups",
}

Resolver signature:

def websocket_groups(recipient, payload):
    return [f"tenant.{recipient.tenant_id}", f"notify.user.{recipient.pk}"]

The backend sends a Channels event containing event["notification"] with:

  • channel
  • recipient_ids
  • recipient_id for single-recipient sends
  • title, body, data, priority

Your consumer should implement the configured event type, which defaults to notify.message.

Custom backends

Create a subclass of AbstractNotificationBackend and implement:

  • channel
  • _deliver(payload, recipient)
  • optionally _deliver_many(payload, recipients) for native batch delivery
  • optionally setup() for one-time initialization

Async delivery with Celery

Install the extra:

uv add 'django-notify-hub[celery]'

Configure:

DJANGO_NOTIFY = {
    "ASYNC": True,
    "CELERY_QUEUE": "notifications",
    "CELERY_MAX_RETRIES": 3,
    "CELERY_RETRY_BACKOFF": 60,
}

Behavior:

  • sync mode persists and delivers in-process
  • async mode dispatches Celery tasks
  • if Celery is not installed, the service falls back to synchronous delivery

Settings reference

  • BACKENDS: dotted backend class paths
  • DEFAULT_CHANNELS: available config key; current facade methods still use the explicit channels= argument and default to FCM if omitted
  • ASYNC: toggle Celery task dispatch
  • CELERY_QUEUE: queue name for notification tasks
  • CELERY_MAX_RETRIES: max retry count
  • CELERY_RETRY_BACKOFF: exponential retry base delay in seconds
  • FCM_CREDENTIALS_FILE: Firebase service account file path
  • WEBSOCKET_LAYER_ALIAS: Channels layer alias
  • WEBSOCKET_EVENT_TYPE: Channels event type
  • WEBSOCKET_USER_GROUP_TEMPLATE: default group naming format
  • WEBSOCKET_GROUP_RESOLVER: dotted path or callable for custom group routing
  • DEFAULT_PRIORITY: available config key; current facade methods use the explicit priority= argument and default to NotificationPriority.NORMAL if omitted
  • PAGINATION_PAGE_SIZE / PAGINATION_MAX_PAGE_SIZE: REST API pagination limits

Important channel behavior

NotificationPayload.channels accepts a tuple, but the current runtime implementation persists and dispatches using the first channel in that tuple. If you need multi-channel fan-out, call notify.send() more than once or add orchestration in your project layer.

Testing

Run the test suite:

uv run python -m pytest tests/ --no-cov -q

Migration sanity check:

uv run python -m django makemigrations django_notify_hub --check --dry-run --settings=test_settings

Build distributions:

uv build

Status

The package is fully structured as a reusable Django app and includes tests for models, repository, services, API, registry, and backends.

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

django_notify_hub-0.1.1.tar.gz (28.3 kB view details)

Uploaded Source

Built Distribution

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

django_notify_hub-0.1.1-py3-none-any.whl (42.0 kB view details)

Uploaded Python 3

File details

Details for the file django_notify_hub-0.1.1.tar.gz.

File metadata

  • Download URL: django_notify_hub-0.1.1.tar.gz
  • Upload date:
  • Size: 28.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for django_notify_hub-0.1.1.tar.gz
Algorithm Hash digest
SHA256 dfec2de56e8cfc5368837bebf253e6c4deb21e55f1568e6306515deb524998b5
MD5 a694553d1e94a1d3059495c909537ec9
BLAKE2b-256 b542f0e69aef285191762b1720a7c6f83e7dd1a14a79ada9fa438421f2b120ac

See more details on using hashes here.

File details

Details for the file django_notify_hub-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: django_notify_hub-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 42.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for django_notify_hub-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b6a11a9edb19213aae0113a07189fb0ab6aa01bbcfeb68402378a570f99725cf
MD5 9cbebf45bd0eddb2aaef1052fdb99b27
BLAKE2b-256 edd8c7b52553e429163e09f0f8c749ecce8fa96971b4cd5aa8ade486fb73807c

See more details on using hashes here.

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