Skip to content

Commit

Permalink
Implement wrap-up page (#71)
Browse files Browse the repository at this point in the history
Co-authored-by: Jay Qi <jayqi@users.noreply.github.com>
  • Loading branch information
jayqi and jayqi authored Jan 8, 2025
1 parent 2ea25ef commit 5b3ff4d
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 29 deletions.
4 changes: 4 additions & 0 deletions huntsite/content/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib import admin
from solo.admin import SingletonModelAdmin

from huntsite.admin import UneditableAsReadOnlyAdminMixin
import huntsite.content.models as models
Expand Down Expand Up @@ -26,3 +27,6 @@ class AttributionsEntryAdmin(UneditableAsReadOnlyAdminMixin, admin.ModelAdmin):
class UpdateEntryAdmin(UneditableAsReadOnlyAdminMixin, admin.ModelAdmin):
list_display = ("__str__", "published_at")
ordering = ("-published_at",)


admin.site.register(models.WrapupEntry, SingletonModelAdmin)
27 changes: 27 additions & 0 deletions huntsite/content/migrations/0005_wrapupentry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.0.4 on 2025-01-06 20:05

import huntsite.content.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('content', '0004_updateentry'),
]

operations = [
migrations.CreateModel(
name='WrapupEntry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField()),
('available_at', models.DateTimeField(default=huntsite.content.models._wrapup_entry_available_at_default)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'abstract': False,
},
),
]
24 changes: 24 additions & 0 deletions huntsite/content/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import datetime

from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
import markdown
from solo.models import SingletonModel


class AboutEntry(models.Model):
Expand Down Expand Up @@ -78,3 +81,24 @@ def __str__(self):

def render_content(self):
return markdown.markdown(self.content)


def _wrapup_entry_available_at_default():
return timezone.now() + timezone.timedelta(days=30)


class WrapupEntry(SingletonModel):
content = models.TextField()
available_at = models.DateTimeField(default=_wrapup_entry_available_at_default)

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return "Wrapup Entry"

def is_available_at(self, dt: datetime.datetime):
return self.available_at <= dt

def render_content(self):
return markdown.markdown(self.content)
57 changes: 57 additions & 0 deletions huntsite/content/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
StoryEntryFactory,
UpdateEntryFactory,
)
from huntsite.content.models import WrapupEntry
from huntsite.puzzles.factories import (
MetapuzzleInfoFactory,
PuzzleAttributionsEntryFactory,
Expand Down Expand Up @@ -443,3 +444,59 @@ def test_updates_page(client):
assert "North Pole" in entries[0].text
assert "Santa" in entries[1].text
assert "reindeer" in entries[2].text


def test_wrapup_navbar(client, settings):
## Hunt state is live
settings.HUNT_IS_LIVE_DATETIME = timezone.now() - timedelta(days=3)
settings.HUNT_IS_ENDED_DATETIME = timezone.now() + timedelta(days=3)

wrapup_entry = WrapupEntry.get_solo()
wrapup_entry.content = "Merry Christmas to all, and to all a good night!"
wrapup_entry.available_at = timezone.now() + timedelta(days=6)
wrapup_entry.save()

response = client.get("/")
assert response.status_code == 200
soup = BeautifulSoup(response.content, "html.parser")
assert not soup.find("a", class_="navbar-item", string="Wrap-up")

## Hint state is ended but before wrapup available
settings.HUNT_IS_ENDED_DATETIME = timezone.now() - timedelta(days=2)
response = client.get("/")
assert response.status_code == 200
soup = BeautifulSoup(response.content, "html.parser")
assert not soup.find("a", class_="navbar-item", string="Wrap-up")

## Wrapup is available
wrapup_entry.available_at = timezone.now() - timedelta(days=1)
wrapup_entry.save()
response = client.get("/")
assert response.status_code == 200
soup = BeautifulSoup(response.content, "html.parser")
assert soup.find("a", class_="navbar-item", string="Wrap-up")


def test_wrapup_page(client):
wrapup_entry = WrapupEntry.get_solo()
wrapup_entry.content = "Merry Christmas to all, and to all a good night!"
wrapup_entry.save()

# 404 on wrapup page if not available
response = client.get("/wrapup/")
assert response.status_code == 404

# Test user can see wrapup page though
test_user = UserFactory(is_tester=True)
test_client = Client()
test_client.force_login(test_user)
response = test_client.get("/wrapup/")
assert response.status_code == 200
assert wrapup_entry.content in response.content.decode()

# Update wrapup entry to be available
wrapup_entry.available_at = timezone.now() - timedelta(days=1)
wrapup_entry.save()
response = client.get("/wrapup/")
assert response.status_code == 200
assert wrapup_entry.content in response.content.decode()
1 change: 1 addition & 0 deletions huntsite/content/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
path("story/victory/", views.victory_page, name="victory"),
path("attributions/", views.attributions_page, name="attributions"),
path("updates/", views.updates_page, name="updates"),
path("wrapup/", views.wrapup_page, name="wrapup"),
]
14 changes: 13 additions & 1 deletion huntsite/content/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from huntsite.content import models
from huntsite.puzzles import models as puzzle_models
from huntsite.utils import HuntState, get_hunt_state
from huntsite.utils import HuntState, get_hunt_state, is_wrapup_available


