chore: rebrand ClubManager -> RosterChief, add lucide icons
The clubmanager.app domain was taken, so the platform is now RosterChief (rosterchief.app). Renames the Django project package clubmanager/ -> rosterchief/ (git tracks it as a move, so history follows), every `from rosterchief.base import ...`, the settings/wsgi/asgi module paths, env vars (ROSTERCHIEF_BASE_DOMAIN / ROSTERCHIEF_RP_NAME), the MFA adapter (RosterChiefMFAAdapter), brand text, and the docs. Two things were deliberately NOT swept: - club.models.ClubManager stays: it is the Django manager *for Club*, not the brand. A blind rename would have silently broken it. - Migrations are untouched (history is not rewritten). The only reference was a cosmetic help_text, so a normal AlterField migration carries the new domain. Note the WebAuthn RP ID is the base domain, so moving to rosterchief.app cryptographically invalidates any passkey enrolled under the old one; they cannot be migrated and must be re-enrolled. Nothing is in production, so the real cost is zero. Add django-lucide (from bsiebens/lucide) for icons: the theme toggle now swaps sun/moon against the effective theme, and the control panel gets icons on its tabs, actions and stat groups. Its classifiers stop at Django 5.0, but that is stale metadata — verified rendering on Django 6 / Python 3.14. Also add formbuilder, shop and controlpanel to ruff's known-first-party list, which had drifted behind the apps that landed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
18
club/migrations/0011_alter_club_slug.py
Normal file
18
club/migrations/0011_alter_club_slug.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.6 on 2026-07-13 13:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('club', '0010_club_archived_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='club',
|
||||
name='slug',
|
||||
field=models.SlugField(blank=True, help_text='Drives subdomain / path resolution (e.g. ajax-united.rosterchief.app).', max_length=255, unique=True, verbose_name='slug'),
|
||||
),
|
||||
]
|
||||
@@ -4,8 +4,8 @@ from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from clubmanager.base import ClubScopedModel, UUIDModel, unique_slugify, validate_club_scope
|
||||
from members.models import Member
|
||||
from rosterchief.base import ClubScopedModel, UUIDModel, unique_slugify, validate_club_scope
|
||||
|
||||
|
||||
class ClubManager(models.Manager):
|
||||
@@ -24,7 +24,7 @@ class ClubManager(models.Manager):
|
||||
|
||||
class Club(UUIDModel):
|
||||
name = models.CharField(_("name"), max_length=255)
|
||||
slug = models.SlugField(_("slug"), max_length=255, unique=True, blank=True, help_text=_("Drives subdomain / path resolution (e.g. ajax-united.clubmanager.app)."))
|
||||
slug = models.SlugField(_("slug"), max_length=255, unique=True, blank=True, help_text=_("Drives subdomain / path resolution (e.g. ajax-united.rosterchief.app)."))
|
||||
|
||||
archived_at = models.DateTimeField(_("archived at"), null=True, blank=True, help_text=_("Archived clubs stop resolving on their subdomain, but their data is retained."))
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class ClubTenantMiddleware:
|
||||
reset_current_club(token)
|
||||
|
||||
def get_club(self, request) -> Club | None:
|
||||
# Imported lazily: club.models imports clubmanager.base, which imports
|
||||
# Imported lazily: club.models imports rosterchief.base, which imports
|
||||
# this module, so a top-level import would be circular.
|
||||
from .models import Club
|
||||
|
||||
@@ -78,7 +78,7 @@ class ClubTenantMiddleware:
|
||||
if not host:
|
||||
return None
|
||||
|
||||
base_domain = getattr(settings, "CLUBMANAGER_BASE_DOMAIN", "").lower().strip(".")
|
||||
base_domain = getattr(settings, "ROSTERCHIEF_BASE_DOMAIN", "").lower().strip(".")
|
||||
|
||||
if base_domain:
|
||||
# Only hosts under the configured base domain carry a tenant slug.
|
||||
|
||||
@@ -231,8 +231,8 @@ class ClubSlugTests(TestCase):
|
||||
|
||||
|
||||
@override_settings(
|
||||
CLUBMANAGER_BASE_DOMAIN="clubmanager.app",
|
||||
ALLOWED_HOSTS=[".clubmanager.app", ".example.com", ".example.org"],
|
||||
ROSTERCHIEF_BASE_DOMAIN="rosterchief.app",
|
||||
ALLOWED_HOSTS=[".rosterchief.app", ".example.com", ".example.org"],
|
||||
)
|
||||
class ClubTenantMiddlewareTests(TestCase):
|
||||
def setUp(self):
|
||||
@@ -252,29 +252,29 @@ class ClubTenantMiddlewareTests(TestCase):
|
||||
return request, response
|
||||
|
||||
def test_subdomain_resolves_to_club(self):
|
||||
request, response = self._run("ajax-united.clubmanager.app")
|
||||
request, response = self._run("ajax-united.rosterchief.app")
|
||||
|
||||
self.assertEqual(response, "response")
|
||||
self.assertEqual(request.club, self.club)
|
||||
self.assertEqual(self.captured["context_club"], self.club)
|
||||
|
||||
def test_subdomain_resolution_ignores_port(self):
|
||||
request, _ = self._run("ajax-united.clubmanager.app:8000")
|
||||
request, _ = self._run("ajax-united.rosterchief.app:8000")
|
||||
|
||||
self.assertEqual(request.club, self.club)
|
||||
|
||||
def test_unknown_subdomain_sets_none(self):
|
||||
request, _ = self._run("unknown-club.clubmanager.app")
|
||||
request, _ = self._run("unknown-club.rosterchief.app")
|
||||
|
||||
self.assertIsNone(request.club)
|
||||
|
||||
def test_bare_base_domain_has_no_club(self):
|
||||
request, _ = self._run("clubmanager.app")
|
||||
request, _ = self._run("rosterchief.app")
|
||||
|
||||
self.assertIsNone(request.club)
|
||||
|
||||
def test_www_is_treated_as_no_club(self):
|
||||
request, _ = self._run("www.clubmanager.app")
|
||||
request, _ = self._run("www.rosterchief.app")
|
||||
|
||||
self.assertIsNone(request.club)
|
||||
|
||||
@@ -284,17 +284,17 @@ class ClubTenantMiddlewareTests(TestCase):
|
||||
self.assertIsNone(request.club)
|
||||
|
||||
def test_context_var_is_reset_after_request(self):
|
||||
self._run("ajax-united.clubmanager.app")
|
||||
self._run("ajax-united.rosterchief.app")
|
||||
|
||||
self.assertIsNone(get_current_club())
|
||||
|
||||
@override_settings(CLUBMANAGER_BASE_DOMAIN="")
|
||||
@override_settings(ROSTERCHIEF_BASE_DOMAIN="")
|
||||
def test_generic_host_resolution_without_base_domain(self):
|
||||
request, _ = self._run("ajax-united.example.com")
|
||||
|
||||
self.assertEqual(request.club, self.club)
|
||||
|
||||
@override_settings(CLUBMANAGER_BASE_DOMAIN="")
|
||||
@override_settings(ROSTERCHIEF_BASE_DOMAIN="")
|
||||
def test_two_label_host_has_no_club_without_base_domain(self):
|
||||
request, _ = self._run("example.com")
|
||||
|
||||
@@ -309,7 +309,7 @@ class _FakeRequest:
|
||||
return self._host
|
||||
|
||||
|
||||
@override_settings(CLUBMANAGER_BASE_DOMAIN="clubmanager.app")
|
||||
@override_settings(ROSTERCHIEF_BASE_DOMAIN="rosterchief.app")
|
||||
class GetSubdomainTests(TestCase):
|
||||
def subdomain(self, host):
|
||||
return ClubTenantMiddleware.get_subdomain(_FakeRequest(host))
|
||||
@@ -318,10 +318,10 @@ class GetSubdomainTests(TestCase):
|
||||
self.assertIsNone(self.subdomain(""))
|
||||
|
||||
def test_trailing_dot_is_stripped(self):
|
||||
self.assertEqual(self.subdomain("ajax-united.clubmanager.app."), "ajax-united")
|
||||
self.assertEqual(self.subdomain("ajax-united.rosterchief.app."), "ajax-united")
|
||||
|
||||
def test_nested_subdomain_uses_leftmost_label(self):
|
||||
self.assertEqual(self.subdomain("a.b.clubmanager.app"), "a")
|
||||
self.assertEqual(self.subdomain("a.b.rosterchief.app"), "a")
|
||||
|
||||
|
||||
class TenantContextTests(TestCase):
|
||||
@@ -537,7 +537,7 @@ class ClubArchivingTests(TestCase):
|
||||
self.assertTrue(Season.objects.filter(club=self.club).exists())
|
||||
|
||||
|
||||
@override_settings(CLUBMANAGER_BASE_DOMAIN="clubmanager.app", ALLOWED_HOSTS=[".clubmanager.app"])
|
||||
@override_settings(ROSTERCHIEF_BASE_DOMAIN="rosterchief.app", ALLOWED_HOSTS=[".rosterchief.app"])
|
||||
class ArchivedClubTenancyTests(TestCase):
|
||||
"""An archived club's subdomain must stop resolving — that is what makes
|
||||
archiving a real deactivation rather than a cosmetic flag."""
|
||||
@@ -547,7 +547,7 @@ class ArchivedClubTenancyTests(TestCase):
|
||||
self.middleware = ClubTenantMiddleware(lambda request: "response")
|
||||
|
||||
def resolve(self):
|
||||
request = RequestFactory().get("/", HTTP_HOST="ajax-united.clubmanager.app")
|
||||
request = RequestFactory().get("/", HTTP_HOST="ajax-united.rosterchief.app")
|
||||
self.middleware(request)
|
||||
return request.club
|
||||
|
||||
|
||||
Reference in New Issue
Block a user