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()andnotify.send_many() - scalable persistence model using shared
NotificationTemplaterows plus per-userNotificationrows - 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 callablesrecipient: Django user instancerelated_object: object used to resolve callable title/body/datadata: JSON-serializable dict or callablepriority:LOW | NORMAL | HIGH | CRITICALchannels: tuple ofNotificationChannelvalues
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:
titleJSONbodyJSONdataprioritychannel
Notification
Stores per-user state and optional overrides:
to_user- nullable
template langoverride_titleoverride_bodycustom_datacustom_prioritycustom_channelstatus,is_read, timestamps
How persistence works
Broadcast/shared notifications
- created through
NotificationRepository.bulk_create(...) - one shared
NotificationTemplate - many per-user
Notificationrows - 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
Notificationrow
Language resolution
When a notification is persisted for a recipient, the repository resolves the recipient language from the first available attribute in this order:
languagelanglocalepreferred_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.titlenotification.bodynotification.datanotification.prioritynotification.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 notificationsGET /api/notifications/my/— current user's notificationsGET /api/notifications/unread-count/— current user's unread countPATCH /api/notifications/{id}/mark-read/— mark one as readPOST /api/notifications/mark-many-read/— bulk mark readDELETE /api/notifications/{id}/— delete one of the current user's notificationsPOST /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=trueor?is_read=false- ordering fields:
created_at,is_read - page size comes from
DJANGO_NOTIFY["PAGINATION_PAGE_SIZE"]
Serializer fields
Responses include:
idtitleandbodyas full localized JSONtitle_displayandbody_displayresolved for the active request languagedata,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"], orGOOGLE_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:
channelrecipient_idsrecipient_idfor single-recipient sendstitle,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 pathsDEFAULT_CHANNELS: available config key; current facade methods still use the explicitchannels=argument and default to FCM if omittedASYNC: toggle Celery task dispatchCELERY_QUEUE: queue name for notification tasksCELERY_MAX_RETRIES: max retry countCELERY_RETRY_BACKOFF: exponential retry base delay in secondsFCM_CREDENTIALS_FILE: Firebase service account file pathWEBSOCKET_LAYER_ALIAS: Channels layer aliasWEBSOCKET_EVENT_TYPE: Channels event typeWEBSOCKET_USER_GROUP_TEMPLATE: default group naming formatWEBSOCKET_GROUP_RESOLVER: dotted path or callable for custom group routingDEFAULT_PRIORITY: available config key; current facade methods use the explicitpriority=argument and default toNotificationPriority.NORMALif omittedPAGINATION_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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfec2de56e8cfc5368837bebf253e6c4deb21e55f1568e6306515deb524998b5
|
|
| MD5 |
a694553d1e94a1d3059495c909537ec9
|
|
| BLAKE2b-256 |
b542f0e69aef285191762b1720a7c6f83e7dd1a14a79ada9fa438421f2b120ac
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6a11a9edb19213aae0113a07189fb0ab6aa01bbcfeb68402378a570f99725cf
|
|
| MD5 |
9cbebf45bd0eddb2aaef1052fdb99b27
|
|
| BLAKE2b-256 |
edd8c7b52553e429163e09f0f8c749ecce8fa96971b4cd5aa8ade486fb73807c
|