@require_safe
Expand Down Expand Up @@ -77,3 +77,15 @@ def updates_page(request):
"entries": entries,
}
return TemplateResponse(request, "updates.html", context)


@require_safe
def wrapup_page(request):
if not request.user.is_tester and not is_wrapup_available(request):
raise Http404

entry = models.WrapupEntry.get_solo()
context = {
"entry": entry,
}
return TemplateResponse(request, "wrapup.html", context)
10 changes: 7 additions & 3 deletions huntsite/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site

from huntsite.utils import HuntState, get_hunt_state
from huntsite.utils import HuntState, get_hunt_state, is_wrapup_available


def canonical(request):
Expand All @@ -24,10 +24,14 @@ def meta(request):

def hunt_state(request):
"""Context processor to set the hunt state."""
return {
hunt_state = get_hunt_state(request)
context = {
"HuntState": HuntState,
"hunt_state": get_hunt_state(request),
"hunt_state": hunt_state,
}
if hunt_state >= HuntState.ENDED:
context["wrapup_is_available"] = is_wrapup_available(request)
return context


def announcement_message(request):
Expand Down
15 changes: 15 additions & 0 deletions huntsite/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import enum

from django.apps import apps
from django.conf import settings
from django.utils import timezone

Expand Down Expand Up @@ -41,3 +42,17 @@ def get_hunt_state(request):
return HuntState.LIVE
else:
return HuntState.PREHUNT


def is_wrapup_available(request):
"""Determine if wrapup is available."""
if (
request.user.is_authenticated
and request.user.is_tester
and (time_traveling_at := read_time_travel_session_var(request))
):
now = time_traveling_at
else:
now = timezone.now()
wrapup_entry = apps.get_model("content", "WrapupEntry").get_solo()
return wrapup_entry.is_available_at(now)
7 changes: 7 additions & 0 deletions project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class DeployEnvironment(StrEnum):
"django_admin_action_forms",
"django_no_queryset_admin_actions",
"robots",
"solo",
# Local apps
"huntsite",
"huntsite.content",
Expand Down Expand Up @@ -265,6 +266,12 @@ class DeployEnvironment(StrEnum):
}
logger.info("Using cache backend: " + CACHES["default"]["BACKEND"])

# Solo cache

if env.bool("SOLO_CACHE_ENABLED", default=False):
SOLO_CACHE = "default"
SOLO_CACHE_TIMEOUT = env.int("SOLO_CACHE_TIMEOUT", default=60 * 5)

## Sessions

