Add the `controlpanel` app: a platform-wide (not club-scoped) admin panel for creating clubs, archiving/restoring them, managing club admins, and per-club statistics (members, teams & staff, events, shop). Statistics are annotated in one query so the club list cannot fan out into N+1, and are returned as stat *groups* so growing the domain means adding one entry. Two access rules, both enforced by PlatformStaffRequiredMixin: - staff only (is_staff/is_superuser); anonymous are sent to login, signed-in non-staff get a 403. Staff already need a second factor, so the panel is 2FA-protected for free. - base domain only: the panel manages *all* clubs, so it 404s if the tenant middleware resolved a club from the subdomain. Granting admin to an unknown email creates the account (unusable password — they set one via password reset) and the Member behind it, since a ClubRole hangs off a Member. A member who already holds a role is promoted in place, because there is only one role per member per club. UI is Tailwind + daisyUI. allauth ships an element system, so overriding allauth/layouts/base.html plus ~13 element partials restyles *every* auth and 2FA screen at once — login, signup, password reset, the 2FA challenge, TOTP enrolment, passkeys and recovery codes — rather than templating 20+ pages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 lines
1.1 KiB
Python
28 lines
1.1 KiB
Python
from django.contrib.auth.mixins import UserPassesTestMixin
|
|
from django.http import Http404
|
|
|
|
|
|
class PlatformStaffRequiredMixin(UserPassesTestMixin):
|
|
"""Gate for the platform control panel.
|
|
|
|
Two rules:
|
|
|
|
* **Staff only.** ``is_staff`` or ``is_superuser``. Anonymous visitors are
|
|
sent to the login page; signed-in non-staff get a 403 (Django's
|
|
AccessMixin already distinguishes those two cases). Staff must also hold a
|
|
second factor — ``RequireMFAMiddleware`` enforces that, so the panel is
|
|
2FA-protected for free.
|
|
* **Base domain only.** The panel manages *all* clubs, so it must not be
|
|
reachable from inside one. If the tenant middleware resolved a club from
|
|
the subdomain, the panel does not exist here.
|
|
"""
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if getattr(request, "club", None) is not None:
|
|
raise Http404("The control panel is not available on a club subdomain.")
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def test_func(self):
|
|
user = self.request.user
|
|
return user.is_staff or user.is_superuser
|