Skip to content

Commit 76e0dd1

Browse files
authored
Make election styling configurable by adding nomination kinds (#3030)
1 parent c502035 commit 76e0dd1

13 files changed

Lines changed: 208 additions & 1 deletion

apps/nominations/admin.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,23 @@
33
from django.contrib import admin
44
from django.db.models.functions import Lower
55

6-
from apps.nominations.models import Election, Nomination, Nominee
6+
from apps.nominations.models import Election, ElectionKind, Nomination, Nominee
7+
8+
9+
@admin.register(ElectionKind)
10+
class ElectionKindAdmin(admin.ModelAdmin):
11+
"""Admin interface for managing election kinds and their accent colors."""
12+
13+
list_display = ("name", "accent_color", "slug")
14+
readonly_fields = ("slug",)
715

816

917
@admin.register(Election)
1018
class ElectionAdmin(admin.ModelAdmin):
1119
"""Admin interface for managing elections."""
1220

21+
list_display = ("name", "kind", "date", "slug")
22+
list_filter = ("kind",)
1323
readonly_fields = ("slug",)
1424

1525

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Generated by Django 5.2.13 on 2026-06-24 15:33
2+
3+
import colorfield.fields
4+
import django.db.models.deletion
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("nominations", "0002_auto_20190514_1435"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="ElectionKind",
16+
fields=[
17+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
18+
("name", models.CharField(max_length=100, unique=True)),
19+
("slug", models.SlugField(blank=True, max_length=120, null=True, unique=True)),
20+
(
21+
"accent_color",
22+
colorfield.fields.ColorField(
23+
default="#0073b7",
24+
help_text="Accent color used to theme this kind's pages.",
25+
image_field=None,
26+
max_length=25,
27+
samples=None,
28+
),
29+
),
30+
],
31+
options={
32+
"ordering": ["name"],
33+
},
34+
),
35+
migrations.AddField(
36+
model_name="election",
37+
name="kind",
38+
field=models.ForeignKey(
39+
blank=True,
40+
null=True,
41+
on_delete=django.db.models.deletion.SET_NULL,
42+
related_name="elections",
43+
to="nominations.electionkind",
44+
),
45+
),
46+
]

apps/nominations/models.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import datetime
44

5+
from colorfield.fields import ColorField
56
from django.db import models
67
from django.db.models.signals import post_save
78
from django.dispatch import receiver
@@ -13,12 +14,48 @@
1314
from apps.users.models import User
1415
from fastly.utils import purge_url
1516

17+
DEFAULT_ACCENT_COLOR = "#0073b7"
18+
19+
20+
class ElectionKind(models.Model):
21+
"""An admin-managed category of election (Board, Packaging Council, ...).
22+
23+
Each kind carries an accent color used to visually distinguish its
24+
election pages. New kinds can be added in the Django admin without
25+
code changes.
26+
"""
27+
28+
name = models.CharField(max_length=100, unique=True)
29+
slug = models.SlugField(max_length=120, unique=True, blank=True, null=True)
30+
accent_color = ColorField(default=DEFAULT_ACCENT_COLOR, help_text="Accent color used to theme this kind's pages.")
31+
32+
class Meta:
33+
"""Meta configuration for ElectionKind."""
34+
35+
ordering = ["name"]
36+
37+
def __str__(self):
38+
"""Return the kind name."""
39+
return self.name
40+
41+
def save(self, *args, **kwargs):
42+
"""Generate slug from name before saving."""
43+
self.slug = slugify(self.name)
44+
super().save(*args, **kwargs)
45+
1646

1747
class Election(models.Model):
1848
"""A PSF board election with nomination open/close dates."""
1949

2050
name = models.CharField(max_length=100)
2151
date = models.DateField()
52+
kind = models.ForeignKey(
53+
ElectionKind,
54+
null=True,
55+
blank=True,
56+
on_delete=models.SET_NULL,
57+
related_name="elections",
58+
)
2259
nominations_open_at = models.DateTimeField(blank=True, null=True)
2360
nominations_close_at = models.DateTimeField(blank=True, null=True)
2461
description = MarkupField(escape_html=False, markup_type="markdown", blank=False, null=True)
@@ -39,6 +76,11 @@ def save(self, *args, **kwargs):
3976
self.slug = slugify(self.name)
4077
super().save(*args, **kwargs)
4178

79+
@property
80+
def accent_color(self):
81+
"""Return the CSS accent color for this election's kind."""
82+
return self.kind.accent_color if self.kind else DEFAULT_ACCENT_COLOR
83+
4284
@property
4385
def nominations_open(self):
4486
"""Return True if the current time is within the nomination window."""
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{% comment %}
2+
Per-election theming. Expects `election` in context (pass via
3+
`{% include "nominations/_theme.html" with election=object.election %}`
4+
where only the nomination object is available). Colors key headings
5+
with the election's accent so Board vs. Packaging Council pages are
6+
visually distinct. See Election.accent_color / the ElectionKind model.
7+
{% endcomment %}
8+
{% if election %}
9+
<style>
10+
:root {
11+
--election-accent: {{ election.accent_color }};
12+
}
13+
/* Scoped to #content; this <style> only renders on themed election
14+
pages, so these selectors never leak to the rest of the site. */
15+
#content h1,
16+
#content .page-title {
17+
color: var(--election-accent);
18+
}
19+
</style>
20+
{% endif %}

apps/nominations/templates/nominations/election_detail.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
{% block page_title %}Elections | {{ SITE_INFO.site_name }}{% endblock %}
66

7+
{% block head %}{% include "nominations/_theme.html" %}{% endblock %}
8+
79
{% block content_attributes %}with-left-sidebar{% endblock %}
810

911

apps/nominations/templates/nominations/nomination_accept_form.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{% endblock %}
77

88
{% block body_attributes %}class="nominations nominations_view"{% endblock %}
9+
{% block head %}{% include "nominations/_theme.html" %}{% endblock %}
910
{% block left_sidebar %}{% endblock %}
1011
{% block content_attributes %}{% endblock %}
1112

apps/nominations/templates/nominations/nomination_detail.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{% endblock %}
77

88
{% block body_attributes %}class="nominations nominations_view"{% endblock %}
9+
{% block head %}{% include "nominations/_theme.html" with election=nomination.election %}{% endblock %}
910
{% block left_sidebar %}{% endblock %}
1011
{% block content_attributes %}{% endblock %}
1112

apps/nominations/templates/nominations/nomination_form.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
{% endblock %}
1111

1212
{% block body_attributes %}class="nominations nominations_form"{% endblock %}
13+
{% block head %}{% include "nominations/_theme.html" %}{% endblock %}
1314
{% block left_sidebar %}{% endblock %}
1415
{% block content_attributes %}{% endblock %}
1516

apps/nominations/templates/nominations/nominee_detail.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{% endblock %}
88

99
{% block body_attributes %}class="nominations nominations_view"{% endblock %}
10+
{% block head %}{% include "nominations/_theme.html" %}{% endblock %}
1011
{% block left_sidebar %}{% endblock %}
1112
{% block content_attributes %}{% endblock %}
1213

apps/nominations/templates/nominations/nominee_list.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
{% block content_attributes %}{% endblock %}
1212

1313
{% block head %}
14+
{% include "nominations/_theme.html" %}
1415
<style>
1516
details
1617
{

0 commit comments

Comments
 (0)