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:
@@ -58,6 +58,7 @@ def club_statistics(club):
|
||||
return [
|
||||
{
|
||||
"title": "Members",
|
||||
"icon": "users",
|
||||
"stats": [
|
||||
("Members", memberships.values("member").distinct().count()),
|
||||
("Active this season", memberships.filter(season=season, status=ClubMembership.StatusChoices.ACTIVE).count() if season else 0),
|
||||
@@ -67,6 +68,7 @@ def club_statistics(club):
|
||||
},
|
||||
{
|
||||
"title": "Teams & staff",
|
||||
"icon": "shield",
|
||||
"stats": [
|
||||
("Teams", Team.objects.filter(club=club).count()),
|
||||
("Players this season", TeamMembership.objects.filter(team__club=club, season=season).count() if season else 0),
|
||||
@@ -75,6 +77,7 @@ def club_statistics(club):
|
||||
},
|
||||
{
|
||||
"title": "Events",
|
||||
"icon": "calendar-days",
|
||||
"stats": [
|
||||
("Upcoming", events.filter(start__gte=now).count()),
|
||||
("This season", events.filter(season=season).count() if season else 0),
|
||||
@@ -82,6 +85,7 @@ def club_statistics(club):
|
||||
},
|
||||
{
|
||||
"title": "Shop",
|
||||
"icon": "shopping-cart",
|
||||
"stats": [
|
||||
("Orders", orders.count()),
|
||||
("Revenue", _money(orders.filter(status__in=PAID_STATUSES))),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
{% load lucide %}
|
||||
|
||||
{% block title %}
|
||||
{% block panel_title %}Control panel{% endblock panel_title %} · ClubManager
|
||||
{% block panel_title %}Control panel{% endblock panel_title %} · RosterChief
|
||||
{% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
@@ -17,8 +18,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div role="tablist" class="tabs-boxed tabs mb-6 w-fit">
|
||||
<a role="tab" href="{% url 'controlpanel:dashboard' %}" class="tab {% if nav == 'dashboard' %}tab-active{% endif %}">Dashboard</a>
|
||||
<a role="tab" href="{% url 'controlpanel:club_list' %}" class="tab {% if nav == 'clubs' %}tab-active{% endif %}">Clubs</a>
|
||||
<a role="tab" href="{% url 'controlpanel:dashboard' %}" class="tab gap-2 {% if nav == 'dashboard' %}tab-active{% endif %}">{% lucide "layout-dashboard" size=16 %} Dashboard</a>
|
||||
<a role="tab" href="{% url 'controlpanel:club_list' %}" class="tab gap-2 {% if nav == 'clubs' %}tab-active{% endif %}">{% lucide "building-2" size=16 %} Clubs</a>
|
||||
</div>
|
||||
{% block panel %}{% endblock panel %}
|
||||
{% endblock main %}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "controlpanel/base.html" %}
|
||||
{% load lucide %}
|
||||
|
||||
{% block heading %}{{ club.name }}{% endblock heading %}
|
||||
|
||||
@@ -12,16 +13,16 @@
|
||||
{% endblock subheading %}
|
||||
|
||||
{% block actions %}
|
||||
<a class="btn btn-ghost" href="{% url 'controlpanel:club_update' club.pk %}">Edit</a>
|
||||
<a class="btn btn-ghost gap-2" href="{% url 'controlpanel:club_update' club.pk %}">{% lucide "pencil" size=16 %} Edit</a>
|
||||
{% if club.is_archived %}
|
||||
<form method="post" action="{% url 'controlpanel:club_restore' club.pk %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-success" type="submit">Restore</button>
|
||||
<button class="btn btn-success gap-2" type="submit">{% lucide "archive-restore" size=16 %} Restore</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'controlpanel:club_archive' club.pk %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-warning" type="submit">Archive</button>
|
||||
<button class="btn btn-warning gap-2" type="submit">{% lucide "archive" size=16 %} Archive</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endblock actions %}
|
||||
@@ -36,7 +37,7 @@
|
||||
{% for group in groups %}
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base">{{ group.title }}</h2>
|
||||
<h2 class="card-title text-base">{% lucide group.icon size=18 %} {{ group.title }}</h2>
|
||||
<dl class="divide-y divide-base-200">
|
||||
{% for label, value in group.stats %}
|
||||
<div class="flex items-center justify-between py-2">
|
||||
@@ -53,7 +54,7 @@
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="card-title text-base">Club admins</h2>
|
||||
<a class="btn btn-primary btn-sm" href="{% url 'controlpanel:club_admin_add' club.pk %}">Add admin</a>
|
||||
<a class="btn btn-primary btn-sm gap-2" href="{% url 'controlpanel:club_admin_add' club.pk %}">{% lucide "user-plus" size=16 %} Add admin</a>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
@@ -72,7 +73,7 @@
|
||||
<td class="text-right">
|
||||
<form method="post" action="{% url 'controlpanel:club_admin_remove' club.pk role.pk %}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-ghost btn-xs text-error" type="submit">Remove</button>
|
||||
<button class="btn btn-ghost btn-xs gap-1 text-error" type="submit">{% lucide "trash-2" size=14 %} Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{% extends "controlpanel/base.html" %}
|
||||
{% load lucide %}
|
||||
|
||||
{% block heading %}{% if show_archived %}Archived clubs{% else %}Clubs{% endif %}{% endblock heading %}
|
||||
|
||||
@@ -8,7 +9,7 @@
|
||||
{% else %}
|
||||
<a class="btn btn-ghost" href="{% url 'controlpanel:club_list' %}?archived=1">Archived</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-primary" href="{% url 'controlpanel:club_create' %}">New club</a>
|
||||
<a class="btn btn-primary gap-2" href="{% url 'controlpanel:club_create' %}">{% lucide "plus" size=16 %} New club</a>
|
||||
{% endblock actions %}
|
||||
|
||||
{% block panel %}
|
||||
@@ -19,7 +20,7 @@
|
||||
value="{{ search }}"
|
||||
placeholder="Search clubs…"
|
||||
class="input input-bordered w-full max-w-xs">
|
||||
<button class="btn" type="submit">Search</button>
|
||||
<button class="btn gap-2" type="submit">{% lucide "search" size=16 %} Search</button>
|
||||
</form>
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{% extends "controlpanel/base.html" %}
|
||||
{% load lucide %}
|
||||
|
||||
{% block heading %}Platform overview{% endblock heading %}
|
||||
|
||||
{% block actions %}
|
||||
<a class="btn btn-primary" href="{% url 'controlpanel:club_create' %}">New club</a>
|
||||
<a class="btn btn-primary gap-2" href="{% url 'controlpanel:club_create' %}">{% lucide "plus" size=16 %} New club</a>
|
||||
{% endblock actions %}
|
||||
|
||||
{% block panel %}
|
||||
|
||||
@@ -56,12 +56,12 @@ class AccessTests(ControlPanelTestBase):
|
||||
|
||||
self.assertEqual(self.client.get(reverse("controlpanel:dashboard")).status_code, 200)
|
||||
|
||||
@override_settings(CLUBMANAGER_BASE_DOMAIN="clubmanager.app", ALLOWED_HOSTS=[".clubmanager.app"])
|
||||
@override_settings(ROSTERCHIEF_BASE_DOMAIN="rosterchief.app", ALLOWED_HOSTS=[".rosterchief.app"])
|
||||
def test_panel_does_not_exist_on_a_club_subdomain(self):
|
||||
# It manages *all* clubs, so it must not be reachable from inside one.
|
||||
Club.objects.create(name="Rival FC", slug="rival-fc")
|
||||
|
||||
response = self.client.get(reverse("controlpanel:dashboard"), headers={"host": "rival-fc.clubmanager.app"})
|
||||
response = self.client.get(reverse("controlpanel:dashboard"), headers={"host": "rival-fc.rosterchief.app"})
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user