feat: make people group labels editable
This commit is contained in:
@@ -50,6 +50,13 @@ class SiteBrandingAdmin(admin.ModelAdmin):
|
|||||||
"description": "These headings are shared by main projects and sub-projects that have people cards.",
|
"description": "These headings are shared by main projects and sub-projects that have people cards.",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"People role headings",
|
||||||
|
{
|
||||||
|
"fields": ("lead_group_label", "organizer_group_label", "collaborator_group_label"),
|
||||||
|
"description": "Shared across About, blog articles, project articles, main projects, and sub-projects. Empty headings are hidden; people cards remain visible.",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"Brand assets",
|
"Brand assets",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.16 on 2026-10-28 20:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0007_sitebranding_project_people_eyebrow_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitebranding',
|
||||||
|
name='collaborator_group_label',
|
||||||
|
field=models.CharField(blank=True, default='Collaborators', help_text='Heading for Collaborators. Leave empty to hide the heading while keeping their cards.', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitebranding',
|
||||||
|
name='lead_group_label',
|
||||||
|
field=models.CharField(blank=True, default='Lead / Leader', help_text='Heading for Lead and Leader people. Leave empty to hide the heading while keeping their cards.', max_length=100),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitebranding',
|
||||||
|
name='organizer_group_label',
|
||||||
|
field=models.CharField(blank=True, default='Organizers', help_text='Heading for Organizers. Leave empty to hide the heading while keeping their cards.', max_length=100),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -49,6 +49,18 @@ class SiteBranding(models.Model):
|
|||||||
max_length=200, default="Organizers & collaborators",
|
max_length=200, default="Organizers & collaborators",
|
||||||
help_text="Heading above the people cards on main projects and sub-projects.",
|
help_text="Heading above the people cards on main projects and sub-projects.",
|
||||||
)
|
)
|
||||||
|
lead_group_label = models.CharField(
|
||||||
|
max_length=100, blank=True, default="Lead / Leader",
|
||||||
|
help_text="Heading for Lead and Leader people. Leave empty to hide the heading while keeping their cards.",
|
||||||
|
)
|
||||||
|
organizer_group_label = models.CharField(
|
||||||
|
max_length=100, blank=True, default="Organizers",
|
||||||
|
help_text="Heading for Organizers. Leave empty to hide the heading while keeping their cards.",
|
||||||
|
)
|
||||||
|
collaborator_group_label = models.CharField(
|
||||||
|
max_length=100, blank=True, default="Collaborators",
|
||||||
|
help_text="Heading for Collaborators. Leave empty to hide the heading while keeping their cards.",
|
||||||
|
)
|
||||||
|
|
||||||
icon = models.ImageField(
|
icon = models.ImageField(
|
||||||
upload_to="branding/",
|
upload_to="branding/",
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,7 @@ from collections import OrderedDict
|
|||||||
|
|
||||||
|
|
||||||
ROLE_PRIORITY = {"lead": 0, "leader": 0, "organizer": 1, "collaborator": 2}
|
ROLE_PRIORITY = {"lead": 0, "leader": 0, "organizer": 1, "collaborator": 2}
|
||||||
ROLE_TITLES = {0: "Lead / Leader", 1: "Organizers", 2: "Collaborators"}
|
ROLE_LABEL_FIELDS = {0: "lead_group_label", 1: "organizer_group_label", 2: "collaborator_group_label"}
|
||||||
|
|
||||||
|
|
||||||
def person_sort_key(person):
|
def person_sort_key(person):
|
||||||
@@ -10,7 +10,7 @@ def person_sort_key(person):
|
|||||||
return (ROLE_PRIORITY.get(role, 3), role if role not in ROLE_PRIORITY else "", person.order, person.pk or 0)
|
return (ROLE_PRIORITY.get(role, 3), role if role not in ROLE_PRIORITY else "", person.order, person.pk or 0)
|
||||||
|
|
||||||
|
|
||||||
def group_people(people):
|
def group_people(people, branding):
|
||||||
groups = OrderedDict()
|
groups = OrderedDict()
|
||||||
for person in sorted(people, key=person_sort_key):
|
for person in sorted(people, key=person_sort_key):
|
||||||
role = (person.role or "").strip()
|
role = (person.role or "").strip()
|
||||||
@@ -18,7 +18,7 @@ def group_people(people):
|
|||||||
key = priority if priority < 3 else role.casefold()
|
key = priority if priority < 3 else role.casefold()
|
||||||
if key not in groups:
|
if key not in groups:
|
||||||
groups[key] = {
|
groups[key] = {
|
||||||
"title": ROLE_TITLES[priority] if priority < 3 else role or "Other contributors",
|
"title": getattr(branding, ROLE_LABEL_FIELDS[priority], "") if priority < 3 else role,
|
||||||
"people": [],
|
"people": [],
|
||||||
}
|
}
|
||||||
groups[key]["people"].append(person)
|
groups[key]["people"].append(person)
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ register = template.Library()
|
|||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def people_rows(people):
|
def people_rows(people, branding):
|
||||||
return group_people(people)
|
return group_people(people, branding)
|
||||||
|
|||||||
@@ -60,6 +60,16 @@ class AboutViewTest(TestCase):
|
|||||||
response = self.client.get(reverse("pages:about"))
|
response = self.client.get(reverse("pages:about"))
|
||||||
self.assertNotContains(response, "Nobody yet")
|
self.assertNotContains(response, "Nobody yet")
|
||||||
|
|
||||||
|
def test_about_role_heading_uses_shared_admin_setting(self):
|
||||||
|
section = AboutSection.objects.create(section_type=AboutSection.TYPE_PEOPLE, title="Our people")
|
||||||
|
AboutSectionItem.objects.create(section=section, title="Team lead", role="Leader")
|
||||||
|
branding = SiteBranding.load()
|
||||||
|
branding.lead_group_label = "Leadership"
|
||||||
|
branding.save(update_fields=["lead_group_label"])
|
||||||
|
response = self.client.get(reverse("pages:about"))
|
||||||
|
self.assertContains(response, "Leadership")
|
||||||
|
self.assertNotContains(response, "Lead / Leader")
|
||||||
|
|
||||||
|
|
||||||
class BlogViewTest(TestCase):
|
class BlogViewTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from apps.core.models import SiteBranding
|
||||||
from apps.products.models import (
|
from apps.products.models import (
|
||||||
Article, ArticlePerson, ArticleSection, MainProduct, MainProductPerson,
|
Article, ArticlePerson, ArticleSection, MainProduct, MainProductPerson,
|
||||||
SubProduct, SubProductPerson,
|
SubProduct, SubProductPerson,
|
||||||
@@ -59,6 +60,22 @@ class ProductOverviewViewTest(ProductViewsSetup):
|
|||||||
|
|
||||||
|
|
||||||
class MainProductDetailViewTest(ProductViewsSetup):
|
class MainProductDetailViewTest(ProductViewsSetup):
|
||||||
|
def test_role_headings_follow_site_settings_and_can_be_hidden(self):
|
||||||
|
MainProductPerson.objects.create(main_product=self.main_product, name="Project lead", role="Lead")
|
||||||
|
MainProductPerson.objects.create(main_product=self.main_product, name="Project organizer", role="Organizer")
|
||||||
|
branding = SiteBranding.load()
|
||||||
|
branding.lead_group_label = "Leadership team"
|
||||||
|
branding.organizer_group_label = ""
|
||||||
|
branding.save(update_fields=["lead_group_label", "organizer_group_label"])
|
||||||
|
|
||||||
|
response = self.client.get(self.main_product.get_absolute_url())
|
||||||
|
self.assertContains(response, "Leadership team")
|
||||||
|
self.assertNotContains(response, "Lead / Leader")
|
||||||
|
self.assertNotContains(response, ">Organizers</h3>")
|
||||||
|
self.assertContains(response, "Project lead")
|
||||||
|
self.assertContains(response, "Project organizer")
|
||||||
|
self.assertNotContains(response, ">Collaborators</h3>")
|
||||||
|
|
||||||
def test_project_and_article_people_are_independent_and_ordered(self):
|
def test_project_and_article_people_are_independent_and_ordered(self):
|
||||||
MainProductPerson.objects.create(main_product=self.main_product, name="Main collaborator", role="Collaborator", order=1)
|
MainProductPerson.objects.create(main_product=self.main_product, name="Main collaborator", role="Collaborator", order=1)
|
||||||
MainProductPerson.objects.create(main_product=self.main_product, name="Main organizer", role="Organizer", order=9)
|
MainProductPerson.objects.create(main_product=self.main_product, name="Main organizer", role="Organizer", order=9)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% load people %}
|
{% load people %}
|
||||||
{% for row in people|people_rows %}
|
{% for row in people|people_rows:site_branding %}
|
||||||
<div class="people-role-row">
|
<div class="people-role-row">
|
||||||
{% if embedded %}<h5 class="people-role-heading">{{ row.title }}</h5>{% else %}<h3 class="people-role-heading">{{ row.title }}</h3>{% endif %}
|
{% if row.title %}{% if embedded %}<h5 class="people-role-heading">{{ row.title }}</h5>{% else %}<h3 class="people-role-heading">{{ row.title }}</h3>{% endif %}{% endif %}
|
||||||
<div class="blog-partners-grid">
|
<div class="blog-partners-grid">
|
||||||
{% for person in row.people %}
|
{% for person in row.people %}
|
||||||
{% if about %}
|
{% if about %}
|
||||||
|
|||||||
Reference in New Issue
Block a user