Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calendar Entry #2

Merged
merged 4 commits into from
Jul 19, 2024
Merged
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
8 changes: 8 additions & 0 deletions dev-requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-r requirements.txt

coverage
djlint
factory-boy
faker
pytest-django
ruff
134 changes: 127 additions & 7 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,127 @@
-r requirements.txt

coverage
factory-boy
faker
pytest-django
ruff
# This file was autogenerated by uv via the following command:
# uv pip compile dev-requirements.in -o dev-requirements.txt
asgiref==3.8.1
# via
# -r requirements.txt
# django
brotli==1.1.0
# via -r requirements.txt
click==8.1.7
# via
# -r requirements.txt
# djlint
# uvicorn
colorama==0.4.6
# via djlint
coverage==7.6.0
# via -r dev-requirements.in
crispy-bootstrap5==2024.2
# via -r requirements.txt
cssbeautifier==1.15.1
# via djlint
dj-database-url==2.2.0
# via -r requirements.txt
dj-email-url==1.0.6
# via -r requirements.txt
django==5.0.4
# via
# -r requirements.txt
# crispy-bootstrap5
# dj-database-url
# django-allauth
# django-crispy-forms
# django-debug-toolbar
django-allauth==0.63.6
# via -r requirements.txt
django-cache-url==3.4.5
# via -r requirements.txt
django-crispy-forms==2.2
# via
# -r requirements.txt
# crispy-bootstrap5
django-debug-toolbar==4.3.0
# via -r requirements.txt
djlint==1.34.1
# via -r dev-requirements.in
editorconfig==0.12.4
# via
# cssbeautifier
# jsbeautifier
environs==11.0.0
# via -r requirements.txt
factory-boy==3.3.0
# via -r dev-requirements.in
faker==26.0.0
# via
# -r dev-requirements.in
# factory-boy
gunicorn==22.0.0
# via -r requirements.txt
h11==0.14.0
# via
# -r requirements.txt
# uvicorn
html-tag-names==0.1.2
# via djlint
html-void-elements==0.1.0
# via djlint
iniconfig==2.0.0
# via pytest
jsbeautifier==1.15.1
# via
# cssbeautifier
# djlint
json5==0.9.25
# via djlint
marshmallow==3.21.3
# via
# -r requirements.txt
# environs
packaging==24.1
# via
# -r requirements.txt
# gunicorn
# marshmallow
# pytest
pathspec==0.12.1
# via djlint
pluggy==1.5.0
# via pytest
psycopg2-binary==2.9.9
# via -r requirements.txt
pytest==8.2.2
# via pytest-django
pytest-django==4.8.0
# via -r dev-requirements.in
python-dateutil==2.9.0.post0
# via faker
python-dotenv==1.0.1
# via
# -r requirements.txt
# environs
pyyaml==6.0.1
# via djlint
regex==2023.12.25
# via djlint
ruff==0.5.3
# via -r dev-requirements.in
six==1.16.0
# via
# cssbeautifier
# jsbeautifier
# python-dateutil
sqlparse==0.5.0
# via
# -r requirements.txt
# django
# django-debug-toolbar
tqdm==4.66.4
# via djlint
typing-extensions==4.12.2
# via
# -r requirements.txt
# dj-database-url
uvicorn==0.30.1
# via -r requirements.txt
whitenoise==6.7.0
# via -r requirements.txt
1 change: 1 addition & 0 deletions huntsite/puzzles/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ def get_readonly_fields(self, request, obj=None):
admin.site.register(models.Puzzle, UneditableAsReadOnlyAdmin)
admin.site.register(models.Guess, UneditableAsReadOnlyAdmin)
admin.site.register(models.Solve, UneditableAsReadOnlyAdmin)
admin.site.register(models.AdventCalendarEntry, UneditableAsReadOnlyAdmin)
58 changes: 39 additions & 19 deletions huntsite/puzzles/factories.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import random

from django.conf import settings
from django.core.files import File
from django.db.models.signals import post_save
import factory
from fpdf import FPDF
import factory.fuzzy
from faker import Faker

fake = Faker()

MOCK_PUZZLES = [
f"{settings.BASE_URL}/static/mock_puzzles/{mock_file.name}"
for mock_file in (settings.BASE_DIR / "static" / "mock_puzzles").glob("*.pdf")
]


def title_text_factory(instance) -> str:
nb = random.randint(1, 3)
return " ".join(fake.words(nb=nb)).title()


def create_pdf_file():
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", size=64)
pdf.cell(text="Hello", new_x="CENTER", align="C")
pdf.output("tuto1.pdf")
def answer_text_factory(instance) -> str:
nb = random.randint(1, 2)
return " ".join(fake.words(nb=nb)).upper()


@factory.django.mute_signals(post_save)
class PuzzleFactory(factory.django.DjangoModelFactory):
class Meta:
model = "puzzles.Puzzle"

name = factory.Faker("sentence", nb_words=4)
name = factory.lazy_attribute(title_text_factory)
slug = factory.Faker("slug")
answer = factory.Faker("word")
answer = factory.lazy_attribute(answer_text_factory)
pdf_url = factory.fuzzy.FuzzyChoice(MOCK_PUZZLES)

@factory.lazy_attribute
def pdf_file(self):
actual_filepath = str(
settings.BASE_DIR / "puzzles" / "tests" / "assets" / "test_puzzle.pdf"
)
return File(open(actual_filepath, "rb"))
calendar_entry = factory.RelatedFactory(
"huntsite.puzzles.factories.AdventCalendarEntryFactory",
factory_related_name="puzzle",
)


