From 0ecdeac354c182e8b0df0e8c968167b2f111637b Mon Sep 17 00:00:00 2001 From: Bernard Siebens Date: Fri, 21 Aug 2026 09:52:59 +0200 Subject: [PATCH] Add a news review workflow and a staff notification area News gains a PENDING_REVIEW status between draft and published. A non-editor author (can_add_news but not can_publish_news -- a coach_manager, not an ADMIN/EDITOR) gets a "Send for review" button instead of Publish; an editor/admin always sees Publish directly, no review step. Submitting notifies every ADMIN/EDITOR in-app only (see notify_members' new send_email=False) -- a review queue that emailed on every submission would get noisy fast. The notification area itself: a topbar bell (badge + dropdown, same
/ convention as the sidebar's user-menu, generalised to a shared .dismissable-details close handler) visible on every page, plus a fuller "Notifications" card on the dashboard, both fed by a new notification_bell context processor. "Mark all read" clears the signed-in staff member's own unread notifications for this club. This is the reusable notification system's first consumer beyond news publishing itself -- validates that notify_members()/Notification generalise the way they were meant to. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9 --- management/context_processors.py | 27 ++++ .../templates/management/_news_preview.html | 14 +- management/templates/management/base.html | 50 ++++++- management/templates/management/home.html | 29 ++++ .../templates/management/news_list.html | 6 +- management/tests.py | 127 ++++++++++++++++++ management/urls.py | 2 + management/views.py | 46 ++++++- news/migrations/0005_alter_news_status.py | 18 +++ news/models.py | 12 +- news/services.py | 18 +++ news/tests.py | 54 +++++++- notifications/services.py | 26 ++-- rosterchief/settings.py | 1 + static/css/app.css | 15 +++ static/css/management.css | 2 +- 16 files changed, 421 insertions(+), 26 deletions(-) create mode 100644 news/migrations/0005_alter_news_status.py diff --git a/management/context_processors.py b/management/context_processors.py index bed78d4..05d4dd0 100644 --- a/management/context_processors.py +++ b/management/context_processors.py @@ -298,3 +298,30 @@ def sidebar_counters(request): "pending_parent_claims_count": ParentClaim.objects.filter(club=club, status=ParentClaim.Status.PENDING).count(), "games_missing_referees_count": games_missing_referees_count(club, limit=int(RefereeManagementDashboardView.DEFAULT_RANGE)), } + + +def notification_bell(request): + """The topbar bell's badge count and dropdown contents -- every signed-in + staff member can have notifications (not just admins, unlike + sidebar_counters' admin-only queues above), since notifications.Notification + is keyed to whichever Member they are, not to a role. + + None (not 0) when there's no club/signed-in user/Member row to key off -- + same "hidden means not applicable, 0 means an empty inbox" distinction + sidebar_counters draws.""" + club = getattr(request, "club", None) + if club is None or not request.user.is_authenticated: + return {"unread_notification_count": None, "recent_notifications": None} + + from members.models import Member + from notifications.models import Notification + + member = Member.objects.filter(user=request.user).first() + if member is None: + return {"unread_notification_count": None, "recent_notifications": None} + + notifications = Notification.objects.filter(club=club, member=member).order_by("-created")[:8] + return { + "unread_notification_count": Notification.objects.filter(club=club, member=member, read_at__isnull=True).count(), + "recent_notifications": notifications, + } diff --git a/management/templates/management/_news_preview.html b/management/templates/management/_news_preview.html index f98982a..75cf817 100644 --- a/management/templates/management/_news_preview.html +++ b/management/templates/management/_news_preview.html @@ -16,6 +16,8 @@
{% if news_item.status == "draft" %} {% trans "Draft" %} + {% elif news_item.status == "pending_review" %} + {% trans "Pending review" %} {% elif news_item.is_scheduled %} {% blocktrans with date=news_item.published_at %}Scheduled for {{ date }}{% endblocktrans %} {% else %} @@ -29,11 +31,17 @@ {% endif %} {% if can_publish %} - {% if news_item.status == "draft" %} - - {% else %} + {% if news_item.status == "published" %} + {% else %} + {% endif %} + {% elif can_edit and news_item.status == "draft" %} + {# Whoever can edit but not publish (a coach_manager, not an editor/admin -- see club.services.access.can_add_news vs can_publish_news) hands it off instead. #} +
+ {% csrf_token %} + +
{% endif %}
diff --git a/management/templates/management/base.html b/management/templates/management/base.html index 7291f32..66aab67 100644 --- a/management/templates/management/base.html +++ b/management/templates/management/base.html @@ -79,7 +79,7 @@ Opens upward (bottom-full): the trigger sits at the very bottom of the sidebar, so a menu opening down would run off the viewport. {% endcomment %} -
+
{% if user.member %}{{ user.member.first_name|slice:":1" }}{{ user.member.last_name|slice:":1" }}{% else %}?{% endif %} @@ -103,6 +103,40 @@ {% block heading %}{% block topbar_title %}Management{% endblock topbar_title %}{% endblock heading %} {% block topbar_context %}{% endblock topbar_context %}
+ {% if unread_notification_count is not None %} + {# Same
/ convention as the sidebar's own user-menu below -- no JS needed to open it, only the shared outside-click/Escape listener to close it. #} +
+ + {% lucide "bell" size=16 %} + {% if unread_notification_count %} + {{ unread_notification_count }} + {% endif %} + +
+
+ {% trans "Notifications" %} + {% if unread_notification_count %} +
+ {% csrf_token %} + + +
+ {% endif %} +
+
+ {% for notification in recent_notifications %} +
+ {{ notification.title }} + {{ notification.body|truncatechars:100 }} + {{ notification.created|timesince }} {% trans "ago" %} +
+ {% empty %} +

{% trans "Nothing yet." %}

+ {% endfor %} +
+
+
+ {% endif %}
{% block actions %}{% endblock actions %}
@@ -140,13 +174,19 @@