From 5954ec1061e059b34cacd51d6325dea5e8b1e448 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 19:49:18 -0500 Subject: [PATCH] Members page: show a live total member count Count every membership account (free and paid together) on each page load and show it under the headline; no per-tier numbers. Hidden when there are no members. Also correct the reference tiers.eom.json and service-groups.eom.json to the real LDAP DNs (ou=Memberships; ou=,ou=Services), which now match the ansible-role-www defaults. Co-Authored-By: Claude Opus 5.5 --- htdocs/static/membership.css | 2 + membership/members/tests/test_membership.py | 9 ++++ membership/members/views.py | 4 +- membership/service-groups.eom.json | 60 ++++++++++----------- membership/templates/members.html | 1 + membership/tiers.eom.json | 10 ++-- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/htdocs/static/membership.css b/htdocs/static/membership.css index fd7f43a..9f50a8d 100755 --- a/htdocs/static/membership.css +++ b/htdocs/static/membership.css @@ -5,6 +5,8 @@ .member-main { padding-bottom: 4rem; } .member-hero { padding-block: clamp(3rem, 8vw, 6rem); } .member-hero h1 { font-size: clamp(2.8rem, 6vw, 5rem); max-width: 900px; } +.member-count { color: var(--muted); font-size: 1.1rem; } +.member-count strong { color: var(--accent-strong); font-size: 1.6rem; font-variant-numeric: tabular-nums; } .member-intro { max-width: 700px; color: var(--muted); font-size: 1.2rem; } .member-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; } .member-actions form { margin: 0; } diff --git a/membership/members/tests/test_membership.py b/membership/members/tests/test_membership.py index 9e0d80f..73d4ae6 100755 --- a/membership/members/tests/test_membership.py +++ b/membership/members/tests/test_membership.py @@ -51,6 +51,15 @@ class RegistrationTests(TestCase): self.assertEqual(response.headers["Cache-Control"], "no-store") self.assertContains(self.client.get("/members/register/"), "Send verification email") + def test_home_shows_live_total_member_count(self): + self.assertNotContains(self.client.get("/members/"), "member-count") + Account.objects.create(uid="alice", email="alice@example.org") + self.assertContains(self.client.get("/members/"), "1 member and counting", html=False) + Account.objects.create(uid="bob", email="bob@example.org", membership_level=3) + response = self.client.get("/members/") + # One total across free and paid levels; no per-tier numbers. + self.assertContains(response, "2 members and counting", html=False) + def start_signup(self): self.client.post("/members/register/", {"uid": "alice", "email": "ALICE@example.org"}) match = re.search(r"/verify/([^/]+)/", str(mail.outbox[-1].body)) diff --git a/membership/members/views.py b/membership/members/views.py index dee05f6..af54992 100755 --- a/membership/members/views.py +++ b/membership/members/views.py @@ -79,7 +79,9 @@ def home(request): # Presentation copies only: never mutate authorization/payment settings. tiers = settings.MEMBERSHIP_TIERS services = service_access_catalog(tiers, settings.MEMBERSHIP_SERVICE_GROUPS) - return page(request, account=account, tiers=tiers, services=services, subscriptions=subscriptions, crypto_invoices=invoices, shkeeper_enabled=settings.SHKEEPER_ENABLED, now=timezone.now()) + # Public total across every level (free and paid); never a per-tier breakdown. + member_count = Account.objects.count() + return page(request, account=account, tiers=tiers, services=services, subscriptions=subscriptions, crypto_invoices=invoices, shkeeper_enabled=settings.SHKEEPER_ENABLED, now=timezone.now(), member_count=member_count) @require_http_methods(["GET", "POST"]) diff --git a/membership/service-groups.eom.json b/membership/service-groups.eom.json index b2342d4..3f0593c 100755 --- a/membership/service-groups.eom.json +++ b/membership/service-groups.eom.json @@ -1,44 +1,44 @@ { "0": [ - "cn=Dex Users,ou=Services,dc=eom,dc=dev", - "cn=Discourse Users,ou=Services,dc=eom,dc=dev", - "cn=Gitea Users,ou=Services,dc=eom,dc=dev", - "cn=Matrix Users,ou=Services,dc=eom,dc=dev" + "cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev", + "cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev", + "cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev", + "cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev" ], "1": [ - "cn=Dex Users,ou=Services,dc=eom,dc=dev", - "cn=Discourse Users,ou=Services,dc=eom,dc=dev", - "cn=Gitea Users,ou=Services,dc=eom,dc=dev", - "cn=Matrix Users,ou=Services,dc=eom,dc=dev" + "cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev", + "cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev", + "cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev", + "cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev" ], "2": [ - "cn=Dex Users,ou=Services,dc=eom,dc=dev", - "cn=Discourse Users,ou=Services,dc=eom,dc=dev", - "cn=Gitea Users,ou=Services,dc=eom,dc=dev", - "cn=Matrix Users,ou=Services,dc=eom,dc=dev" + "cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev", + "cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev", + "cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev", + "cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev" ], "3": [ - "cn=Dex Users,ou=Services,dc=eom,dc=dev", - "cn=Discourse Users,ou=Services,dc=eom,dc=dev", - "cn=Gitea Users,ou=Services,dc=eom,dc=dev", - "cn=Matrix Users,ou=Services,dc=eom,dc=dev", - "cn=Mastodon Users,ou=Services,dc=eom,dc=dev", - "cn=PeerTube Users,ou=Services,dc=eom,dc=dev" + "cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev", + "cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev", + "cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev", + "cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev", + "cn=Mastodon Users,ou=Mastodon,ou=Services,dc=eom,dc=dev", + "cn=PeerTube Users,ou=PeerTube,ou=Services,dc=eom,dc=dev" ], "4": [ - "cn=Dex Users,ou=Services,dc=eom,dc=dev", - "cn=Discourse Users,ou=Services,dc=eom,dc=dev", - "cn=Gitea Users,ou=Services,dc=eom,dc=dev", - "cn=Matrix Users,ou=Services,dc=eom,dc=dev", - "cn=Mastodon Users,ou=Services,dc=eom,dc=dev", - "cn=PeerTube Users,ou=Services,dc=eom,dc=dev" + "cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev", + "cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev", + "cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev", + "cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev", + "cn=Mastodon Users,ou=Mastodon,ou=Services,dc=eom,dc=dev", + "cn=PeerTube Users,ou=PeerTube,ou=Services,dc=eom,dc=dev" ], "5": [ - "cn=Dex Users,ou=Services,dc=eom,dc=dev", - "cn=Discourse Users,ou=Services,dc=eom,dc=dev", - "cn=Gitea Users,ou=Services,dc=eom,dc=dev", - "cn=Matrix Users,ou=Services,dc=eom,dc=dev", - "cn=Mastodon Users,ou=Services,dc=eom,dc=dev", - "cn=PeerTube Users,ou=Services,dc=eom,dc=dev" + "cn=Dex Users,ou=Dex,ou=Services,dc=eom,dc=dev", + "cn=Discourse Users,ou=Discourse,ou=Services,dc=eom,dc=dev", + "cn=Gitea Users,ou=Gitea,ou=Services,dc=eom,dc=dev", + "cn=Matrix Users,ou=Matrix,ou=Services,dc=eom,dc=dev", + "cn=Mastodon Users,ou=Mastodon,ou=Services,dc=eom,dc=dev", + "cn=PeerTube Users,ou=PeerTube,ou=Services,dc=eom,dc=dev" ] } diff --git a/membership/templates/members.html b/membership/templates/members.html index 71a7ebe..38e3200 100755 --- a/membership/templates/members.html +++ b/membership/templates/members.html @@ -43,6 +43,7 @@

Music, films, projects & shared worlds

Support the work.
Get closer to what’s next.

+ {% if member_count %}

{{ member_count }} member{{ member_count|pluralize }} and counting

{% endif %}

Membership supports my music, films, writing, open-source projects, and game servers. Join the community for free, or choose a paid tier to support the work and receive the benefits listed below.

{% if account %}