class GuessFactory(factory.django.DjangoModelFactory):
Expand All @@ -34,7 +46,7 @@ class Meta:

user = factory.SubFactory("users.factories.UserFactory")
puzzle = factory.SubFactory(PuzzleFactory)
text = factory.Faker("word")
text = factory.lazy_attribute(answer_text_factory)
is_correct = factory.Faker("boolean")


Expand All @@ -51,7 +63,15 @@ class IncorrectGuessFactory(GuessFactory):

@factory.post_generation
def text(self, create, extracted, **kwargs):
word = factory.Faker("word")
word = answer_text_factory()
while word == self.puzzle.answer:
word = factory.Faker("word")
word = answer_text_factory()
self.text = word


@factory.django.mute_signals(post_save)
class AdventCalendarEntryFactory(factory.django.DjangoModelFactory):
puzzle = factory.SubFactory("huntsite.puzzles.factories.PuzzleFactory", calendar_entry=None)

class Meta:
model = "puzzles.AdventCalendarEntry"
14 changes: 14 additions & 0 deletions huntsite/puzzles/management/commands/create_demo_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.core.management.base import BaseCommand
from django.db import transaction

from huntsite.puzzles.factories import PuzzleFactory


class Command(BaseCommand):
def handle(self, *args, **options):
with transaction.atomic():
# Puzzles
for i in range(24):
PuzzleFactory(calendar_entry__day=i + 1)

self.stdout.write(self.style.SUCCESS("create_demo_data complete."))
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 5.0.4 on 2024-07-19 20:08

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('puzzles', '0002_initial'),
]

operations = [
migrations.AlterModelOptions(
name='guess',
options={'verbose_name_plural': 'Guesses'},
),
migrations.CreateModel(
name='AdventCalendarEntry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('day', models.IntegerField(default=-1)),
('puzzle', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='calendar_entry', to='puzzles.puzzle')),
],
options={
'verbose_name_plural': 'Advent Calendar Entries',
'ordering': ['day'],
},
),
]
23 changes: 23 additions & 0 deletions huntsite/puzzles/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse

from huntsite.puzzles.utils import clean_answer, normalize_answer
Expand Down Expand Up @@ -42,6 +44,9 @@ class Guess(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
verbose_name_plural = "Guesses"

@property
def evaluation(self):
return "Correct" if self.is_correct else "Incorrect"
Expand All @@ -65,3 +70,21 @@ class Meta:

def __str__(self):
return f"{self.user.teamprofile.team_name} - {self.puzzle.name} - {self.solved_datetime}"


class AdventCalendarEntry(models.Model):
puzzle = models.OneToOneField(Puzzle, on_delete=models.CASCADE, related_name="calendar_entry")
day = models.IntegerField(default=-1)

class Meta:
ordering = ["day"]
verbose_name_plural = "Advent Calendar Entries"

def __str__(self):
return f"{self.day} | {self.puzzle.name}"


@receiver(post_save, sender=Puzzle)
def create_advent_calendar_entry(sender, instance, created, **kwargs):
if created:
AdventCalendarEntry.objects.create(puzzle=instance)
5 changes: 5 additions & 0 deletions huntsite/puzzles/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@
def puzzle_guess_list(puzzle: Puzzle, user):
"""Function to return all guess submissions for a puzzle by a team."""
return puzzle.guess_set.filter(user=user).order_by("-created_at")


def solve_list(user):
"""Function to return all solves by a team."""
return user.solve_set.order_by("-created_at")
Empty file added huntsite/puzzles/signals.py
Empty file.
7 changes: 6 additions & 1 deletion huntsite/puzzles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@

def puzzle_list(request):
"""View to display a list of all puzzles."""
puzzles = Puzzle.objects.all()
puzzles = Puzzle.objects.select_related("calendar_entry").all()
if request.user.is_authenticated:
solves = {solve.puzzle: solve for solve in puzzle_selectors.solve_list(request.user)}
else:
solves = {}
context = {
"puzzles": puzzles,
"solves": solves,
}
return TemplateResponse(request, "puzzle_list.html", context)

Expand Down
7 changes: 7 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ requirements:
# Create requirements lockfile
compile-requirements:
uv pip compile requirements.in -o requirements.txt
uv pip compile dev-requirements.in -o dev-requirements.txt

# Lint the project
lint:
Expand All @@ -40,3 +41,9 @@ lint:
format:
ruff format project huntsite
ruff check --fix project huntsite

createsuperuser:
python manage.py createsuperuser --noinput

demo-data:
python manage.py create_demo_data
3 changes: 3 additions & 0 deletions project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env.bool("DEBUG", default=False)

if env("BASE_URL", ""):
BASE_URL = env("BASE_URL")

ALLOWED_HOSTS = []

RENDER_EXTERNAL_HOSTNAME = env("RENDER_EXTERNAL_HOSTNAME", "")
Expand Down
Binary file added static/mock_puzzles/christmas_tree.pdf
Binary file not shown.
Binary file added static/mock_puzzles/frosty.pdf
Binary file not shown.
Binary file added static/mock_puzzles/jingle_bells.pdf
Binary file not shown.
Binary file added static/mock_puzzles/rudolph.pdf
Binary file not shown.
Binary file added static/mock_puzzles/santa.pdf
Binary file not shown.
Binary file added static/mock_puzzles/snow.pdf
Binary file not shown.
Loading