Rework Teams/Positions access control and add full Locations/Opponents CRUD

Team managers/coaches now only see their own teams, can't create teams,
and can view (but not edit) positions -- admins keep full rights.
Locations and Opponents move from read-only stubs to full CRUD, gated
to admins and management-position staff, with a country dropdown
(django-countries) instead of free text. Also: the team list shows
player/staff counts, and deleting a news item's main photo promotes
another one instead of leaving the item without one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R1gj3J1QPfP38XWpnpbFpy
This commit is contained in:
2026-08-04 12:27:24 +02:00
parent 9a4da9b136
commit e6850232f0
23 changed files with 813 additions and 25 deletions

View File

@@ -7,7 +7,7 @@ Same reasoning for ``news_permissions`` below, gating just the "New news item"
action rather than the whole section (``NewsAuthorRequiredMixin``/``can_add_news``).
"""
from club.services.access import can_add_news, has_management_access, is_club_admin
from club.services.access import can_add_news, has_management_access, is_club_admin, is_coach_manager
#: Every management URL name, mapped to the nav item it should light up --
#: management/templates/management/_nav_items.html compares against this.
@@ -67,7 +67,13 @@ _NAV_SECTIONS = {
"event_list": "event_list",
"event_series_list": "event_series_list",
"location_list": "location_list",
"location_create": "location_list",
"location_update": "location_list",
"location_delete": "location_list",
"opponent_list": "opponent_list",
"opponent_create": "opponent_list",
"opponent_update": "opponent_list",
"opponent_delete": "opponent_list",
"product_list": "product_list",
"order_list": "order_list",
"discount_list": "discount_list",
@@ -96,6 +102,17 @@ def is_admin(request):
return {"is_club_admin": is_club_admin(request.user, club)}
def management_position(request):
"""Whether the signed-in user holds a management position (or is ADMIN) --
gates the nav's Locations/Opponents links, which ``ManagementPositionRequiredMixin``
restricts to exactly this group (unlike most staff-visible sections)."""
club = getattr(request, "club", None)
if club is None or not request.user.is_authenticated:
return {"has_management_position": False}
return {"has_management_position": is_club_admin(request.user, club) or is_coach_manager(request.user, club)}
def management_link(request):
"""Whether to show a "Management" link in the global navbar (templates/_base.html),
next to the Django admin one -- only on a club subdomain, and only for someone with