Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions convention-template/config/settings.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ INSTALLED_APPS = [
"waffle",
# Convention admin utilities and seeding commands
"nomnom.convention_admin",
# Admin-configurable homepage timeline
"nomnom.timeline",
]

SITE_ID = 1
Expand Down
2 changes: 2 additions & 0 deletions nomnom_dev/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ def __bool__(self): # if using Python 2, use __nonzero__ instead
"nomnom.hugopacket",
# Convention admin utilities and seeding commands
"nomnom.convention_admin",
# Admin-configurable homepage timeline
"nomnom.timeline",
]

SITE_ID = 1
Expand Down
3 changes: 3 additions & 0 deletions src/nomnom/base/templates/nomnom/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{% if not user.is_authenticated %}
{% include "bits/login_forms.html" %}
{% else %}
{% switch "timeline" %}
{% include "timeline/timeline.html" %}
{% endswitch %}
<!-- For mobile: separate sections stacked -->
<div class="d-block d-md-none">
<!-- WSFS Elections Section for Mobile -->
Expand Down
4 changes: 1 addition & 3 deletions src/nomnom/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@


def index(request: HttpRequest) -> HttpResponse:
context = {}
if request.user.is_authenticated:
context = {}
for receiver, response in index_content_load.send(sender=None, request=request):
if isinstance(response, dict):
context.update(response)

else:
context = {}
return render(request, "nomnom/index.html", context)
205 changes: 205 additions & 0 deletions src/nomnom/nominate/static/css/layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,208 @@
position: relative;
z-index: 1;
}

.timeline {
margin: 0 1rem 2rem 0;
}

.timeline-toggle,
.timeline-toggle-label {
display: none;
}

.timeline-events {
list-style: none;
margin: 0;
padding: 0 0 0 2rem;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
row-gap: 1.25rem;
}

.timeline-event {
flex: 1 1 3rem;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0 0.5rem;
max-width: 8rem;
}

.timeline-event:first-child {
margin-left: -2rem;
}