if REDIS_URL:
Expand Down
13 changes: 3 additions & 10 deletions requirements/demo.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ charset-normalizer==3.3.2
# via
# -r requirements/deploy.txt
# requests
click==8.1.7
# via
# -r requirements/deploy.txt
# uvicorn
crispy-bulma==0.11.0
# via -r requirements/deploy.txt
dj-database-url==2.2.0
Expand All @@ -36,6 +32,7 @@ django==5.0.4
# django-crispy-forms
# django-debug-toolbar
# django-no-queryset-admin-actions
# django-solo
django-admin-action-forms==1.2.4
# via -r requirements/deploy.txt
django-allauth==0.63.6
Expand All @@ -54,6 +51,8 @@ django-no-queryset-admin-actions==1.1.0
# via -r requirements/deploy.txt
django-robots==6.1
# via -r requirements/deploy.txt
django-solo==2.4.0
# via -r requirements/deploy.txt
environs==11.0.0
# via -r requirements/deploy.txt
factory-boy==3.3.0
Expand All @@ -64,10 +63,6 @@ faker==26.0.0
# factory-boy
gunicorn==22.0.0
# via -r requirements/deploy.txt
h11==0.14.0
# via
# -r requirements/deploy.txt
# uvicorn
hiredis==3.0.0
# via -r requirements/deploy.txt
idna==3.7
Expand Down Expand Up @@ -129,7 +124,5 @@ urllib3==2.2.2
# django-anymail
# requests
# sentry-sdk
uvicorn==0.30.1
# via -r requirements/deploy.txt
whitenoise==6.7.0
# via -r requirements/deploy.txt
3 changes: 2 additions & 1 deletion requirements/deploy.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ django-crispy-forms
django-debug-toolbar
django-no-queryset-admin-actions
django-robots>=4.0.0
django-solo
environs[django]
gunicorn
logtail-python
loguru
markdown
psycopg2-binary
redis[hiredis]
uvicorn
# uvicorn
sentry-sdk[django]
whitenoise[brotli]
9 changes: 3 additions & 6 deletions requirements/deploy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ certifi==2024.7.4
# sentry-sdk
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via uvicorn
crispy-bulma==0.11.0
# via -r requirements/deploy.in
dj-database-url==2.2.0
Expand All @@ -29,6 +27,7 @@ django==5.0.4
# django-crispy-forms
# django-debug-toolbar
# django-no-queryset-admin-actions
# django-solo
# sentry-sdk
django-admin-action-forms==1.2.4
# via -r requirements/deploy.in
Expand All @@ -48,12 +47,12 @@ django-no-queryset-admin-actions==1.1.0
# via -r requirements/deploy.in
django-robots==6.1
# via -r requirements/deploy.in
django-solo==2.4.0
# via -r requirements/deploy.in
environs==11.0.0
# via -r requirements/deploy.in
gunicorn==22.0.0
# via -r requirements/deploy.in
h11==0.14.0
# via uvicorn
hiredis==3.0.0
# via redis
idna==3.7
Expand Down Expand Up @@ -95,7 +94,5 @@ urllib3==2.2.2
# django-anymail
# requests
# sentry-sdk
uvicorn==0.30.1
# via -r requirements/deploy.in
whitenoise==6.7.0
# via -r requirements/deploy.in
11 changes: 3 additions & 8 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,9 @@ charset-normalizer==3.3.2
# requests
click==8.1.7
# via
# -r requirements/demo.txt
# djlint
# flask
# typer
# uvicorn
cloudpathlib==0.18.1
# via -r requirements/dev.in
colorama==0.4.6
Expand Down Expand Up @@ -68,6 +66,7 @@ django==5.0.4
# django-crispy-forms
# django-debug-toolbar
# django-no-queryset-admin-actions
# django-solo
django-admin-action-forms==1.2.4
# via -r requirements/demo.txt
django-allauth==0.63.6
Expand All @@ -86,6 +85,8 @@ django-no-queryset-admin-actions==1.1.0
# via -r requirements/demo.txt
django-robots==6.1
# via -r requirements/demo.txt
django-solo==2.4.0
# via -r requirements/demo.txt
djlint==1.34.1
# via -r requirements/dev.in
editorconfig==0.12.4
Expand Down Expand Up @@ -124,10 +125,6 @@ greenlet==3.0.3
# via gevent
gunicorn==22.0.0
# via -r requirements/demo.txt
h11==0.14.0
# via
# -r requirements/demo.txt
# uvicorn
hiredis==3.0.0
# via -r requirements/demo.txt
html-tag-names==0.1.2
Expand Down Expand Up @@ -301,8 +298,6 @@ urllib3==2.2.2
# geventhttpclient
# requests
# sentry-sdk
uvicorn==0.30.1
# via -r requirements/demo.txt
wcwidth==0.2.13
# via prompt-toolkit
werkzeug==3.0.3
Expand Down
Loading

0 comments on commit 5b3ff4d

Please sign in to comment.