Skip to content

Commit 11e232f

Browse files
committed
Merge branch 'banners' into develop
2 parents 7b480b9 + 0bca3ed commit 11e232f

File tree

8 files changed

+99
-75
lines changed

8 files changed

+99
-75
lines changed

core/admin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,13 @@ def content_object(self, obj):
781781
content_object.short_description = "Associated Post"
782782

783783

784+
class BannerAdmin(admin.ModelAdmin):
785+
list_display = ["name", "start_date", "end_date"]
786+
search_fields = ["name"]
787+
788+
list_filter = ["start_date", "end_date"]
789+
790+
784791
admin.site.register(User, UserAdmin)
785792
admin.site.register(models.Timetable, TimetableAdmin)
786793
admin.site.register(models.Term, TermAdmin)
@@ -795,6 +802,7 @@ def content_object(self, obj):
795802
admin.site.register(models.Event, EventAdmin)
796803
admin.site.register(models.Raffle, RaffleAdmin)
797804
admin.site.register(models.StaffMember)
805+
admin.site.register(models.Banner, BannerAdmin)
798806

799807
admin.site.unregister(FlatPage)
800808
admin.site.register(FlatPage, CustomFlatPageAdmin)

core/api/views/meta.py

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from rest_framework.response import Response
88
from rest_framework.views import APIView
99

10+
from ...models import Banner
11+
1012

1113
@extend_schema(
1214
description="Returns the current API version.",
@@ -57,28 +59,6 @@ def get(request) -> Dict[str, str]:
5759
],
5860
)
5961
class Banners(APIView):
60-
uncensored_keys = (
61-
"start",
62-
"end",
63-
"content",
64-
"icon_url",
65-
"cta_link",
66-
"cta_label",
67-
)
68-
allowed_blank = ("icon_url", "cta_link", "cta_label")
69-
70-
@classmethod
71-
def censor(cls, banner: Dict) -> Dict:
72-
res = {}
73-
for key in cls.uncensored_keys:
74-
if key in banner:
75-
res[key] = banner[key]
76-
elif key not in banner and key in cls.allowed_blank:
77-
pass
78-
else:
79-
raise KeyError(f"Required Key {key} not found in banner {banner}")
80-
return res
81-
8262
@staticmethod
8363
def get(request):
8464
"""Returns the current banners and upcoming banners for the home page. note: upcoming banners only return the banners for the next day"""
@@ -87,11 +67,14 @@ def get(request):
8767
@classmethod
8868
def calculate_banners(cls):
8969
now = timezone.now()
90-
current = filter(lambda b: b["start"] <= now < b["end"], settings.BANNER3)
91-
current = list(map(Banners.censor, current))
92-
upcoming = filter(
93-
lambda b: now < b["start"] > now + timedelta(days=1),
94-
settings.BANNER3,
70+
71+
fields = [f.name for f in Banner._meta.fields if f.name != "name"]
72+
73+
current = Banner.objects.filter(start_date__lte=now, end_date__gt=now).values(
74+
*fields
9575
)
96-
upcoming = list(map(Banners.censor, upcoming))
97-
return dict(current=current, upcoming=upcoming)
76+
upcoming = Banner.objects.filter(
77+
start_date__gt=now, start_date__lt=now + timedelta(days=1)
78+
).values(*fields)
79+
80+
return dict(current=list(current), upcoming=list(upcoming))

core/migrations/0081_banner.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Generated by Django 5.1.14 on 2025-11-08 02:00
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('core', '0080_alter_announcement_options_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='Banner',
15+
fields=[
16+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('name', models.CharField(help_text='Give the banner a name! Only used for admin purposes to differentiate banners, not displayed elsewhere', max_length=100)),
18+
('content', models.CharField(max_length=200)),
19+
('icon_url', models.CharField(blank=True, max_length=150)),
20+
('cta_link', models.CharField(blank=True, max_length=150)),
21+
('cta_label', models.CharField(blank=True, help_text='Required only if cta_link is present', max_length=100)),
22+
('start_date', models.DateTimeField()),
23+
('end_date', models.DateTimeField()),
24+
],
25+
options={
26+
'constraints': [models.CheckConstraint(condition=models.Q(('cta_link', ''), models.Q(models.Q(('cta_link', ''), _negated=True), models.Q(('cta_label', ''), _negated=True)), _connector='OR'), name='cta_label_requirement'), models.CheckConstraint(condition=models.Q(('end_date__gte', models.F('start_date'))), name='end_date_gte_to_start_date')],
27+
},
28+
),
29+
]

core/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
from .tag import *
88
from .timetable import *
99
from .user import *
10+
from .banner import *