.timeline-event::before,
.timeline-event::after {
content: "";
position: absolute;
top: calc(0.75rem - 2.5px);
height: 5px;
background: var(--timeline-color, #555);
z-index: 0;
}

.timeline-event::before {
left: 0;
right: 50%;
}

.timeline-event::after {
left: 50%;
right: 0;
}

.timeline-event:first-child::before,
.timeline-event:last-child::after {
content: none;
}

.timeline-bubble {
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
border: 5px solid var(--timeline-color, #555);
background: #fff;
position: relative;
z-index: 1;
margin-bottom: 0.4rem;
}

.timeline-event.is-complete .timeline-bubble {
background: var(--timeline-color, #555);
}

.timeline-label {
font-weight: bold;
font-size: 0.8rem;
line-height: 1.15;
}

.timeline-date {
font-size: 0.75rem;
margin-top: 0.15rem;
}

.timeline-footnote {
font-size: 0.7rem;
margin-top: 0.5rem;
margin-bottom: 0;
}

@media (max-width: 767.98px) {
.timeline-events {
flex-direction: column;
align-items: flex-start;
flex-wrap: nowrap;
padding-left: 0;
row-gap: 0;
}

.timeline-event {
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto;
column-gap: 0.75rem;
flex: 0 0 auto;
max-width: none;
padding: 0.5rem 0;
width: 100%;
text-align: left;
}

.timeline-event:first-child {
margin-left: 0;
}

.timeline-bubble {
grid-column: 1;
grid-row: 1 / span 2;
align-self: center;
margin-bottom: 0;
}

.timeline-label {
grid-column: 2;
grid-row: 1;
align-self: end;
}

.timeline-date {
grid-column: 2;
grid-row: 2;
align-self: start;
}

.timeline-event::before,
.timeline-event::after {
left: calc(0.75rem - 2.5px);
right: auto;
top: auto;
width: 5px;
height: 50%;
}

.timeline-event::before {
top: 0;
}

.timeline-event::after {
bottom: 0;
}

.timeline-label,
.timeline-date {
display: block;
text-align: left;
}

.timeline {
position: relative;
}

.timeline-toggle {
display: block;
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.timeline-toggle-label {
display: block;
position: absolute;
inset: 0;
cursor: pointer;
z-index: 2;
}

.timeline-toggle:focus-visible ~ .timeline-toggle-label {
outline: auto;
outline-offset: 2px;
}

.timeline-toggle:not(:checked) ~ .timeline-events .timeline-event:not(.timeline-event--shown) {
display: none;
}

.timeline-toggle:not(:checked) ~ .timeline-events .timeline-event:not(.timeline-event--shown) + .timeline-event--shown::before {
background:
linear-gradient(var(--timeline-color, #555), var(--timeline-color, #555)) center top 2px / 5px 5px no-repeat,
linear-gradient(var(--timeline-color, #555), var(--timeline-color, #555)) center top 10px / 5px 5px no-repeat;
}

.timeline-toggle:not(:checked) ~ .timeline-events .timeline-event--shown:has(+ .timeline-event:not(.timeline-event--shown))::after {
background:
linear-gradient(var(--timeline-color, #555), var(--timeline-color, #555)) center bottom 2px / 5px 5px no-repeat,
linear-gradient(var(--timeline-color, #555), var(--timeline-color, #555)) center bottom 10px / 5px 5px no-repeat;
}
}
1 change: 1 addition & 0 deletions src/nomnom/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"nomnom.canonicalize",
"nomnom.wsfs",
"nomnom.convention_admin",
"nomnom.timeline",
]
NPLUSONE_RAISE = True

Expand Down
Empty file added src/nomnom/timeline/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions src/nomnom/timeline/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib import admin

from . import models


@admin.register(models.TimelineEvent)
class TimelineEventAdmin(admin.ModelAdmin):
list_display = ["label", "date", "provisional", "position", "complete"]
list_editable = ["position", "complete"]
ordering = ["position"]
16 changes: 16 additions & 0 deletions src/nomnom/timeline/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.apps import AppConfig


class TimelineConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "nomnom.timeline"

def ready(self) -> None:
self.enable_signals()

return super().ready()

def enable_signals(self):
from . import (
receivers, # noqa: F401
)
3 changes: 3 additions & 0 deletions src/nomnom/timeline/feature_switches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Waffle switch name constants for the timeline feature."""

SWITCH_TIMELINE = "timeline"
51 changes: 51 additions & 0 deletions src/nomnom/timeline/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Generated by Django 5.2.11 on 2026-04-24 19:00

from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="TimelineEvent",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("label", models.CharField(max_length=255)),
(
"date",
models.CharField(
help_text="e.g. 'Feb 12th 2026' or 'Early May 2026'.",
max_length=255,
),
),
("position", models.PositiveSmallIntegerField(default=0)),
(
"provisional",
models.BooleanField(
default=False,
help_text="Whether to show a caveat about the date being provisional.",
),
),
(
"complete",
models.BooleanField(
default=False, help_text="Whether the event has passed."
),
),
],
options={
"ordering": ["position"],
},
),
]
38 changes: 38 additions & 0 deletions src/nomnom/timeline/migrations/0002_create_timeline_switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Data migration to create a feature flag toggle for the homepage timeline."""

from django.db import migrations

from nomnom.timeline.feature_switches import SWITCH_TIMELINE

SWITCHES = [
{
"name": SWITCH_TIMELINE,
"active": False,
"note": "Toggle homepage timeline visibility",
},
]


def create_switches(apps, schema_editor):
Switch = apps.get_model("waffle", "Switch")
for switch in SWITCHES:
Switch.objects.get_or_create(
name=switch["name"],
defaults={"active": switch["active"], "note": switch["note"]},
)


def delete_switches(apps, schema_editor):
Switch = apps.get_model("waffle", "Switch")
Switch.objects.filter(name__in=[s["name"] for s in SWITCHES]).delete()


class Migration(migrations.Migration):
dependencies = [
("waffle", "0004_update_everyone_nullbooleanfield"),
("timeline", "0001_initial"),
]

operations = [
migrations.RunPython(create_switches, delete_switches),
]
Empty file.
Loading
Loading