From 1972f89bea7d0a8d165e28f31414f56f52284dd7 Mon Sep 17 00:00:00 2001 From: lizerk Date: Mon, 9 Mar 2026 09:51:00 +0300 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=D0=B7=20=D0=B1=D1=8D=D0=BA?= =?UTF-8?q?=D0=B5=D0=BD=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Фронтенд: добавлена обработка поля 'notifications' (бэкенд возвращает { notifications: [...] }) - Фронтенд: исправлены URL endpoints для отметки прочитанным (/read/ вместо /mark_read/) - Бэкенд: добавлено поле 'short_message' в ответ API для корректного отображения превью --- backend/api/v1/notifications/views.py | 3 ++- frontend/src/hooks/useApi.ts | 3 ++- frontend/src/lib/api.ts | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/api/v1/notifications/views.py b/backend/api/v1/notifications/views.py index 4abc2352..16b94c5a 100644 --- a/backend/api/v1/notifications/views.py +++ b/backend/api/v1/notifications/views.py @@ -63,7 +63,8 @@ def get_notifications(request): { 'id': n.id, 'title': n.title, - 'message': n.message, # Полное сообщение для списка + 'message': n.message, + 'short_message': n.short_message or n.message[:150], # Краткое сообщение для превью 'category': n.notification_type.category.code, 'category_name': n.notification_type.category.name, 'icon': n.notification_type.category.icon, diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index 22bbaf9c..1b06901c 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -179,7 +179,8 @@ export function useNotifications() { async function fetchNotifications() { try { const data = await apiClient.getNotifications(); - const notifs = data.results || data; + // Бэкенд возвращает { notifications: [...] } + const notifs = data.notifications || data.results || data; // Убеждаемся что notifs это массив const notificationsArray = Array.isArray(notifs) ? notifs : []; setNotifications(notificationsArray); diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index a376f3bc..cdc080e6 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -792,13 +792,13 @@ class ApiClient { } async markNotificationAsRead(id: number): Promise { - await this.request(`/api/v1/notifications/${id}/mark_read/`, { + await this.request(`/api/v1/notifications/${id}/read/`, { method: 'POST', }); } async markAllNotificationsAsRead(): Promise { - await this.request('/api/v1/notifications/mark_all_read/', { + await this.request('/api/v1/notifications/read-all/', { method: 'POST', }); } From 30ba5bfceb3a45f77ea3c4ad8681d744b7e83f59 Mon Sep 17 00:00:00 2001 From: lizerk Date: Mon, 9 Mar 2026 09:52:49 +0300 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B0?= =?UTF-8?q?=20=D1=81=D0=BE=20=D0=B2=D1=81=D0=B5=D0=BC=D0=B8=20=D1=83=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Полный список всех уведомлений с прокруткой - Поиск по заголовку и содержимому - Фильтрация по статусу (все/непрочитанные/прочитанные) - Фильтрация по категориям - Кнопка "Прочитать все" для непрочитанных - Красивые карточки с иконками категорий - Клик на уведомление переходит по action_url - Responsive дизайн --- frontend/src/app/notifications/page.tsx | 244 ++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 frontend/src/app/notifications/page.tsx diff --git a/frontend/src/app/notifications/page.tsx b/frontend/src/app/notifications/page.tsx new file mode 100644 index 00000000..f67eeef2 --- /dev/null +++ b/frontend/src/app/notifications/page.tsx @@ -0,0 +1,244 @@ +'use client'; + +import { AppShell } from '@/components/AppShell'; +import { useNotifications } from '@/hooks/useApi'; +import { Bell, Check, CheckCheck, Filter, Search, Trash2 } from 'lucide-react'; +import { useState, useMemo } from 'react'; +import { formatDistanceToNow } from 'date-fns'; +import { ru } from 'date-fns/locale/ru'; +import Link from 'next/link'; + +export default function NotificationsPage() { + const { notifications: notificationsData, loading, markAsRead, markAllAsRead } = useNotifications(); + const notifications = Array.isArray(notificationsData) ? notificationsData : []; + + const [search, setSearch] = useState(''); + const [filterRead, setFilterRead] = useState<'all' | 'unread' | 'read'>('all'); + const [filterCategory, setFilterCategory] = useState(''); + const [filtersOpen, setFiltersOpen] = useState(false); + + // Уникальные категории + const categories = useMemo(() => { + const cats = new Set(); + notifications.forEach((n: any) => { + if (n.category_name) cats.add(n.category_name); + }); + return Array.from(cats).sort(); + }, [notifications]); + + // Фильтрация + const filteredNotifications = useMemo(() => { + return notifications.filter((n: any) => { + // Фильтр по прочитанности + if (filterRead === 'unread' && n.is_read) return false; + if (filterRead === 'read' && !n.is_read) return false; + + // Фильтр по категории + if (filterCategory && n.category_name !== filterCategory) return false; + + // Поиск + if (search) { + const q = search.toLowerCase(); + const title = (n.title || '').toLowerCase(); + const message = (n.message || '').toLowerCase(); + if (!title.includes(q) && !message.includes(q)) return false; + } + + return true; + }); + }, [notifications, search, filterRead, filterCategory]); + + const unreadCount = notifications.filter((n: any) => !n.is_read).length; + + const handleNotificationClick = async (notification: any) => { + if (!notification.is_read) { + await markAsRead(notification.id); + } + if (notification.action_url) { + window.location.href = notification.action_url; + } + }; + + const getCategoryIcon = (icon?: string) => { + // Можно добавить маппинг иконок по необходимости + return icon || '📋'; + }; + + return ( + +
+ {/* Заголовок */} +
+
+

Уведомления

+

+ {unreadCount > 0 ? `${unreadCount} непрочитанных` : 'Все прочитано'} +

+
+ {unreadCount > 0 && ( + + )} +
+ + {/* Поиск и фильтры */} +
+
+
+ + setSearch(e.target.value)} + placeholder="Поиск по уведомлениям..." + className="w-full rounded-lg border border-gray-200 bg-gray-50 py-2.5 pl-9 pr-3 text-sm text-gray-800 transition focus:border-sky-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-sky-100" + /> +
+ +
+ + {filtersOpen && ( +
+ + + + + {(filterRead !== 'all' || filterCategory) && ( + + )} +
+ )} +
+ + {/* Список уведомлений */} + {loading ? ( +
+
+

Загрузка уведомлений...

+
+ ) : filteredNotifications.length === 0 ? ( +
+ +

+ {search || filterRead !== 'all' || filterCategory + ? 'Нет уведомлений по заданным критериям' + : 'Нет уведомлений'} +

+

+ {search || filterRead !== 'all' || filterCategory + ? 'Попробуйте изменить фильтры' + : 'Все новые уведомления появятся здесь'} +

+
+ ) : ( +
+ {filteredNotifications.map((notification: any) => ( +
handleNotificationClick(notification)} + className={`group cursor-pointer rounded-xl border transition hover:shadow-md ${ + notification.is_read + ? 'border-gray-200 bg-white' + : 'border-sky-200 bg-sky-50/30' + }`} + > +
+ {/* Иконка */} +
+ {getCategoryIcon(notification.icon)} +
+ + {/* Содержимое */} +
+
+

+ {notification.title} +

+ {!notification.is_read && ( +
+ )} +
+ +

+ {notification.short_message || notification.message} +

+ +
+ {notification.category_name && ( + + {notification.category_name} + + )} + + {formatDistanceToNow(new Date(notification.created_at), { + addSuffix: true, + locale: ru, + })} + +
+
+
+
+ ))} +
+ )} +
+
+ ); +} From bd1238b3da7448e5997efd7efaa9a3e0b75def15 Mon Sep 17 00:00:00 2001 From: lizerk Date: Mon, 9 Mar 2026 09:56:02 +0300 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BA=D0=BE=D0=BD=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B9?= =?UTF-8?q?=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен маппинг категорий на эмодзи-иконки - Исправлено отображение текста вместо иконок (bi-chat-dots -> 💬) - Увеличен размер иконок (text-xl) - Добавлены эмодзи для всех основных категорий --- frontend/src/app/notifications/page.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/notifications/page.tsx b/frontend/src/app/notifications/page.tsx index f67eeef2..5ff06e46 100644 --- a/frontend/src/app/notifications/page.tsx +++ b/frontend/src/app/notifications/page.tsx @@ -59,9 +59,21 @@ export default function NotificationsPage() { } }; - const getCategoryIcon = (icon?: string) => { - // Можно добавить маппинг иконок по необходимости - return icon || '📋'; + const getCategoryIconEmoji = (categoryCode?: string, icon?: string) => { + // Маппинг категорий на эмодзи + const iconMap: Record = { + 'communications': '💬', + 'messages': '💬', + 'requests': '📝', + 'documents': '📄', + 'calendar': '📅', + 'system': '⚙️', + 'finance': '💰', + 'procurement': '🛒', + 'equipment': '🖥️', + }; + + return iconMap[categoryCode || ''] || '📋'; }; return ( @@ -192,7 +204,7 @@ export default function NotificationsPage() {
{/* Иконка */}
- {getCategoryIcon(notification.icon)} + {getCategoryIconEmoji(notification.category, notification.icon)}
{/* Содержимое */} From 70f9c1950d2f3f84f173d26a09e965963de0e6a6 Mon Sep 17 00:00:00 2001 From: lizerk Date: Mon, 9 Mar 2026 10:05:57 +0300 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20URL-=D1=8B=20=D0=B2=20=D1=83=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=D1=85=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D1=83=D1=89=D0=B5=D1=81=D1=82=D0=B2=D1=83?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D0=B5=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D1=8B=20=D1=84=D1=80=D0=BE=D0=BD=D1=82=D0=B5=D0=BD=D0=B4?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /requests/{id}/ → /requests - /documents/{id}/ → /documents - /calendar (вместо /?event_id={id}) - /procurement/orders/{id}/ → /procurement - /messages (вместо /communications/chats/{id}/) - / для постов ленты Теперь клик по уведомлениям не вызывает 404 ошибки --- backend/api/v1/procurement/views.py | 20 +++++++++---------- backend/calendar_app/notification_signals.py | 7 ++----- .../communications/notification_signals.py | 8 ++++---- backend/documents/notification_signals.py | 10 +++++----- backend/feed/notification_signals.py | 6 +++--- backend/notifications/task_base.py | 4 ++-- backend/procurement/signals.py | 6 +++--- backend/requests_app/notification_signals.py | 6 +++--- 8 files changed, 32 insertions(+), 35 deletions(-) diff --git a/backend/api/v1/procurement/views.py b/backend/api/v1/procurement/views.py index 5766cb66..b99b6ffb 100644 --- a/backend/api/v1/procurement/views.py +++ b/backend/api/v1/procurement/views.py @@ -184,7 +184,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'Остаток: {budget.remaining_amount}₽ ' f'({remaining_ratio*100:.1f}%)' ), - action_url='/procurement/budgets/', + action_url='/procurement', send_immediately=True, ) elif remaining_ratio <= LOW_BUDGET_THRESHOLD: @@ -198,7 +198,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'Остаток: {budget.remaining_amount}₽ ' f'({remaining_ratio*100:.1f}%)' ), - action_url='/procurement/budgets/', + action_url='/procurement', send_immediately=True, ) @@ -289,7 +289,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'ожидает вашего согласования. ' f'Сумма: {procurement_request.total_cost}₽' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -330,7 +330,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'{request.user.get_full_name()} одобрил ' f'заявку "{procurement_request.title}".' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -353,7 +353,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'Ваша заявка "{procurement_request.title}" ' f'была полностью одобрена. Можно приступать к закупке.' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -402,7 +402,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'заявку "{procurement_request.title}". ' f'Причина: {approval.comment}' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -513,7 +513,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'взята в работу пользователем ' f'{request.user.get_full_name()}.' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -530,7 +530,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'взята в работу пользователем ' f'{request.user.get_full_name()}.' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -576,7 +576,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'Заявка "{procurement_request.title}" ' f'успешно завершена.' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -659,7 +659,7 @@ class ProcurementRequestViewSet(viewsets.ModelViewSet): f'была отменена автором. ' f'Причина: {reason or "не указана"}' ), - action_url=f'/procurement/orders/{procurement_request.id}/', + action_url='/procurement', send_immediately=True, ) diff --git a/backend/calendar_app/notification_signals.py b/backend/calendar_app/notification_signals.py index 07ea6567..afec4b53 100644 --- a/backend/calendar_app/notification_signals.py +++ b/backend/calendar_app/notification_signals.py @@ -255,9 +255,6 @@ def get_event_recipients(event): def get_calendar_url(event): """ - Возвращает URL календаря в зависимости от типа события. - Для всех типов событий используем параметр event_id для прямого открытия. + Возвращает URL календаря. """ - # Возвращаем главную страницу календаря с параметром event_id - # Это позволит открыть модал события автоматически - return f'/?event_id={event.id}' + return '/calendar' diff --git a/backend/communications/notification_signals.py b/backend/communications/notification_signals.py index 74ceadcc..63d80aa7 100644 --- a/backend/communications/notification_signals.py +++ b/backend/communications/notification_signals.py @@ -146,7 +146,7 @@ def _create_message_notifications_sync(instance): title=f'Вас упомянул {author.get_full_name() or author.username}', message=truncate_message(content, 100), content_object=instance, - action_url=f'/communications/chats/{chat.id}/?message={instance.id}', + action_url='/messages', metadata={ 'chat_id': chat.id, 'chat_name': get_chat_name(chat), @@ -170,7 +170,7 @@ def _create_message_notifications_sync(instance): title=f'{author.get_full_name() or author.username} ответил на ваше сообщение', message=truncate_message(content, 100), content_object=instance, - action_url=f'/communications/chats/{chat.id}/?message={instance.id}', + action_url='/messages', metadata={ 'chat_id': chat.id, 'chat_name': get_chat_name(chat), @@ -219,7 +219,7 @@ def _create_message_notifications_sync(instance): title=title, message=truncate_message(content, 150 if is_announcement else 100), content_object=instance, - action_url=f'/communications/chats/{chat.id}/', + action_url='/messages', metadata=metadata ) @@ -260,7 +260,7 @@ def create_chat_added_notifications(sender, instance, action, pk_set, **kwargs): title=f'Вас добавили в чат', message=f'Вы были добавлены в чат "{get_chat_name(chat)}"', content_object=chat, - action_url=f'/communications/chats/{chat.id}/', + action_url='/messages', metadata={ 'chat_id': chat.id, 'chat_type': chat.type, diff --git a/backend/documents/notification_signals.py b/backend/documents/notification_signals.py index f3d09281..4b1645a0 100644 --- a/backend/documents/notification_signals.py +++ b/backend/documents/notification_signals.py @@ -358,7 +358,7 @@ def check_all_acknowledged(sender, instance, created, **kwargs): f'"{document.title}"' ), content_object=document, - action_url='/documents/', + action_url='/documents', metadata={ 'document_id': document.id, 'total_recipients': total_recipients, @@ -477,7 +477,7 @@ def create_document_ready_notification(document, recipient, send_immediately=Tru f'Требуется ознакомление.' ), content_object=document, - action_url='/documents/', + action_url='/documents', metadata={ 'document_id': document.id, 'uploaded_by_id': ( @@ -549,7 +549,7 @@ def notify_on_new_comment(sender, instance, created, **kwargs): f'комментарий к документу "{document.title}"' ), content_object=comment, - action_url=f'/documents/{document.pk}/#comment-{comment.pk}', + action_url='/documents', action_text='Посмотреть', metadata={ 'document_id': document.id, @@ -589,7 +589,7 @@ def notify_on_new_comment(sender, instance, created, **kwargs): f'к вашему документу "{document.title}"' ), content_object=comment, - action_url=f'/documents/{document.pk}/#comment-{comment.pk}', + action_url='/documents', action_text='Посмотреть', metadata={ 'document_id': document.id, @@ -646,7 +646,7 @@ def notify_on_related_document_added(sender, instance, action, pk_set, **kwargs) f'с документом "{main_document.title}"' ), content_object=main_document, - action_url=f'/documents/{main_document.pk}/', + action_url='/documents', action_text='Посмотреть', metadata={ 'document_id': main_document.id, diff --git a/backend/feed/notification_signals.py b/backend/feed/notification_signals.py index 44eaeaf6..8dc1332b 100644 --- a/backend/feed/notification_signals.py +++ b/backend/feed/notification_signals.py @@ -77,7 +77,7 @@ def create_comment_notification(sender, instance, created, **kwargs): f' прокомментировал: {truncate_text(comment.text, 80)}' ), content_object=post, - action_url=f'/post/{post.id}/', + action_url='/', metadata={ 'post_id': post.id, 'post_title': post.title, @@ -115,7 +115,7 @@ def notify_new_post(post): title=f'Новая публикация {context}', message=f'{post.title}', content_object=post, - action_url=f'/post/{post.id}/', + action_url='/', metadata={ 'post_id': post.id, 'post_type': post.type, @@ -191,7 +191,7 @@ def notify_post_reaction(post, user): f'понравилась публикация "{post.title}"' ), content_object=post, - action_url=f'/post/{post.id}/', + action_url='/', metadata={ 'post_id': post.id, 'post_title': post.title, diff --git a/backend/notifications/task_base.py b/backend/notifications/task_base.py index aebc77f8..f30ad0cc 100644 --- a/backend/notifications/task_base.py +++ b/backend/notifications/task_base.py @@ -537,7 +537,7 @@ class RequestNotificationProcessor(BaseNotificationProcessor): title=title_map.get(notification_type, f'Обновление заявки #{request_obj.id}'), message=truncate_text(request_obj.comment or '', 150), content_object=request_obj, - action_url=f'/requests/{request_obj.id}/', + action_url='/requests', metadata={ 'request_id': request_obj.id, 'request_type': request_obj.type, @@ -594,7 +594,7 @@ class DocumentNotificationProcessor(BaseNotificationProcessor): title=f'Новый документ: {document.title}', message=truncate_text(document.description or '', 150), content_object=document, - action_url=f'/documents/{document.id}/', + action_url='/documents', metadata={ 'document_id': document.id, 'document_title': document.title, diff --git a/backend/procurement/signals.py b/backend/procurement/signals.py index a9f76e97..015eafbc 100644 --- a/backend/procurement/signals.py +++ b/backend/procurement/signals.py @@ -80,7 +80,7 @@ def notify_on_request_status_change(sender, instance, created, **kwargs): f'Создана заявка "{instance.title}" ' f'на сумму {instance.total_cost}₽' ), - action_url=f'/procurement/requests/{instance.id}/', + action_url='/procurement', send_immediately=True, ) return @@ -189,7 +189,7 @@ def notify_approver(approval): f'ожидает вашего согласования. ' f'Сумма: {approval.request.total_cost}₽' ), - action_url=f'/procurement/requests/{approval.request.id}/', + action_url='/procurement', send_immediately=True, ) @@ -201,7 +201,7 @@ def notify_requestor(request, notification_type_code, title, message): notification_type_code=notification_type_code, title=title, message=message, - action_url=f'/procurement/requests/{request.id}/', + action_url='/procurement', send_immediately=True, ) diff --git a/backend/requests_app/notification_signals.py b/backend/requests_app/notification_signals.py index e0b44238..324114ff 100644 --- a/backend/requests_app/notification_signals.py +++ b/backend/requests_app/notification_signals.py @@ -269,7 +269,7 @@ def create_comment_notification(sender, instance, created, **kwargs): f'"{request_type}": {instance.text[:100]}' ), content_object=request_obj, - action_url=f"/requests/{request_obj.id}/", + action_url="/requests", metadata={ "request_id": request_obj.id, "request_type": request_obj.type, @@ -457,7 +457,7 @@ def notify_new_request(request_obj): title=title, message=message, content_object=request_obj, - action_url=f"/requests/{request_obj.id}/", + action_url="/requests", metadata={ "request_id": request_obj.id, "request_type": request_obj.type, @@ -562,7 +562,7 @@ def notify_status_change(request_obj, old_status, new_status): title=title, message=message, content_object=request_obj, - action_url=f"/requests/{request_obj.id}/", + action_url="/requests", metadata={ "request_id": request_obj.id, "request_type": request_obj.type,