core/models/banner.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from django.db import models
2+
from django.core.exceptions import ValidationError
3+
4+
5+
class Banner(models.Model):
6+
name = models.CharField(
7+
max_length=100,
8+
help_text="Give the banner a name! Only used for admin purposes to differentiate banners, not displayed elsewhere",
9+
)
10+
11+
content = models.CharField(max_length=200)
12+
icon_url = models.CharField(max_length=150, blank=True)
13+
14+
cta_link = models.CharField(max_length=150, blank=True)
15+
cta_label = models.CharField(
16+
max_length=100, help_text="Required only if cta_link is present", blank=True
17+
)
18+
19+
start_date = models.DateTimeField()
20+
end_date = models.DateTimeField()
21+
22+
def clean(self):
23+
errors = {}
24+
25+
if self.cta_link and not self.cta_label:
26+
errors["cta_label"] = "cta label must be present if cta link is present"
27+
28+
if self.end_date and self.start_date and self.end_date < self.start_date:
29+
errors["end_date"] = "End date must be after start date"
30+
31+
if errors:
32+
raise ValidationError(errors)
33+
34+
return super().clean()
35+
36+
class Meta:
37+
constraints = [
38+
models.CheckConstraint(
39+
name="cta_label_requirement",
40+
check=models.Q(cta_link="")
41+
| (~models.Q(cta_link="") & ~models.Q(cta_label="")),
42+
),
43+
models.CheckConstraint(
44+
name="end_date_gte_to_start_date",
45+
check=models.Q(end_date__gte=models.F("start_date")),
46+
),
47+
]

core/models/post.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ class Meta:
330330
constraints = [
331331
models.CheckConstraint(
332332
name="organization_or_organization_string_required",
333-
check=models.Q(organization__isnull=False)
333+
condition=models.Q(organization__isnull=False)
334334
| models.Q(organization_string__isnull=False),
335335
)
336336
]

metropolis/local_settings_sample.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,6 @@
3535
"application/javascript", ".js", True
3636
) # fix some browser issues.
3737

38-
# Banner config
39-
now = datetime.now(TZ)
40-
# BANNER_REFERENCE_TIME = datetime.strptime("2023-11-14", "%Y-%m-%d").replace(tzinfo=timezone.utc) # use instead of $now for non-relative banners.
41-
42-
BANNER3 += [
43-
dict(
44-
start=now, # when to start displaying the banner
45-
end=now + timedelta(days=5),
46-
# when to stop displaying the banner (e.g. 5 days after start)
47-
content="Hello Hey!", # banner text
48-
icon_url="/static/core/img/logo/logo-maskable-192.png",
49-
# optionally, displays an icon on the left of the banner
50-
cta_link="https://jasoncameron.dev", # optional
51-
cta_label="wow! go visit this cool site!", # optional (but required if cta_link is present)
52-
)
53-
]
54-
55-
5638
if not DEBUG:
5739
"""
5840
Only used on production

metropolis/settings.py

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
import os
3-
from datetime import datetime, timedelta
3+
from datetime import timedelta
44
from typing import Dict, Final, List, Literal
55

66
import pytz
@@ -531,16 +531,6 @@
531531

532532
MAINTENANCE_MODE: Literal["", False, "pre", "post", "in"] = ""
533533
PRE = ""
534-
BANNER3: List = [
535-
# dict(
536-
# start=BANNER_REFERENCE_TIME,
537-
# end=BANNER_REFERENCE_TIME + timedelta(days=5),
538-
# content="This is some banner :)",
539-
# icon_url="...", # optional
540-
# cta_link="https://nyiyui.ca", # optional
541-
# cta_label="some shameless plug to nowhere amirite", # optional (but required if cta_link is present)
542-
# ),
543-
]
544534

545535
CELERY_TIMEZONE = "America/Toronto"
546536

@@ -553,22 +543,6 @@
553543
NOTIF_DRY_RUN = True
554544
NOTIFICATIONS_ENABLED = False
555545

556-
557-
def is_aware(d: datetime) -> bool:
558-
return d.tzinfo is not None and d.tzinfo.utcoffset(d) is not None
559-
560-
561-
def check_banner3(banner: Dict) -> None:
562-
assert is_aware(banner["start"])
563-
assert is_aware(banner["end"])
564-
assert bool(banner.get("cta_link")) == bool(
565-
banner.get("cta_label")
566-
) # both or neither, not one or the other
567-
568-
569-
for banner in BANNER3:
570-
check_banner3(banner)
571-
572546
try:
573547
with open(os.path.join(os.path.dirname(__file__), "./local_settings.py")) as f:
574548
exec(f.read(), globals())

0 commit comments

Comments
 (0)