Add a placeholder Evaluations nav entry so the design doesn't get forgotten

Under Settings, next to Referee levels/Forms -- but deliberately not behind
a waffle Flag like the shop/forms stubs, since a flag-gated link disappears
from the nav entirely until someone remembers to turn it on, which defeats
the point of a standing reminder. Visible to MEMBER_ADMIN/ADMIN
unconditionally, links to a bare "coming soon" page (reuses
_generic_list.html with an empty object_list -- there's no model yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-22 18:44:52 +02:00
parent 618d15a3e0
commit 3d0242749d
5 changed files with 62 additions and 0 deletions

View File

@@ -126,6 +126,7 @@ _NAV_SECTIONS = {
"invoice_list": "invoice_list",
"form_list": "form_list",
"submission_list": "form_list",
"evaluations": "evaluations",
"club_settings": "club_settings",
"onboarding_requirement_list": "onboarding_requirement_list",
"onboarding_requirement_create": "onboarding_requirement_list",
@@ -171,6 +172,7 @@ _TOP_SECTION = {
"discount_list": "finance",
"invoice_list": "finance",
"form_list": "settings",
"evaluations": "settings",
"club_settings": "settings",
"onboarding_requirement_list": "settings",
"role_list": "settings",

View File

@@ -100,6 +100,12 @@
<a class="nav-subitem {% if nav == 'position_list' %}active{% endif %}" href="{% url 'management:position_list' %}">{% trans "Positions" %}</a>
{% endif %}
<a class="nav-subitem {% if nav == 'referee_level_list' %}active{% endif %}" href="{% url 'management:referee_level_list' %}">{% trans "Referee levels" %}</a>
{% comment %}
Deliberately not behind a waffle flag like Forms above -- this is a standing
"not built yet" reminder, so it stays visible rather than disappearing until
someone remembers to flip a flag on. See management.views.EvaluationsComingSoonView.
{% endcomment %}
<a class="nav-subitem flex items-center gap-1.5 {% if nav == 'evaluations' %}active{% endif %}" href="{% url 'management:evaluations' %}">{% trans "Evaluations" %} <span class="badge badge-neutral badge-xs">{% trans "Soon" %}</span></a>
{% if is_club_admin %}
{% if forms_enabled %}
<a class="nav-subitem {% if nav == 'form_list' %}active{% endif %}" href="{% url 'management:form_list' %}">{% trans "Forms" %}</a>

View File

@@ -6716,6 +6716,42 @@ class FeatureGatedSectionsTests(ManagementTestBase):
self.assertNotContains(response, "Forms")
class EvaluationsComingSoonViewTests(ManagementTestBase):
"""Placeholder nav entry/page for player evaluations (design: ARCHITECTURE.md
§5.8) -- see management.views.EvaluationsComingSoonView."""
def make_plain_staff(self, email="physio-eval@example.com"):
staff_user = User.objects.create_user(email=email, password="pw-secret-123")
staff_member = Member.objects.create(user=staff_user, first_name="Pat", last_name="Physio")
ClubMembership.objects.create(club=self.club, member=staff_member, season=self.season, status=ClubMembership.StatusChoices.ACTIVE)
team = Team.objects.create(club=self.club, name="Physio Team", short_name="PHY")
position = Position.objects.create(club=self.club, name="Physio", short_name="PH", staff_position=True, management_position=False)
StaffAssignment.objects.create(team=team, member=staff_member, season=self.season, position=position)
return staff_user
def test_member_admin_can_reach_it(self):
self.client.force_login(self.admin_user)
response = self.club_get("evaluations")
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Player evaluations")
def test_plain_staff_gets_403(self):
self.client.force_login(self.make_plain_staff())
response = self.club_get("evaluations")
self.assertEqual(response.status_code, 403)
def test_nav_shows_the_placeholder_unconditionally_no_flag_needed(self):
self.client.force_login(self.admin_user)
response = self.club_get("club_settings")
self.assertContains(response, "Evaluations")
RBIHF_SAMPLE_HTML = """<html><body>
<div class="block"><div class="block-header"><h2>Sportoase Antwerp Phantoms</h2></div></div>
<div class="block"><div class="block-header"><h2 id="games-upcoming">Upcoming games</h2></div>

View File

@@ -135,6 +135,8 @@ urlpatterns = [
# Forms
path("forms/", views.FormListView.as_view(), name="form_list"),
path("forms/<uuid:pk>/submissions/", views.SubmissionListView.as_view(), name="submission_list"),
# Evaluations (placeholder -- see ARCHITECTURE.md §5.8, nothing built yet)
path("evaluations/", views.EvaluationsComingSoonView.as_view(), name="evaluations"),
# Settings (admin only)
path("settings/", views.ClubSettingsView.as_view(), name="club_settings"),
path("settings/email-previews/<str:key>/render/", views.EmailPreviewRenderView.as_view(), name="email_preview_render"),

View File

@@ -3405,6 +3405,22 @@ class SubmissionListView(FeatureRequiredMixin, StubListMixin, ListView):
return Submission.objects.filter(form__club=self.request.club, form_id=self.kwargs["pk"])
class EvaluationsComingSoonView(MemberAdminRequiredMixin, TemplateView):
"""Placeholder nav entry for player evaluations (design: ARCHITECTURE.md
§5.8) -- nothing else is built yet. Deliberately *not* a FeatureRequiredMixin/
waffle-flagged stub like the shop/forms sections above: those hide their nav
item until a platform operator turns the flag on for a club, which is exactly
wrong for a standing "don't forget to build this" reminder -- this stays
visible to every MEMBER_ADMIN/ADMIN on every club unconditionally. Reuses
_generic_list.html (StubListMixin's own template) with an empty object_list
rather than a real queryset, since there's no model behind this yet at all."""
template_name = "management/_generic_list.html"
def get_context_data(self, **kwargs):
return super().get_context_data(page_title=_("Player evaluations"), object_list=[], **kwargs)
class ClubSettingsView(ClubAdminRequiredMixin, UpdateView):
"""A club's own self-service identity/branding editor -- the "Club identity"
settings sub-item. Singleton by construction: always edits request.club, never