11 KiB
Уведомления
Текущая система уведомлений — verb-based Django app с хранением в PostgreSQL и доставкой через WebSocket, email и Web Push.
Источники истины
- models:
backend/notifications/models.py - создание:
backend/notifications/signals.py - channel routing:
backend/notifications/channels.py - REST API:
backend/notifications/api/ - senders/tasks:
backend/notifications/senders/иbackend/notifications/tasks/ - realtime state events:
backend/notifications/realtime.py - frontend context:
frontend/src/contexts/NotificationsContext.tsx - Web Push hook:
frontend/src/hooks/useWebPush.ts - tests:
backend/tests/api/v1/notifications/иbackend/tests/realtime/
NotificationService в backend/notifications/services.py — deprecated stub и выбрасывает NotImplementedError. Новый код обязан использовать notify.send(...).
Создание уведомления
from notifications.signals import notify
notify.send(
sender=request.user,
recipient=recipient,
verb="document_assigned",
target=document,
description="Вам назначен документ",
action_url=f"/documents/{document.pk}",
data={
"title": "Новый документ",
"document_id": document.pk,
},
)
recipient может быть одним Employee или списком/tuple сотрудников. Обязательны recipient и verb. actor, action_object и target хранятся через GenericForeignKey.
Не создавайте уведомления пользователю о его собственном действии, если продуктовый сценарий явно этого не требует. action_url должен быть внутренним разрешённым маршрутом frontend, а data — JSON-serializable.
Для сообщений verb chat_mention создаётся только по проверенному структурированному полю Message.mentions, а не по произвольному тексту после @. Получатель упоминания исключается из обычного chat_new_message или chat_reply для этого же сообщения, поэтому одно действие не создаёт два уведомления. Персональное упоминание обходит mute конкретного чата (ChatUserSettings.notifications_enabled=false), но общие настройки канала, DND и системное разрешение Web Push продолжают применяться.
Поток доставки
notify.sendсоздаётNotification.post_saveпроверяетUserChannelPreferences, disabled verb и DND.- Через
transaction.on_commitзапускаются Celery tasks. - WebSocket sender отправляет событие в
user_{id}. - Email sender отправляет instant email либо digest.
- Push sender выбирает активные устройства и маршруты приложений.
Если Celery broker недоступен, создание записи уведомления не откатывается: ошибка постановки задачи логируется. Повторная доставка требует отдельной операционной проверки.
Модели
Notification содержит recipient, actor, verb, action object, target, description, action URL, JSON data, unread/deleted/public/emailed flags и timestamps. Удаление через API мягкое (deleted=True).
UserChannelPreferences управляет:
web_enabled;email_enabledиemail_frequency:instant,daily,weekly,never;push_enabled;- DND interval;
disabled_verbs.
При DND отправляется только silent WebSocket, если web channel включён. Email и push в этот момент не ставятся в очередь.
NotificationInstallation связывает Web Push subscription с приложением main или messenger и device group. NotificationDeliveryRouting выбирает приложение доставки по категориям: messages, tasks, requests, procurement, documents, guests, calendar, feed, other.
REST API
Prefix: /api/v1/notifications/.
| Method и path | Назначение |
|---|---|
GET / |
Список; filters page, page_size, verb, unread_only, search |
GET /count/ |
Общее количество непрочитанных |
GET /summary/ |
Агрегаты unread для навигации и доменных экранов |
GET /verb-types/ |
Доступные verbs и счётчики |
POST /{id}/read/ |
Отметить одно прочитанным |
POST /{id}/unread/ |
Вернуть в непрочитанные |
DELETE /{id}/ |
Soft-delete |
POST /read-all/ |
Прочитать все или один verb |
POST /category/read/ |
Прочитать список verbs категории |
DELETE /delete-all-read/ |
Soft-delete всех прочитанных |
GET, PUT /preferences/ |
Настройки каналов |
GET /push/vapid-key/ |
Публичный VAPID key |
POST /push/subscribe/ |
Создать/обновить push subscription |
POST, DELETE /push/unsubscribe/ |
Удалить одну или все subscriptions |
POST /push/installations/heartbeat/ |
Обновить installation lease |
GET, PUT /push/delivery-routing/ |
Маршрутизация main/messenger |
DELETE /push/installations/disconnect/ |
Отключить приложение на устройстве |
Endpoint /push/subscriptions/ отсутствует. Не используйте старые примеры с /api/v2/notifications/ или /ws/notifications/.
Realtime events
Через единый /ws/ frontend получает:
notification— новая запись;unread_count— актуальный общий счётчик;notification_read— прочтение в другой вкладке/сессии;notifications_read_all— массовое прочтение.
NotificationsContext объединяет REST initial load и realtime delta, подавляет дубли и выполняет fallback refresh. Не поднимайте отдельный notifications WebSocket.
Web Push
Backend требует VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY и VAPID_ADMIN_EMAIL. Subscription сохраняется в django-push-notifications WebPushDevice и связывается с NotificationInstallation.
Frontend flow:
- проверяет browser support и permission;
- получает VAPID public key;
- регистрирует service worker;
- отправляет installation heartbeat;
- только по user gesture запрашивает permission и создаёт subscription;
- передаёт endpoint,
p256dh,auth, installation/device group и app kind backend-у.
Push работает только в secure context, кроме localhost. Permission denied нельзя повторно запросить из JavaScript — пользователь должен изменить настройку сайта в браузере.
При permanent delivery error (404, 410, expired/unregistered subscription) device деактивируется и отвязывается от installation. Transient error не удаляет устройство.
Для messenger-only route неуспешная доставка может fallback-нуться на main installation того же device group. Устаревшая messenger installation также возвращает доставку в main.
instant ставит отдельную Celery task при создании уведомления. daily и weekly обрабатываются Celery Beat в 09:00 по timezone проекта. never отключает email.
Проверьте EMAIL_BACKEND, SMTP settings, DEFAULT_FROM_EMAIL и SITE_URL. В DEBUG по умолчанию используется console email backend.
Настройки
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
CELERY_BROKER_URL=redis://127.0.0.1:6379/0
CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/1
VAPID_PUBLIC_KEY=change-me
VAPID_PRIVATE_KEY=change-me
VAPID_ADMIN_EMAIL=admin@example.com
SITE_URL=http://localhost:8000
Запуск worker и Beat:
cd backend
celery -A eusrr_backend worker -l info
celery -A eusrr_backend beat -l info
Добавление нового verb
- Выберите стабильное имя в
snake_caseс доменным prefix. - Добавьте translation/category mapping на frontend, если verb виден пользователю.
- Передавайте title в
data.title, если техническое имя verb не подходит как заголовок. - Передавайте ID доменного объекта в
dataдля unread summary/navigation. - Не отправляйте секреты и большой payload.
- Добавьте тест создания и routing по каналам.
- Добавьте тест action URL и self-notification rule.
Проверка
cd backend
pytest tests/api/v1/notifications tests/realtime -q
Для ручной проверки создайте уведомление через notify.send, затем проверьте REST list, Celery log, WebSocket event и push device. Не вызывайте sender напрямую: это обходит channel preferences, DND и transaction boundary.
Диагностика
- Запись есть, realtime нет: проверьте Celery task
notifications.send_websocket_notification, Redis channel layer и подписку наuser_{id}. - Push не создаётся: проверьте HTTPS/localhost, permission, service worker и VAPID public key.
- Push создан, но не приходит: проверьте
push_enabled, activeWebPushDevice, installation route и Celery worker. - Уведомление дублируется: найдите повторный
notify.send; UI deduplication не должна маскировать backend double-create. - Unread count устарел: операции read должны идти через API/model methods, которые инвалидируют cache и отправляют realtime state event.
- Email не приходит: проверьте frequency, DND, SMTP и поле
emailed.