Checkpoint: management app redesign, onboarding/signup workflow, and events calendar backend

Large uncommitted body of work accumulated across sessions on this branch --
committing as a checkpoint so it's tracked and future worktree-isolated agents
see the real codebase instead of a stale ancestor commit. Covers the
management app's dedicated Tailwind theme and templates, the club onboarding
requirement/signup workflow (club/services/onboarding.py, requirement/status
models, sign-up dashboard), fee/status auto-activation decoupling, referee
management, and the new events calendar grid service layer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-19 23:34:43 +02:00
parent bff685966d
commit adf1120358
157 changed files with 20342 additions and 4008 deletions

View File

@@ -4,7 +4,7 @@ from waffle import flag_is_active
from members.models import Group
from .services.access import can_add_news, can_edit_news, can_publish_news, groups_manageable_by, has_management_access, is_club_admin, is_coach_manager, teams_managed_by
from .services.access import can_add_news, can_edit_news, can_manage_members, can_publish_news, groups_manageable_by, has_management_access, is_club_admin, is_coach_manager, teams_managed_by
class ClubStaffRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
@@ -25,6 +25,12 @@ class ClubStaffRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
def dispatch(self, request, *args, **kwargs):
if getattr(request, "club", None) is None:
raise Http404("The management app is not available on the base domain.")
# Read by club/context_processors.py's branding() -- allauth's password-change/MFA/
# logout screens live under /accounts/, not /manage/, so a path check alone can't
# tell they were reached from the management app's own user menu. This sticks for
# the rest of the session (nothing clears it back to False on a public-site visit),
# which is the right default for the common case of one person, one role.
request.session["management_context"] = True
return super().dispatch(request, *args, **kwargs)
def test_func(self):
@@ -32,13 +38,28 @@ class ClubStaffRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
class ClubAdminRequiredMixin(ClubStaffRequiredMixin):
"""ADMIN role only — club-wide settings that aren't scoped to a single team:
seasons, positions, roles, shop configuration."""
"""ADMIN role only (a platform superuser always passes too, see
is_club_admin) — genuinely admin-only ground: Finance/Shop, Club identity,
Sponsors, seasons, and granting/revoking ClubRole itself. Everything a
MEMBER_ADMIN may also touch uses MemberAdminRequiredMixin below instead."""
def test_func(self):
return is_club_admin(self.request.user, self.request.club)
class MemberAdminRequiredMixin(ClubStaffRequiredMixin):
"""ADMIN, a platform superuser, or MEMBER_ADMIN specifically -- full read/write
on people: members, families, groups, parent claims, member import, teams
(roster/staff/CRUD), referee levels, referee management, and onboarding
requirements. Deliberately does NOT cover Finance/Shop, Club identity,
Sponsors, or role-granting (role_list/role_create/role_revoke stay
ClubAdminRequiredMixin) -- a MEMBER_ADMIN must never be able to grant
themselves, or anyone else, real ADMIN."""
def test_func(self):
return can_manage_members(self.request.user, self.request.club)
class FeatureRequiredMixin(ClubAdminRequiredMixin):
"""Gate for a whole management section (shop, forms, ...) this club doesn't
have at all unless its waffle Flag (see the ``features`` app, set per-club