From 0b49c39af5df9400c32db42bdee26c8f5a10ceac Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 23:45:46 -0500 Subject: [PATCH 01/71] Added start/end via current hunt --- core/context_processors.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index 7e1c569..3417a86 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,14 +1,16 @@ import datetime +from core.models import Hunt from django.conf import settings def start(request): - return dict( - START=(start := settings.START), - START_BEFORE=start > datetime.datetime.utcnow(), - START_UNTIL=start - datetime.datetime.utcnow(), - END=(end := settings.END), - END_BEFORE=end > datetime.datetime.utcnow(), - END_UNTIL=end - datetime.datetime.utcnow(), - ) + hunt = Hunt.current_hunt() + return dict( + START=(start := hunt.start), + START_BEFORE=start > datetime.datetime.utcnow(), + START_UNTIL=start - datetime.datetime.utcnow(), + END=(end := hunt.end), + END_BEFORE=end > datetime.datetime.utcnow(), + END_UNTIL=end - datetime.datetime.utcnow(), + ) From 0e577092b3115c96bf6e1a7efba9eca2f4a906d9 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 23:55:09 -0500 Subject: [PATCH 02/71] I'm clownin --- core/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/models.py b/core/models.py index 8f84743..c7039a4 100644 --- a/core/models.py +++ b/core/models.py @@ -204,6 +204,12 @@ class Hunt(models.Model): ending_location = models.ForeignKey( QrCode, on_delete=models.PROTECT, related_name="ending_location" ) + middle_locations = models.ManyToManyField( + QrCode, + related_name="hunt", + help_text="Possible locations that are not the start or end", + blank=True, + ) early_access_users = models.ManyToManyField( User, related_name="early_access_users", From 701f97cf32e396ad89537c365c2441eccd6f8af1 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Dec 2023 00:03:17 -0500 Subject: [PATCH 03/71] switched all references of settings.start or end to Hunt start/end --- .idea/workspace.xml | 86 ++++++------ core/context_processors.py | 18 +-- core/views/qr.py | 252 ++++++++++++++++++++---------------- core/views/team.py | 6 +- wlmac-scavenger/settings.py | 2 - 5 files changed, 195 insertions(+), 169 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 97adc6f..c4e80ac 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,13 +4,8 @@ - - @@ -447,7 +449,9 @@ - diff --git a/core/context_processors.py b/core/context_processors.py index 3417a86..ace58e2 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -5,12 +5,12 @@ def start(request): - hunt = Hunt.current_hunt() - return dict( - START=(start := hunt.start), - START_BEFORE=start > datetime.datetime.utcnow(), - START_UNTIL=start - datetime.datetime.utcnow(), - END=(end := hunt.end), - END_BEFORE=end > datetime.datetime.utcnow(), - END_UNTIL=end - datetime.datetime.utcnow(), - ) + hunt = Hunt.current_hunt() + return dict( + START=(start := hunt.start), + START_BEFORE=start > datetime.datetime.utcnow(), + START_UNTIL=start - datetime.datetime.utcnow(), + END=(end := hunt.end), + END_BEFORE=end > datetime.datetime.utcnow(), + END_UNTIL=end - datetime.datetime.utcnow(), + ) diff --git a/core/views/qr.py b/core/views/qr.py index 4a7c1fa..e5ab6df 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -1,56 +1,77 @@ import datetime +from functools import wraps from queue import LifoQueue -from django.conf import settings -from functools import wraps from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.views.decorators.http import require_http_methods -from django.shortcuts import render, redirect, get_object_or_404 -from django.urls import reverse -from django.utils.translation import gettext as _ -from ..models import QrCode, LogicPuzzleHint, Team from django.db.models import signals from django.dispatch import Signal, receiver from django.http import StreamingHttpResponse +from django.shortcuts import render, redirect, get_object_or_404 +from django.urls import reverse +from django.utils.translation import gettext as _ +from django.views.decorators.http import require_http_methods + +from ..models import QrCode, LogicPuzzleHint, Team, Hunt # NOTE: some of these GET routes are not idempotent, but that should be fine def team_required(f): - @wraps(f) - def wrapped(*args, **kwargs): - request = args[0] - if request.user.team is None: - messages.error( - request, - _("Please join a team or choose to go solo before getting a hint."), - ) - return redirect(reverse("index")) - return f(*args, **kwargs) - - return wrapped + @wraps(f) + def wrapped(*args, **kwargs): + request = args[0] + if request.user.team is None: + messages.error( + request, + _("Please join a team or choose to go solo before getting a hint."), + ) + return redirect(reverse("index")) + return f(*args, **kwargs) + + return wrapped def after_start(f): - @wraps(f) - def wrapped(*args, **kwargs): - request = args[0] - if ( - not request.user.has_perm( - "core.view_before_start" - ) # or current_hunt. todo impl - and settings.START > datetime.datetime.now() - ): - messages.error( - request, - _("Contest has not started yet."), - ) - return redirect(reverse("index")) - return f(*args, **kwargs) - - return wrapped + hunt_ = Hunt.current_hunt() + """ + Decorator for views that checks that the hunt has started. + + User can access the view if they meet ANY of the following conditions: + - They have the view_before_start permission + - The hunt has started + - They are on the early access list for that hunt + - They are a superuser + """ + + @wraps(f) + def wrapped(*args, **kwargs): + request = args[0] + if any( + [ + request.user.has_perm("core.view_before_start"), + (hunt_.start < datetime.datetime.now() < hunt_.end), + hunt_.early_access_users.filter(user=request.user).exists(), + request.user.is_superuser, + ] + ): + return f(*args, **kwargs) + + # if they DON'T have perms and the hunt hasn't started + if hunt_.start > datetime.datetime.now(): + messages.error( + request, + _("Contest has not started yet."), + ) + else: + messages.error( + request, + _("Contest has ended."), + ) + return redirect(reverse("index")) + + return wrapped @login_required @@ -58,21 +79,22 @@ def wrapped(*args, **kwargs): @team_required @after_start def qr(request, key): - context = dict(first=False) - context["qr"] = qr = get_object_or_404(QrCode, key=key) - context["hunt"] = qr.hunt - codes = QrCode.code_pks(request.user.team) - if qr.id not in codes: - context["offpath"] = True - return render(request, "core/qr.html", context=context) - i = codes.index(qr.id) - context["nextqr"] = ( - None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) - ) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - # TODO: check if they skipped? - request.user.team.update_current_qr_i(i) - return render(request, "core/qr.html", context=context) + context = dict(first=False) + context["qr"] = qr = get_object_or_404(QrCode, key=key) + context["qr"]: QrCode + context["hunt"]: Hunt = qr.hunt + codes = QrCode.code_pks(request.user.team) + if qr.id not in codes: + context["offpath"] = True + return render(request, "core/qr.html", context=context) + i = codes.index(qr.id) + context["nextqr"] = ( + None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) + ) + context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + # TODO: check if they skipped? + request.user.team.update_current_qr_i(i) + return render(request, "core/qr.html", context=context) @login_required @@ -80,16 +102,16 @@ def qr(request, key): @team_required @after_start def qr_first(request): - context = dict(first=True) - # check if the user is on the first qr code - # if request.user.team.current_qr_i != 0: - # messages.error(request, _("You are not on the first QR code.")) - # return redirect(reverse("qr_current")) - context["qr"] = QrCode.codes(request.user.team)[0] - codes = QrCode.code_pks(request.user.team) - context["nextqr"] = QrCode.objects.get(id=codes[0]) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - return render(request, "core/qr.html", context=context) + context = dict(first=True) + # check if the user is on the first qr code + # if request.user.team.current_qr_i != 0: + # messages.error(request, _("You are not on the first QR code.")) + # return redirect(reverse("qr_current")) + context["qr"] = QrCode.codes(request.user.team)[0] + codes = QrCode.code_pks(request.user.team) + context["nextqr"] = QrCode.objects.get(id=codes[0]) + context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + return render(request, "core/qr.html", context=context) @login_required @@ -97,15 +119,15 @@ def qr_first(request): @team_required @after_start def qr_current(request): - i = request.user.team.current_qr_i - context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[request.user.team.current_qr_i] - codes = QrCode.code_pks(request.user.team) - context["nextqr"] = ( - None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) - ) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - return render(request, "core/qr.html", context=context) + i = request.user.team.current_qr_i + context = dict(first=i == 0, current=True) + context["qr"] = QrCode.codes(request.user.team)[request.user.team.current_qr_i] + codes = QrCode.code_pks(request.user.team) + context["nextqr"] = ( + None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) + ) + context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + return render(request, "core/qr.html", context=context) @login_required @@ -113,10 +135,10 @@ def qr_current(request): @team_required @after_start def qr_catalog(request): - i = request.user.team.current_qr_i - context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] - return render(request, "core/qr_catalog.html", context=context) + i = request.user.team.current_qr_i + context = dict(first=i == 0, current=True) + context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] + return render(request, "core/qr_catalog.html", context=context) global_notifs = Signal() @@ -124,44 +146,44 @@ def qr_catalog(request): @receiver(signals.post_save, sender=Team) def team_change(sender, **kwargs): - global_notifs.send("team_change", orig_sender=sender, kwargs=kwargs) + global_notifs.send("team_change", orig_sender=sender, kwargs=kwargs) class SignalStream: - def __init__(self, signal, pk: int): - self.signal = signal - self.pk = pk - self.q = LifoQueue() - self.end = False - self.__setup() - - def __setup(self): - self.signal.connect(self.__receive) - self.q.put(("init", {})) - - def __del__(self): - self.signal.disconnect(self.__receive) - - def __receive(self, sender, **kwargs): - self.q.put((sender, kwargs)) - - def __iter__(self): - return self - - def __next__(self): - while 1: - if self.end: - raise StopIteration - sender, kwargs = self.q.get() - if sender == "team_change": - team = kwargs["kwargs"]["instance"] - if self.pk == team.id: - self.end = True - return f"event: {sender}\ndata: null\n" - else: - continue - else: - return f"event: {sender}\ndata: null\n" + def __init__(self, signal, pk: int): + self.signal = signal + self.pk = pk + self.q = LifoQueue() + self.end = False + self.__setup() + + def __setup(self): + self.signal.connect(self.__receive) + self.q.put(("init", {})) + + def __del__(self): + self.signal.disconnect(self.__receive) + + def __receive(self, sender, **kwargs): + self.q.put((sender, kwargs)) + + def __iter__(self): + return self + + def __next__(self): + while 1: + if self.end: + raise StopIteration + sender, kwargs = self.q.get() + if sender == "team_change": + team = kwargs["kwargs"]["instance"] + if self.pk == team.id: + self.end = True + return f"event: {sender}\ndata: null\n" + else: + continue + else: + return f"event: {sender}\ndata: null\n" @login_required @@ -169,9 +191,9 @@ def __next__(self): @team_required @after_start def qr_signal(request): - s = StreamingHttpResponse( - SignalStream(signal=global_notifs, pk=request.user.team.id), - content_type="text/event-stream", - ) - s["Cache-Control"] = "no-cache" - return s + s = StreamingHttpResponse( + SignalStream(signal=global_notifs, pk=request.user.team.id), + content_type="text/event-stream", + ) + s["Cache-Control"] = "no-cache" + return s diff --git a/core/views/team.py b/core/views/team.py index 2ea014d..ba737a2 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -17,7 +17,8 @@ @login_required @require_http_methods(["GET", "POST"]) def join(request): - if settings.START < datetime.datetime.now() and request.user.team is not None: + hunt_ = Hunt.current_hunt() + if hunt_.start < datetime.datetime.now() and request.user.team is not None: messages.error( request, _( @@ -61,7 +62,8 @@ def join(request): @login_required @require_http_methods(["GET", "POST"]) def make(request): - if settings.START < datetime.datetime.now() and request.user.team is not None: + hunt_ = Hunt.current_hunt() + if hunt_.start < datetime.datetime.now() and request.user.team is not None: messages.error( request, _("Since the hunt has already begun, making new teams is disallowed."), diff --git a/wlmac-scavenger/settings.py b/wlmac-scavenger/settings.py index 87b2aec..d99f813 100644 --- a/wlmac-scavenger/settings.py +++ b/wlmac-scavenger/settings.py @@ -124,5 +124,3 @@ raise TypeError("local_settings.py not found") SECRET_KEY # type: ignore -START # type: ignore -END # type: ignore From c4c3745beaa264c302c42a25c1ac3eb0d97cd563 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 3 Dec 2023 05:04:17 +0000 Subject: [PATCH 04/71] format --- core/views/qr.py | 244 +++++++++++++++++++++++------------------------ 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index e5ab6df..b5499bb 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -19,23 +19,23 @@ def team_required(f): - @wraps(f) - def wrapped(*args, **kwargs): - request = args[0] - if request.user.team is None: - messages.error( - request, - _("Please join a team or choose to go solo before getting a hint."), - ) - return redirect(reverse("index")) - return f(*args, **kwargs) - - return wrapped + @wraps(f) + def wrapped(*args, **kwargs): + request = args[0] + if request.user.team is None: + messages.error( + request, + _("Please join a team or choose to go solo before getting a hint."), + ) + return redirect(reverse("index")) + return f(*args, **kwargs) + + return wrapped def after_start(f): - hunt_ = Hunt.current_hunt() - """ + hunt_ = Hunt.current_hunt() + """ Decorator for views that checks that the hunt has started. User can access the view if they meet ANY of the following conditions: @@ -44,34 +44,34 @@ def after_start(f): - They are on the early access list for that hunt - They are a superuser """ - - @wraps(f) - def wrapped(*args, **kwargs): - request = args[0] - if any( - [ - request.user.has_perm("core.view_before_start"), - (hunt_.start < datetime.datetime.now() < hunt_.end), - hunt_.early_access_users.filter(user=request.user).exists(), - request.user.is_superuser, - ] - ): - return f(*args, **kwargs) - - # if they DON'T have perms and the hunt hasn't started - if hunt_.start > datetime.datetime.now(): - messages.error( - request, - _("Contest has not started yet."), - ) - else: - messages.error( - request, - _("Contest has ended."), - ) - return redirect(reverse("index")) - - return wrapped + + @wraps(f) + def wrapped(*args, **kwargs): + request = args[0] + if any( + [ + request.user.has_perm("core.view_before_start"), + (hunt_.start < datetime.datetime.now() < hunt_.end), + hunt_.early_access_users.filter(user=request.user).exists(), + request.user.is_superuser, + ] + ): + return f(*args, **kwargs) + + # if they DON'T have perms and the hunt hasn't started + if hunt_.start > datetime.datetime.now(): + messages.error( + request, + _("Contest has not started yet."), + ) + else: + messages.error( + request, + _("Contest has ended."), + ) + return redirect(reverse("index")) + + return wrapped @login_required @@ -79,22 +79,22 @@ def wrapped(*args, **kwargs): @team_required @after_start def qr(request, key): - context = dict(first=False) - context["qr"] = qr = get_object_or_404(QrCode, key=key) - context["qr"]: QrCode - context["hunt"]: Hunt = qr.hunt - codes = QrCode.code_pks(request.user.team) - if qr.id not in codes: - context["offpath"] = True - return render(request, "core/qr.html", context=context) - i = codes.index(qr.id) - context["nextqr"] = ( - None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) - ) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - # TODO: check if they skipped? - request.user.team.update_current_qr_i(i) - return render(request, "core/qr.html", context=context) + context = dict(first=False) + context["qr"] = qr = get_object_or_404(QrCode, key=key) + context["qr"]: QrCode + context["hunt"]: Hunt = qr.hunt + codes = QrCode.code_pks(request.user.team) + if qr.id not in codes: + context["offpath"] = True + return render(request, "core/qr.html", context=context) + i = codes.index(qr.id) + context["nextqr"] = ( + None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) + ) + context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + # TODO: check if they skipped? + request.user.team.update_current_qr_i(i) + return render(request, "core/qr.html", context=context) @login_required @@ -102,16 +102,16 @@ def qr(request, key): @team_required @after_start def qr_first(request): - context = dict(first=True) - # check if the user is on the first qr code - # if request.user.team.current_qr_i != 0: - # messages.error(request, _("You are not on the first QR code.")) - # return redirect(reverse("qr_current")) - context["qr"] = QrCode.codes(request.user.team)[0] - codes = QrCode.code_pks(request.user.team) - context["nextqr"] = QrCode.objects.get(id=codes[0]) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - return render(request, "core/qr.html", context=context) + context = dict(first=True) + # check if the user is on the first qr code + # if request.user.team.current_qr_i != 0: + # messages.error(request, _("You are not on the first QR code.")) + # return redirect(reverse("qr_current")) + context["qr"] = QrCode.codes(request.user.team)[0] + codes = QrCode.code_pks(request.user.team) + context["nextqr"] = QrCode.objects.get(id=codes[0]) + context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + return render(request, "core/qr.html", context=context) @login_required @@ -119,15 +119,15 @@ def qr_first(request): @team_required @after_start def qr_current(request): - i = request.user.team.current_qr_i - context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[request.user.team.current_qr_i] - codes = QrCode.code_pks(request.user.team) - context["nextqr"] = ( - None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) - ) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - return render(request, "core/qr.html", context=context) + i = request.user.team.current_qr_i + context = dict(first=i == 0, current=True) + context["qr"] = QrCode.codes(request.user.team)[request.user.team.current_qr_i] + codes = QrCode.code_pks(request.user.team) + context["nextqr"] = ( + None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) + ) + context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + return render(request, "core/qr.html", context=context) @login_required @@ -135,10 +135,10 @@ def qr_current(request): @team_required @after_start def qr_catalog(request): - i = request.user.team.current_qr_i - context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] - return render(request, "core/qr_catalog.html", context=context) + i = request.user.team.current_qr_i + context = dict(first=i == 0, current=True) + context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] + return render(request, "core/qr_catalog.html", context=context) global_notifs = Signal() @@ -146,44 +146,44 @@ def qr_catalog(request): @receiver(signals.post_save, sender=Team) def team_change(sender, **kwargs): - global_notifs.send("team_change", orig_sender=sender, kwargs=kwargs) + global_notifs.send("team_change", orig_sender=sender, kwargs=kwargs) class SignalStream: - def __init__(self, signal, pk: int): - self.signal = signal - self.pk = pk - self.q = LifoQueue() - self.end = False - self.__setup() - - def __setup(self): - self.signal.connect(self.__receive) - self.q.put(("init", {})) - - def __del__(self): - self.signal.disconnect(self.__receive) - - def __receive(self, sender, **kwargs): - self.q.put((sender, kwargs)) - - def __iter__(self): - return self - - def __next__(self): - while 1: - if self.end: - raise StopIteration - sender, kwargs = self.q.get() - if sender == "team_change": - team = kwargs["kwargs"]["instance"] - if self.pk == team.id: - self.end = True - return f"event: {sender}\ndata: null\n" - else: - continue - else: - return f"event: {sender}\ndata: null\n" + def __init__(self, signal, pk: int): + self.signal = signal + self.pk = pk + self.q = LifoQueue() + self.end = False + self.__setup() + + def __setup(self): + self.signal.connect(self.__receive) + self.q.put(("init", {})) + + def __del__(self): + self.signal.disconnect(self.__receive) + + def __receive(self, sender, **kwargs): + self.q.put((sender, kwargs)) + + def __iter__(self): + return self + + def __next__(self): + while 1: + if self.end: + raise StopIteration + sender, kwargs = self.q.get() + if sender == "team_change": + team = kwargs["kwargs"]["instance"] + if self.pk == team.id: + self.end = True + return f"event: {sender}\ndata: null\n" + else: + continue + else: + return f"event: {sender}\ndata: null\n" @login_required @@ -191,9 +191,9 @@ def __next__(self): @team_required @after_start def qr_signal(request): - s = StreamingHttpResponse( - SignalStream(signal=global_notifs, pk=request.user.team.id), - content_type="text/event-stream", - ) - s["Cache-Control"] = "no-cache" - return s + s = StreamingHttpResponse( + SignalStream(signal=global_notifs, pk=request.user.team.id), + content_type="text/event-stream", + ) + s["Cache-Control"] = "no-cache" + return s From b2889e9211911ee3445fe5452f1f3aaf4fd53cb6 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Dec 2023 00:05:47 -0500 Subject: [PATCH 05/71] .idea then I sleep --- .idea/misc.xml | 4 +- .idea/scavenger.iml | 2 +- .idea/workspace.xml | 100 +++++++++++++++++++++++++++----------------- 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index b1ea2f1..995cec4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - - + + \ No newline at end of file diff --git a/.idea/scavenger.iml b/.idea/scavenger.iml index dbb060e..d1a04da 100644 --- a/.idea/scavenger.iml +++ b/.idea/scavenger.iml @@ -14,7 +14,7 @@ - + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index c4e80ac..a7d0ce4 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,9 +4,7 @@ - - - + - { + "keyToString": { + "NIXITCH_NIXPKGS_CONFIG": "", + "NIXITCH_NIX_CONF_DIR": "", + "NIXITCH_NIX_OTHER_STORES": "", + "NIXITCH_NIX_PATH": "", + "NIXITCH_NIX_PROFILES": "", + "NIXITCH_NIX_REMOTE": "", + "NIXITCH_NIX_USER_PROFILE_DIR": "", + "RunOnceActivity.OpenProjectViewOnStart": "true", + "RunOnceActivity.ShowReadmeOnStart": "true", + "SHARE_PROJECT_CONFIGURATION_FILES": "true", + "WebServerToolWindowFactoryState": "true", + "WebServerToolWindowPanel.toolwindow.highlight.mappings": "true", + "WebServerToolWindowPanel.toolwindow.highlight.symlinks": "true", + "WebServerToolWindowPanel.toolwindow.show.date": "false", + "WebServerToolWindowPanel.toolwindow.show.permissions": "false", + "WebServerToolWindowPanel.toolwindow.show.size": "false", + "git-widget-placeholder": "main", + "ignore.virus.scanning.warn.message": "true", + "last_opened_file_path": "D:/projects/metropolis/backend", + "node.js.detected.package.eslint": "true", + "node.js.detected.package.tslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "settings.editor.selected.configurable": "Errors", + "ts.external.directory.path": "C:\\Users\\jason\\AppData\\Local\\Programs\\PyCharm Professional\\plugins\\javascript-impl\\jsLanguageServicesImpl\\external", + "vue.rearranger.settings.migration": "true" } -}]]> +} @@ -185,6 +182,7 @@ + - @@ -426,9 +448,6 @@ - - - @@ -451,7 +470,10 @@ - From 22e80302f76ce1e3936c623fb194b3f167cc0110 Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 3 Dec 2023 00:42:47 -0500 Subject: [PATCH 06/71] time sensitive + cleaner --- core/context_processors.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index ace58e2..a2eeb47 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -2,15 +2,17 @@ from core.models import Hunt from django.conf import settings +from django.utils import timezone def start(request): hunt = Hunt.current_hunt() + now = timezone.now() return dict( START=(start := hunt.start), - START_BEFORE=start > datetime.datetime.utcnow(), - START_UNTIL=start - datetime.datetime.utcnow(), + START_BEFORE=start > now, + START_UNTIL=start - now, END=(end := hunt.end), - END_BEFORE=end > datetime.datetime.utcnow(), - END_UNTIL=end - datetime.datetime.utcnow(), + END_BEFORE=end > now, + END_UNTIL=end - now, ) From 5b189c0f451bba00dcafda289744aa6cef50d200 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 13:31:27 -0500 Subject: [PATCH 07/71] updated .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bc58bf4..66b2122 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__ .direnv /db.sqlite3 /venv +.idea/workspace.xml From 775a010c7ad8e38ddd7ad7238e5dfbc2dfb274a9 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 13:39:34 -0500 Subject: [PATCH 08/71] updated .gitignore removes two .idea files --- .gitignore | 3 + .idea/dataSources.local.xml | 7 +- .idea/misc.xml | 7 - .idea/workspace.xml | 483 ------------------------------------ 4 files changed, 9 insertions(+), 491 deletions(-) delete mode 100644 .idea/misc.xml delete mode 100644 .idea/workspace.xml diff --git a/.gitignore b/.gitignore index 66b2122..098cd58 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ __pycache__ /db.sqlite3 /venv .idea/workspace.xml +.idea/misc.xml + +``` diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml index aed4b13..4daa519 100644 --- a/.idea/dataSources.local.xml +++ b/.idea/dataSources.local.xml @@ -2,7 +2,12 @@ - + + " + + + master_key + no-auth diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 995cec4..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index a7d0ce4..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,483 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "associatedIndex": 8 -} - - - - - { - "keyToString": { - "NIXITCH_NIXPKGS_CONFIG": "", - "NIXITCH_NIX_CONF_DIR": "", - "NIXITCH_NIX_OTHER_STORES": "", - "NIXITCH_NIX_PATH": "", - "NIXITCH_NIX_PROFILES": "", - "NIXITCH_NIX_REMOTE": "", - "NIXITCH_NIX_USER_PROFILE_DIR": "", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "SHARE_PROJECT_CONFIGURATION_FILES": "true", - "WebServerToolWindowFactoryState": "true", - "WebServerToolWindowPanel.toolwindow.highlight.mappings": "true", - "WebServerToolWindowPanel.toolwindow.highlight.symlinks": "true", - "WebServerToolWindowPanel.toolwindow.show.date": "false", - "WebServerToolWindowPanel.toolwindow.show.permissions": "false", - "WebServerToolWindowPanel.toolwindow.show.size": "false", - "git-widget-placeholder": "main", - "ignore.virus.scanning.warn.message": "true", - "last_opened_file_path": "D:/projects/metropolis/backend", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "settings.editor.selected.configurable": "Errors", - "ts.external.directory.path": "C:\\Users\\jason\\AppData\\Local\\Programs\\PyCharm Professional\\plugins\\javascript-impl\\jsLanguageServicesImpl\\external", - "vue.rearranger.settings.migration": "true" - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1701109954551 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From cfca466faaebbd15c7590807bfd4512667ec5262 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 13:45:22 -0500 Subject: [PATCH 09/71] Updates credits --- .idea/scavenger.iml | 4 +- core/templates/core/credits.html | 80 ++++++++++++++++---------------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/.idea/scavenger.iml b/.idea/scavenger.iml index d1a04da..533dad0 100644 --- a/.idea/scavenger.iml +++ b/.idea/scavenger.iml @@ -14,7 +14,7 @@ - + @@ -22,7 +22,7 @@ - - + diff --git a/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py b/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py index c61d716..695ff3e 100644 --- a/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py +++ b/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py @@ -31,7 +31,7 @@ def undo_create_hunt(apps, schema_editor): try: Hunt.objects.get(name="First Hunt").delete() - except Hunt.DoesNotExist: + except (Hunt.DoesNotExist, django.db.utils.OperationalError): pass diff --git a/core/migrations/0021_hunt_middle_locations.py b/core/migrations/0021_hunt_middle_locations.py new file mode 100644 index 0000000..7d37aa9 --- /dev/null +++ b/core/migrations/0021_hunt_middle_locations.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2023-12-03 19:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0020_remove_hunt_form_in_ending_text_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='hunt', + name='middle_locations', + field=models.ManyToManyField(blank=True, help_text='Possible locations that are not the start or end', related_name='hunt', to='core.qrcode'), + ), + ] diff --git a/core/views/qr.py b/core/views/qr.py index b5499bb..2cb0e51 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -34,7 +34,6 @@ def wrapped(*args, **kwargs): def after_start(f): - hunt_ = Hunt.current_hunt() """ Decorator for views that checks that the hunt has started. @@ -47,6 +46,8 @@ def after_start(f): @wraps(f) def wrapped(*args, **kwargs): + hunt_ = Hunt.current_hunt() + request = args[0] if any( [ From e47586ae6a2f4cf818b9d56856bbc3fc31b930d1 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 3 Dec 2023 19:08:38 +0000 Subject: [PATCH 11/71] format --- core/migrations/0021_hunt_middle_locations.py | 14 +++++++++----- core/views/qr.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/core/migrations/0021_hunt_middle_locations.py b/core/migrations/0021_hunt_middle_locations.py index 7d37aa9..9ca1f56 100644 --- a/core/migrations/0021_hunt_middle_locations.py +++ b/core/migrations/0021_hunt_middle_locations.py @@ -4,15 +4,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('core', '0020_remove_hunt_form_in_ending_text_and_more'), + ("core", "0020_remove_hunt_form_in_ending_text_and_more"), ] operations = [ migrations.AddField( - model_name='hunt', - name='middle_locations', - field=models.ManyToManyField(blank=True, help_text='Possible locations that are not the start or end', related_name='hunt', to='core.qrcode'), + model_name="hunt", + name="middle_locations", + field=models.ManyToManyField( + blank=True, + help_text="Possible locations that are not the start or end", + related_name="hunt", + to="core.qrcode", + ), ), ] diff --git a/core/views/qr.py b/core/views/qr.py index 2cb0e51..b050b75 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -35,19 +35,19 @@ def wrapped(*args, **kwargs): def after_start(f): """ - Decorator for views that checks that the hunt has started. - - User can access the view if they meet ANY of the following conditions: - - They have the view_before_start permission - - The hunt has started - - They are on the early access list for that hunt - - They are a superuser - """ + Decorator for views that checks that the hunt has started. + + User can access the view if they meet ANY of the following conditions: + - They have the view_before_start permission + - The hunt has started + - They are on the early access list for that hunt + - They are a superuser + """ @wraps(f) def wrapped(*args, **kwargs): hunt_ = Hunt.current_hunt() - + request = args[0] if any( [ From c1df39b58a9b77f3c9850953f03a11afa8566daf Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 15:32:18 -0500 Subject: [PATCH 12/71] Added some helper fields to Hunt formatted --- core/locale/en_CA/LC_MESSAGES/django.mo | Bin 337 -> 434 bytes core/locale/en_CA/LC_MESSAGES/django.po | 185 +++++++++++------- core/locale/ja_JP/LC_MESSAGES/django.mo | Bin 4248 -> 2914 bytes core/migrations/0021_hunt_middle_locations.py | 14 +- core/models.py | 30 ++- core/templates/core/base.html | 4 +- core/views/qr.py | 10 +- core/views/team.py | 2 +- 8 files changed, 156 insertions(+), 89 deletions(-) diff --git a/core/locale/en_CA/LC_MESSAGES/django.mo b/core/locale/en_CA/LC_MESSAGES/django.mo index 6c5906d1cd061dff54de8b533942893de34efc9e..5d3a7323185aeecba4ba0580f73a852a46437121 100644 GIT binary patch delta 200 zcmcb}w28U?o)F7a1|VPtVi_Pd0b*7l_5orLNC09kAWj5gP9V+!ViiUPhB6?{1H`j| zY>@a0APq!d05S^%m_UR=e0*wNN^w9@Dnn6fZenI$W?ni&Kv8~HYI2FL=R~J$Rj`2C0F8iKV3yZlOLt3Vx2h Yt_n7;zK)(g4vsD^L9W5Ulld8Y0R6ZPga7~l diff --git a/core/locale/en_CA/LC_MESSAGES/django.po b/core/locale/en_CA/LC_MESSAGES/django.po index b4692ff..68ce15a 100644 --- a/core/locale/en_CA/LC_MESSAGES/django.po +++ b/core/locale/en_CA/LC_MESSAGES/django.po @@ -8,89 +8,122 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-17 01:39+0000\n" +"POT-Creation-Date: 2023-12-03 14:46-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" +"Last-Translator: Jason Cameron \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: core/templates/core/base.html:22 -msgid "starts in:" +#: .\core\admin.py:14 +msgid "Set selected users as a Location Setter" msgstr "" -#: core/templates/core/base.html:23 -msgid "ends in:" +#: .\core\admin.py:25 +msgid "Set selected users as a Logic Puzzle Setter" msgstr "" -#: core/templates/core/base.html:24 +#: .\core\admin.py:117 +msgid "Link to Hint Page" +msgstr "" + +#: .\core\templates\core\base.html:22 +msgid "starts in" +msgstr "" + +#: .\core\templates\core\base.html:23 +msgid "__startsPost" +msgstr "" + +#: .\core\templates\core\base.html:24 +msgid " __endsPre" +msgstr "Hurry Hurry!" + +#: .\core\templates\core\base.html:25 +msgid "remaining" +msgstr "remaining" + +#: .\core\templates\core\base.html:26 msgid "scavenger hunt ended!" msgstr "" -#: core/templates/core/base.html:145 +#: .\core\templates\core\base.html:105 msgid "Home" msgstr "" -#: core/templates/core/base.html:152 +#: .\core\templates\core\base.html:112 msgid "Admin" msgstr "" -#: core/templates/core/base.html:158 +#: .\core\templates\core\base.html:117 msgid "current" msgstr "" -#: core/templates/core/base.html:163 core/templates/core/logout.html:6 +#: .\core\templates\core\base.html:121 .\core\templates\core\logout.html:6 msgid "Logout" msgstr "" -#: core/templates/core/credits.html:6 +#: .\core\templates\core\base.html:128 +msgid "Login" +msgstr "" + +#: .\core\templates\core\credits.html:6 msgid "Credits" msgstr "" -#: core/templates/core/credits.html:10 +#: .\core\templates\core\credits.html:10 msgid "credits" msgstr "" -#: core/templates/core/credits.html:14 -msgid "" -"\n" -"project manager: ApocalypseCalculator
\n" -"programming: nyiyui, JasonLovesDoggo, Jimmy Liu, cheollie, and Misheel " -"Batkhuu
\n" -"UI/UX design: cheollie
\n" -"support: project metropolis and " -"SAC
\n" +#: .\core\templates\core\credits.html:14 +msgid "project manager" msgstr "" -#: core/templates/core/credits.html:20 +#: .\core\templates\core\credits.html:19 +msgid "programming" +msgstr "" + +#: .\core\templates\core\credits.html:28 +msgid "content" +msgstr "" + +#: .\core\templates\core\credits.html:34 +msgid "UI/UX design" +msgstr "" + +#: .\core\templates\core\credits.html:41 msgid "hints" msgstr "" -#: core/templates/core/gate.html:6 core/templates/core/index.html:6 +#: .\core\templates\core\credits.html:48 +msgid "support" +msgstr "" + +#: .\core\templates\core\gate.html:6 .\core\templates\core\index.html:6 msgid "Scavenger Hunt" msgstr "" -#: core/templates/core/gate.html:11 +#: .\core\templates\core\gate.html:11 msgid "The hunt has not begun yet." msgstr "" -#: core/templates/core/gate.html:14 +#: .\core\templates\core\gate.html:14 #, python-format msgid "" "\n" "Please login.\n" msgstr "" -#: core/templates/core/index.html:11 +#: .\core\templates\core\index.html:10 #, python-format msgid "" "\n" "welcome, %(username)s!\n" msgstr "" -#: core/templates/core/index.html:23 +#: .\core\templates\core\index.html:23 #, python-format msgid "" "\n" @@ -99,23 +132,23 @@ msgid "" " " msgstr "" -#: core/templates/core/index.html:29 +#: .\core\templates\core\index.html:29 #, python-format msgid "" "\n" -" or, do you still want to invite team " +" or, would you like to invite more team " "members?\n" " " msgstr "" -#: core/templates/core/index.html:36 +#: .\core\templates\core\index.html:37 msgid "" "\n" " you're going solo\n" " " msgstr "" -#: core/templates/core/index.html:40 +#: .\core\templates\core\index.html:41 #, python-format msgid "" "\n" @@ -123,125 +156,127 @@ msgid "" " " msgstr "" -#: core/templates/core/index.html:48 core/templates/core/team_join.html:10 +#: .\core\templates\core\index.html:50 .\core\templates\core\team_join.html:10 msgid "join a team" msgstr "" -#: core/templates/core/index.html:51 core/templates/core/team_new.html:10 +#: .\core\templates\core\index.html:53 .\core\templates\core\team_new.html:10 msgid "make a team" msgstr "" -#: core/templates/core/index.html:57 +#: .\core\templates\core\index.html:60 msgid "go solo " msgstr "" -#: core/templates/core/logic_hints.html:8 -msgid "Logic Clues" -msgstr "" - -#: core/templates/core/logic_hints.html:13 -msgid "All of your logic clues so far" -msgstr "" - -#: core/templates/core/logout.html:10 core/templates/core/logout.html:16 +#: .\core\templates\core\logout.html:10 .\core\templates\core\logout.html:16 msgid "logout" msgstr "" -#: core/templates/core/qr.html:10 +#: .\core\templates\core\qr.html:10 msgid "Hint" msgstr "" -#: core/templates/core/qr.html:32 +#: .\core\templates\core\qr.html:33 msgid "your team found:" msgstr "" -#: core/templates/core/qr.html:36 +#: .\core\templates\core\qr.html:37 msgid "change" msgstr "" -#: core/templates/core/qr.html:44 +#: .\core\templates\core\qr.html:46 msgid "your first hint is:" msgstr "" -#: core/templates/core/qr.html:46 +#: .\core\templates\core\qr.html:48 msgid "your next hint is:" msgstr "" -#: core/templates/core/qr.html:68 -msgid "thank you for playing!" +#: .\core\templates\core\qr.html:72 +msgid "" +"Oh ho? What lieth there? Tis the light at the end of the tunnel!
" +"Congratulations valiant scavenger and thank you for playing!" msgstr "" -#: core/templates/core/team_invite.html:7 +#: .\core\templates\core\team_invite.html:7 msgid "Invite Members" msgstr "" -#: core/templates/core/team_invite.html:11 +#: .\core\templates\core\team_invite.html:11 msgid "invite members" msgstr "" -#: core/templates/core/team_invite.html:28 +#: .\core\templates\core\team_invite.html:33 #, python-format msgid "" "\n" -" either you can\n" -" ,\n" -" or let them scan the QR code below:\n" -" " +" you may either\n" +" ,\n" +" share via invite code %(code)s,\n" +" or allow them to scan the QR code below:\n" +" " msgstr "" -#: core/templates/core/team_join.html:6 +#: .\core\templates\core\team_join.html:6 msgid "Join a Team" msgstr "" -#: core/templates/core/team_join.html:14 +#: .\core\templates\core\team_join.html:14 msgid "either enter your team's join code" msgstr "" -#: core/templates/core/team_join.html:18 +#: .\core\templates\core\team_join.html:18 msgid "Join" msgstr "" -#: core/templates/core/team_join.html:20 +#: .\core\templates\core\team_join.html:20 msgid "or, scan your team's QR code" msgstr "" -#: core/templates/core/team_new.html:6 +#: .\core\templates\core\team_new.html:6 msgid "Make a Team" msgstr "" -#: core/templates/core/team_new.html:27 -msgid "make" +#: .\core\templates\core\team_new.html:24 +msgid "create" msgstr "" -#: core/views/qr.py:28 +#: .\core\views\qr.py:28 msgid "Please join a team or choose to go solo before getting a hint." msgstr "" -#: core/views/qr.py:46 +#: .\core\views\qr.py:66 msgid "Contest has not started yet." msgstr "" -#: core/views/team.py:23 -msgid "Since the hunt has already begun, switching teams is disallowed." +#: .\core\views\qr.py:71 +msgid "Contest has ended." +msgstr "" + +#: .\core\views\team.py:25 +msgid "" +"Since the hunt has already begun, switching teams is disallowed. If you need " +"to switch teams, please contact an admin." msgstr "" -#: core/views/team.py:40 +#: .\core\views\team.py:45 #, python-format msgid "Joined team %(team_name)s" msgstr "" -#: core/views/team.py:46 +#: .\core\views\team.py:51 #, python-format msgid "Team %(team_name)s is full." msgstr "" -#: core/views/team.py:63 +#: .\core\views\team.py:69 msgid "Since the hunt has already begun, making new teams is disallowed." msgstr "" -#: core/views/team.py:76 +#: .\core\views\team.py:85 #, python-format msgid "Made team %(team_name)s" msgstr "" diff --git a/core/locale/ja_JP/LC_MESSAGES/django.mo b/core/locale/ja_JP/LC_MESSAGES/django.mo index e894a2b456603d28f691cc44c80869ab9d0dca01..d9e955c4addfd8c766916811e71e52d85a6fcbcf 100644 GIT binary patch delta 1050 zcmXZaO-NKx6u|K_sWW5Rj9K|zX?~K>BC;$fidwWtQo=<=P1882eT*YSF5o=f|cfXpix#b0F>EB`yzuW!_o}%Bv;G;N-`rZfBaeq+r z{ImViwIV6~qmqVBT#K5p1-E0n?MIL?l0Z#x)jDpyk9&DOhx*_?Z*LEEj=hBV!R$a(e z+b**f%8Avz&;zHT`>3I-Y~<3f)u`gqQ>zDwD!T`E9cpOhIGe)9x=~>!@{XH8!SL*m zx7uSq`)cY#p=hip(HW0QD3nNsaIU%F^R| zOXFiQ|2nsr9XAERBj#lApqUDGG&t!gCzEwXae-pq zTB&Q?+1SjVk!HX=Fh=9F#e?;zX{%r z@mnB%?63G+1O5~21@D58K5!J=0v-iF1-=Q={J((sv43dZbSq=+7~c-!$1<8<0?EED zAnAV?yd4|`$v#7m$3e2+13wNP0#o1%;9cN(@T1@_z&pX;fgc9{46Z>;-_heWSac8G zcYwbIw}IQib|@9#1b9FAE0E%N>jxO);NL*<>+TOG>p4jNJqX?gZqwWelKyXiG~WR? zfFbxHa6ykRfM3V>Es*qYg7Z7U2&DPH0crg^AnCaUi)sBG_*)O&qsN1qUjxYw3ncx% z<_t)3=Jfa!_+^YQf^dob3B-^6UB6EuXjC^_L5lM?!Ow#}h%mCx;6~{wn>a`^Xg=kT z>a%`*77uInfJzzRRBnj@^BAUqCHJZCuiV~^#O!hi?mlGxln76yUqwDU>B4l&HRr837F z{y5Zf+j1t_xn*u{_X~z6(qjQ;nnqxxlW`!P2=uqri_|OJ#GtFQxHY&!$>7)=PsUeO~J>6?M`LY%(~mI*Tqd&x#S0yZSyI^!E%@P7`)(# zaZ+VDlU5-1hMo;wy~#)giWfz3On81zx6%7xi&*lw5JFDD@VOrtULedigr1us=aowV zsqa(3Bud{a1?oLg+~Z8#zV&n>yOr2?%K@9yi@@>pt0*q9EB)^>!x z@EoHkI{mg3>oegN>vx?%_(>`pH>jtBmqn0a{hlzbz-QYLY_=Uj*r3WaA5Id>9zm6< zKVc>rPda07qJ#~(c{dE$uz}*Dj?s-%|E#m7ir+{kpgwa2*F`Q-W2gw~MAYTDOT8(A zfVvZv2YWJXx8>w8ueF#A+Y^Ra#)^FCbRoqPw7?NlO1jT2pPQC%*r;=1{x=f*DJ#f9 zUH;t#j9ljwsTdD!JHv7X!^sPl(=p72o+l7Sy-DCP*(#DfWu2ew0n~>~I3`SWvaB8e zMh^Q-x0dcI))F@cPlftJbSTn{8do*A>Lq#w6}r(GK1>Q2=Ot|jeP}?YIx$fk!1f8% z^@uz#*+v;5Xk$uNv;W|0HhJe@Yf@QhzEb%fcSFa_vK>jwr3cOQ9_$B}i@M*@*Z-Bi zZ3BGIz^>hck3E{&C8+Mx!+zc}(+`DtKRx1Rd1}XF>0M&dqBZHQMj*0$i!G0`GfiZqEfrj(ITTc z8PCdSR!&D(UYmXUrDHNWdu8dV+L?vweq;bPz@dZ6o}b7V-abzjMn`3|NLh+2aFL~I z^DoQj)D4dnSy`Y}m8T)CHhaAK`hMEaWgJ((y;M8#0$C6rkkc_j7(FH9gEBfKqm$Q; z&&W7}Vb_k#R$rW5z7(}6SaGV<=BKL%PQ0t-iK4^R%g-o_7a;|iXr6y7jv7j5d>*-k zwQ8aE^HtdPe|Y$`o;gSZ?Hn{oQPpx_BAA*mzmHutde$?23q?z7emM zxWHa0I0(_TBj;5Q?Q58NYi1y8X~Hv(KrWIOKHeT zbVpRnH;NEcD^<0n>Dq~Ni2?O2*N;N<8d=|x%ti{&>fE&K8rA*iZnQLQG3@FO<10%u zwYm7(k!NVzSQTJc)vS<)(0W}o+-%eUOJde^1}VJm)&IDgX&dGAJZ%)o#?xx>zIuTl zBDW~pv5cRjT`PJC$;P6N^=}+oe(gtZ99!R+S-!lG$*%w7+yZ(;?@$dNk!OklHd^dk TH+b6;NUBY$-tQ)*OH2O+HEIRX diff --git a/core/migrations/0021_hunt_middle_locations.py b/core/migrations/0021_hunt_middle_locations.py index 7d37aa9..9ca1f56 100644 --- a/core/migrations/0021_hunt_middle_locations.py +++ b/core/migrations/0021_hunt_middle_locations.py @@ -4,15 +4,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('core', '0020_remove_hunt_form_in_ending_text_and_more'), + ("core", "0020_remove_hunt_form_in_ending_text_and_more"), ] operations = [ migrations.AddField( - model_name='hunt', - name='middle_locations', - field=models.ManyToManyField(blank=True, help_text='Possible locations that are not the start or end', related_name='hunt', to='core.qrcode'), + model_name="hunt", + name="middle_locations", + field=models.ManyToManyField( + blank=True, + help_text="Possible locations that are not the start or end", + related_name="hunt", + to="core.qrcode", + ), ), ] diff --git a/core/models.py b/core/models.py index c7039a4..57f3984 100644 --- a/core/models.py +++ b/core/models.py @@ -230,12 +230,40 @@ def __str__(self): return self.name @classmethod - def current_hunt(cls): + def lastest_hunt(cls) -> Hunt | None: + """ + Returns the latest hunt, ended or not. + :return: Hunt or None + """ + try: + Hunt.objects.filter(start__lte=timezone.now()).order_by("start").last() + except IndexError: + return None + + @classmethod + def current_hunt(cls) -> Hunt | None: try: return cls.objects.get(start__lte=timezone.now(), end__gte=timezone.now()) except cls.DoesNotExist: return None + @classmethod + def next_hunt(cls) -> Hunt | None: + try: + return ( + cls.objects.filter(start__gte=timezone.now()).order_by("start").first() + ) + except IndexError: + return None + + @property + def started(self): + return self.start < timezone.now() + + @property + def ended(self): + return self.end < timezone.now() + def clean(self): """ Due to how this was designed, it is not possible to have multiple hunts running at the same time. diff --git a/core/templates/core/base.html b/core/templates/core/base.html index f0e069e..b5a8a29 100644 --- a/core/templates/core/base.html +++ b/core/templates/core/base.html @@ -20,8 +20,8 @@ {{ START.timestamp }} * 1000, {{ END.timestamp }} * 1000, "{% translate 'starts in' %}", - "{% translate '__startsPost' %}", - "{% translate '__endsPre' %}", + "{% translate '' %}", + "{% translate '' %}", "{% translate 'remaining' %}", "{% translate 'scavenger hunt ended!' %}", ) diff --git a/core/views/qr.py b/core/views/qr.py index 2cb0e51..16856c4 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -51,24 +51,24 @@ def wrapped(*args, **kwargs): request = args[0] if any( [ + hunt_.started and not hunt_.ended, request.user.has_perm("core.view_before_start"), - (hunt_.start < datetime.datetime.now() < hunt_.end), - hunt_.early_access_users.filter(user=request.user).exists(), + hunt_.early_access_users.contains(request.user), request.user.is_superuser, ] ): return f(*args, **kwargs) # if they DON'T have perms and the hunt hasn't started - if hunt_.start > datetime.datetime.now(): + if hunt_.ended: messages.error( request, - _("Contest has not started yet."), + _("Contest has ended."), ) else: messages.error( request, - _("Contest has ended."), + _("Contest has not started yet."), ) return redirect(reverse("index")) diff --git a/core/views/team.py b/core/views/team.py index ba737a2..3d820c0 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -18,7 +18,7 @@ @require_http_methods(["GET", "POST"]) def join(request): hunt_ = Hunt.current_hunt() - if hunt_.start < datetime.datetime.now() and request.user.team is not None: + if hunt_.started and request.user.team is not None: messages.error( request, _( From 9c653749c89dc9a4c92a13fe6903eb6b38a715d1 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 15:36:18 -0500 Subject: [PATCH 13/71] reamed after_start to during_hunt to reflect it's internal changes --- core/views/puzzle.py | 4 ++-- core/views/qr.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/views/puzzle.py b/core/views/puzzle.py index 6c2defb..c352d84 100644 --- a/core/views/puzzle.py +++ b/core/views/puzzle.py @@ -2,14 +2,14 @@ from django.shortcuts import render from django.views.decorators.http import require_http_methods -from .qr import team_required, after_start +from .qr import team_required, during_hunt from ..models import LogicPuzzleHint @login_required @require_http_methods(["GET", "POST"]) @team_required -@after_start +@during_hunt def logic_clues(request): context = dict(logic_clues=LogicPuzzleHint.get_clues(request.user.team)) return render(request, "core/logic_hints.html", context=context) diff --git a/core/views/qr.py b/core/views/qr.py index 16856c4..bafbbbb 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -33,7 +33,7 @@ def wrapped(*args, **kwargs): return wrapped -def after_start(f): +def during_hunt(f): """ Decorator for views that checks that the hunt has started. @@ -78,7 +78,7 @@ def wrapped(*args, **kwargs): @login_required @require_http_methods(["GET", "POST"]) @team_required -@after_start +@during_hunt def qr(request, key): context = dict(first=False) context["qr"] = qr = get_object_or_404(QrCode, key=key) @@ -101,7 +101,7 @@ def qr(request, key): @login_required @require_http_methods(["GET", "POST"]) @team_required -@after_start +@during_hunt def qr_first(request): context = dict(first=True) # check if the user is on the first qr code @@ -118,7 +118,7 @@ def qr_first(request): @login_required @require_http_methods(["GET", "POST"]) @team_required -@after_start +@during_hunt def qr_current(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) @@ -134,7 +134,7 @@ def qr_current(request): @login_required @require_http_methods(["GET", "POST"]) @team_required -@after_start +@during_hunt def qr_catalog(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) @@ -190,7 +190,7 @@ def __next__(self): @login_required @require_http_methods(["GET"]) @team_required -@after_start +@during_hunt def qr_signal(request): s = StreamingHttpResponse( SignalStream(signal=global_notifs, pk=request.user.team.id), From f643162b13c40b4804b8d3b9f61ae118cc2ecfad Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 15:39:25 -0500 Subject: [PATCH 14/71] renamed Hunt.early_access_users to Hunt.testers --- core/models.py | 6 +++--- core/views/qr.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/models.py b/core/models.py index 57f3984..5b9b252 100644 --- a/core/models.py +++ b/core/models.py @@ -210,10 +210,10 @@ class Hunt(models.Model): help_text="Possible locations that are not the start or end", blank=True, ) - early_access_users = models.ManyToManyField( + testers = models.ManyToManyField( User, - related_name="early_access_users", - help_text="Users that can access this hunt before it starts", + related_name="testers", + help_text="Users that can access this hunt before it starts as well as after", blank=True, ) form_url = models.URLField( diff --git a/core/views/qr.py b/core/views/qr.py index bafbbbb..25bf01a 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -52,8 +52,8 @@ def wrapped(*args, **kwargs): if any( [ hunt_.started and not hunt_.ended, - request.user.has_perm("core.view_before_start"), - hunt_.early_access_users.contains(request.user), + request.user.has_perm("core.view_before_start") and not hunt_.ended, + hunt_.testers.contains(request.user), request.user.is_superuser, ] ): From 1aec86c111ac1a1ceccf94d692212bf480b29074 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 15:46:43 -0500 Subject: [PATCH 15/71] add manual migration (untested) fix docs. --- ...ve_hunt_early_access_users_hunt_testers.py | 28 +++++++++++++++++++ core/views/qr.py | 8 +++--- 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 core/migrations/0022_remove_hunt_early_access_users_hunt_testers.py diff --git a/core/migrations/0022_remove_hunt_early_access_users_hunt_testers.py b/core/migrations/0022_remove_hunt_early_access_users_hunt_testers.py new file mode 100644 index 0000000..f830316 --- /dev/null +++ b/core/migrations/0022_remove_hunt_early_access_users_hunt_testers.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.13 on 2023-12-03 20:44 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0021_hunt_middle_locations"), + ] + + operations = [ + migrations.RenameField( + model_name="hunt", + old_name="early_access_users", + new_name="testers", + ), + migrations.AlterField( + model_name="hunt", + name="testers", + field=models.ManyToManyField( + blank=True, + help_text="Users that can access this hunt before it starts as well as after", + related_name="testers", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/core/views/qr.py b/core/views/qr.py index 1d4fe15..2337bbe 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -35,12 +35,12 @@ def wrapped(*args, **kwargs): def during_hunt(f): """ - Decorator for views that checks that the hunt has started. + Decorator for views that checks that the hunt is currently active. User can access the view if they meet ANY of the following conditions: - - They have the view_before_start permission - - The hunt has started - - They are on the early access list for that hunt + - They have the view_before_start permission and the hunt hasn't ended + - The hunt has started and hasn't ended + - They are on the testers list for that hunt - They are a superuser """ From 86ca83373a3dcd2f202ce3ab3d3d0486ccd2fa35 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 22:05:29 -0600 Subject: [PATCH 16/71] add scavenger.iml to gitignore since I have different configs on win/linux --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 098cd58..4f9dc78 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ __pycache__ /venv .idea/workspace.xml .idea/misc.xml - +.idea/scavenger.iml ``` From 29a39ea8c11d531531b6a368abd57920e42679e6 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 22:19:19 -0600 Subject: [PATCH 17/71] Fixed incorrect ternary op added the logic for hunts in context_processors.py add scavenger.iml to gitignore since I have different configs on win/linux --- .idea/scavenger.iml | 2 +- core/context_processors.py | 26 +++++++++++++++++++------- core/static/core/countdown.js | 4 ++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.idea/scavenger.iml b/.idea/scavenger.iml index c370e70..7a335b4 100644 --- a/.idea/scavenger.iml +++ b/.idea/scavenger.iml @@ -14,7 +14,7 @@
- + diff --git a/core/context_processors.py b/core/context_processors.py index a2eeb47..dc99249 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,18 +1,30 @@ import datetime from core.models import Hunt -from django.conf import settings from django.utils import timezone def start(request): hunt = Hunt.current_hunt() now = timezone.now() + if hunt is not None: + event_start = hunt.start + event_end = hunt.end + elif Hunt.next_hunt() is not None: + hunt = Hunt.next_hunt() + event_start = hunt.start + event_end = hunt.end + else: + print("WARNING: No hunt is scheduled") + print("Please add a hunt in the future to the database") + event_start = timezone.now() + datetime.timedelta(weeks=75) + event_end = event_start + datetime.timedelta(hours=3) + return dict( - START=(start := hunt.start), - START_BEFORE=start > now, - START_UNTIL=start - now, - END=(end := hunt.end), - END_BEFORE=end > now, - END_UNTIL=end - now, + START=event_start, + START_BEFORE=event_start > now, + START_UNTIL=event_start - now, + END=event_end, + END_BEFORE=event_end > now, + END_UNTIL=event_end - now, ) diff --git a/core/static/core/countdown.js b/core/static/core/countdown.js index 1af3968..5373540 100644 --- a/core/static/core/countdown.js +++ b/core/static/core/countdown.js @@ -49,8 +49,8 @@ const newCalculateTime = (start, end, startsPre, startsPost, endsPre, endsPost, startsPost = startsPost.startsWith('__') ? '' : startsPost endsPre = endsPre.endsWith('__') ? '' : endsPre endsPost = endsPost.endsWith('__') ? '' : endsPost - eTextPre.innerText = started ? (finished ? ended : endsPre) : startsPre - eTextPost.innerText = started ? (finished ? ended : endsPost) : startsPost + eTextPre.innerText = started ? startsPre : (finished ? ended : endsPre) + eTextPost.innerText = started ? startsPost : (finished ? ended : endsPost) } export default newCalculateTime From 2c4ac15383c676efdecdf9afc0ed4b13c966ccb6 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 22:22:08 -0600 Subject: [PATCH 18/71] adds black. --- poetry.lock | 131 +++++++++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + 2 files changed, 129 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 339a307..aaacb38 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "asgiref" @@ -59,6 +59,48 @@ files = [ [package.extras] tzdata = ["tzdata"] +[[package]] +name = "black" +version = "23.11.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -233,6 +275,31 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + [[package]] name = "cryptography" version = "41.0.7" @@ -373,6 +440,54 @@ files = [ {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "4.0.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + [[package]] name = "pycparser" version = "2.21" @@ -421,6 +536,17 @@ dev = ["build", "flake8"] doc = ["sphinx"] test = ["pytest", "pytest-cov"] +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "typing-extensions" version = "4.8.0" @@ -462,5 +588,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "9f7ca6a088c6b910bc55cc7a9368ce083a9279353d61dffbfb277e44472113bf" - +content-hash = "94092c6d189cacb49a082d696195c49bcb804654f76ed0f434eaf6b8cbaebb9b" diff --git a/pyproject.toml b/pyproject.toml index af8bb4f..d963f5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ fontawesomefree = "^6.2.1" mistune = "^3.0.2" django-ckeditor = "^6.0.0" django-impersonate = "^1.9.2" +black = "^23.11.0" [tool.poetry.dev-dependencies] [build-system] From 4e0506e1f04870a5f7c34e155687c8f4e113f289 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sun, 3 Dec 2023 23:52:28 -0600 Subject: [PATCH 19/71] added dynamic hunt naming (dw i'll clean up chunky context) Slight changes to translations --- core/templates/core/base.html | 2 +- core/templates/core/index.html | 2 +- core/views/index.py | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/templates/core/base.html b/core/templates/core/base.html index b5a8a29..59e9775 100644 --- a/core/templates/core/base.html +++ b/core/templates/core/base.html @@ -21,7 +21,7 @@ {{ END.timestamp }} * 1000, "{% translate 'starts in' %}", "{% translate '' %}", - "{% translate '' %}", + "{% translate 'Only' %}", "{% translate 'remaining' %}", "{% translate 'scavenger hunt ended!' %}", ) diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 9afc798..e5c0b3c 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -21,7 +21,7 @@ {% if request.user.in_team and not START_BEFORE %} {% url 'qr_first' as first_url %} {% blocktranslate %} - the hunt has started! go decrypt your first hint! + {{ hunt_name }} has started! go decrypt your first hint! {% endblocktranslate %}
{% if not request.user.team.solo %} diff --git a/core/views/index.py b/core/views/index.py index db5a8f2..5df8582 100644 --- a/core/views/index.py +++ b/core/views/index.py @@ -3,15 +3,18 @@ from django.contrib.auth.models import Group from django.views.decorators.http import require_http_methods -from ..models import User +from ..models import User, Hunt @require_http_methods(["GET"]) def index(q): + hunt = Hunt.current_hunt() or Hunt.next_hunt() + context = dict(first=False) + context["hunt_name"] = hunt or "The hunt" return render( q, "core/index.html" if q.user.is_authenticated else "core/gate.html", - {}, + context, ) From 5dc0256e22981b4aa1af534162e082d02a1dcae4 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 12:21:43 -0500 Subject: [PATCH 20/71] Updated usages to support joining a team before the event started feat: added a @upcoming_hunt_required decorator that confirms .upcoming or .current exists (todo cache?) --- core/context_processors.py | 5 ++--- core/views/qr.py | 24 ++++++++++++++++++++++-- core/views/team.py | 17 +++++++++++------ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index dc99249..ade5f7f 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -5,9 +5,8 @@ def start(request): - hunt = Hunt.current_hunt() now = timezone.now() - if hunt is not None: + if hunt := Hunt.current_hunt() is not None: event_start = hunt.start event_end = hunt.end elif Hunt.next_hunt() is not None: @@ -17,7 +16,7 @@ def start(request): else: print("WARNING: No hunt is scheduled") print("Please add a hunt in the future to the database") - event_start = timezone.now() + datetime.timedelta(weeks=75) + event_start = now + datetime.timedelta(weeks=75) event_end = event_start + datetime.timedelta(hours=3) return dict( diff --git a/core/views/qr.py b/core/views/qr.py index 2337bbe..c47b004 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -32,6 +32,19 @@ def wrapped(*args, **kwargs): return wrapped +def upcoming_hunt_required(f): + @wraps(f) + def wrapped(*args, **kwargs): + request = args[0] + if Hunt.current_hunt() is None and Hunt.next_hunt() is None: + messages.error( + request, + _("No hunts are in the database, please contact an admin.") + ) + return redirect(reverse("index")) + return f(*args, **kwargs) # todo: maybe return the hunt object to be used in the view? (more efficient) + + return wrapped def during_hunt(f): """ @@ -45,10 +58,17 @@ def during_hunt(f): """ @wraps(f) + @upcoming_hunt_required def wrapped(*args, **kwargs): - hunt_ = Hunt.current_hunt() - + hunt_ = Hunt.current_hunt() or Hunt.next_hunt() request = args[0] + if hunt_ is None: + messages.error( + request, + _("No hunt is currently active."), + ) + return redirect(reverse("index")) + if any( [ hunt_.started and not hunt_.ended, diff --git a/core/views/team.py b/core/views/team.py index 3d820c0..091a971 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -11,13 +11,14 @@ from ..models import Team, Invite, generate_invite_code, Hunt from ..forms import TeamJoinForm, TeamMakeForm -from .qr import team_required +from .qr import team_required, upcoming_hunt_required @login_required @require_http_methods(["GET", "POST"]) +@upcoming_hunt_required def join(request): - hunt_ = Hunt.current_hunt() + hunt_ = Hunt.current_hunt() or Hunt.next_hunt() if hunt_.started and request.user.team is not None: messages.error( request, @@ -61,9 +62,10 @@ def join(request): @login_required @require_http_methods(["GET", "POST"]) +@upcoming_hunt_required def make(request): - hunt_ = Hunt.current_hunt() - if hunt_.start < datetime.datetime.now() and request.user.team is not None: + hunt_ = Hunt.current_hunt() or Hunt.next_hunt() + if hunt_.started and request.user.team is not None: messages.error( request, _("Since the hunt has already begun, making new teams is disallowed."), @@ -73,7 +75,7 @@ def make(request): form = TeamMakeForm(request.POST) if form.is_valid(): raw: Team = form.save(commit=False) - raw.hunt = Hunt.current_hunt() + raw.hunt = Hunt.current_hunt() or Hunt.next_hunt() raw.save() request.user.team = raw request.user.save() @@ -92,9 +94,11 @@ def make(request): @login_required +@upcoming_hunt_required def solo(q: HttpRequest): + hunt_ = Hunt.current_hunt() or Hunt.next_hunt() team = Team.objects.create( - solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team" + solo=True, hunt=hunt_, name=f"{q.user.username}'s Solo Team" ) q.user.team = team q.user.save() @@ -104,6 +108,7 @@ def solo(q: HttpRequest): @login_required @require_http_methods(["GET"]) @team_required +@upcoming_hunt_required # redundant def invite(q): invites = Invite.objects.filter(team=q.user.team).values_list("code", flat=True) if invites.count() == 0: From c3c008a6bd7384930f046cb348c479a1dcf5f2ba Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Dec 2023 17:22:53 +0000 Subject: [PATCH 21/71] format --- core/views/qr.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index c47b004..b8b1805 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -32,20 +32,23 @@ def wrapped(*args, **kwargs): return wrapped + def upcoming_hunt_required(f): @wraps(f) def wrapped(*args, **kwargs): request = args[0] if Hunt.current_hunt() is None and Hunt.next_hunt() is None: messages.error( - request, - _("No hunts are in the database, please contact an admin.") + request, _("No hunts are in the database, please contact an admin.") ) return redirect(reverse("index")) - return f(*args, **kwargs) # todo: maybe return the hunt object to be used in the view? (more efficient) + return f( + *args, **kwargs + ) # todo: maybe return the hunt object to be used in the view? (more efficient) return wrapped + def during_hunt(f): """ Decorator for views that checks that the hunt is currently active. From 33b493c371c86abae2011a5da970ce8af0d1b974 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 13:35:43 -0500 Subject: [PATCH 22/71] docs: improved docs for starting, middle and ending locations. docs: added docs for how alg works based fixed alg for the new Hunt system. added some warnings so ppl (admins) don't get confused allows start/end to be optional --- core/admin.py | 24 ++++++++++++++++++++++-- core/models.py | 45 ++++++++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/core/admin.py b/core/admin.py index 960cf81..215583b 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,4 +1,4 @@ -from django.contrib import admin +from django.contrib import admin, messages from django.contrib.auth.admin import UserAdmin as UserAdmin_ from django.contrib.auth.models import Group from django.utils.safestring import mark_safe @@ -120,6 +120,26 @@ def url(self, qr): return "" +class HuntAdmin(admin.ModelAdmin): + def save_model(self, request, obj, form, change): + + # todo: add warning if admin attempts to change path length or middle locations when hunt has started (will basically re-randomize the hunt (I THINK)) + + #extras = 1 if obj.starting_location else 0 + #extras += 1 if obj.ending_location else 0 + + if obj.path_length > obj.middle_locations.count(): + messages.warning( + request, + "The path length is longer than the amount of locations. " + "If you do not increase middle locations or decrease path length, " + "the path length will be reduced to the amount of locations.", + ) + # raise ValidationError( + # "The path length is longer than the amount of locations. Please increase the amount of locations or decrease the path length." + # ) + super().save_model(request, obj, form, change) + class UserAdmin(UserAdmin_): readonly_fields = ( "username", @@ -148,5 +168,5 @@ class UserAdmin(UserAdmin_): admin.site.register(Team, TeamAdmin) admin.site.register(QrCode, QrCodeAdmin) admin.site.register(LogicPuzzleHint, LogicPuzzleAdmin) -admin.site.register(Hunt) +admin.site.register(Hunt, HuntAdmin) admin.site.register(Invite) diff --git a/core/models.py b/core/models.py index 5b9b252..3d08c79 100644 --- a/core/models.py +++ b/core/models.py @@ -81,16 +81,26 @@ def codes(cls, team: "Team"): @classmethod def code_pks(cls, team: "Team"): - r = random.Random(team.id) - pks = [a["pk"] for a in QrCode.objects.all().values("pk")] - pks = pks[: team.hunt.path_length] + """ + Returns the QR codes that the team has to find in order. + The algorithm behind this system is as follows: + 1. Get the hunt that the team belongs to + 2. Get the QR codes that are in the middle of the path for the specified hunt + 3. Shuffle QR codes (using the team id appended to hunt id as the key) + 4. Get the first n QR codes (n being the path length) + 5. Add the starting and ending QR codes to list + 6. Return the list + """ + hunt: Hunt = team.hunt + r = random.Random(str(hunt.id) + str(team.id)) + hunt_codes = hunt.middle_locations.all() + pks = [a["pk"] for a in hunt_codes.values("pk")] r.shuffle(pks) - if isinstance((pk := team.hunt.ending_location_id), int): - i = pks.index(pk) if pk in pks else r.randrange(0, len(pks)) - pks = pks[:i] + pks[i + 1 :] + [pk] - if isinstance((pk := team.hunt.starting_location_id), int): - i = pks.index(pk) if pk in pks else r.randrange(0, len(pks)) - pks = [pk] + pks[:i] + pks[i + 1 :] + pks = pks[: hunt.path_length] + if hunt_end := hunt.ending_location: + pks.append(hunt_end.id) + if hunt_start := hunt.starting_location: + pks.insert(0, hunt_start.id) return pks def hint(self, team: "Team"): @@ -195,14 +205,23 @@ class Hunt(models.Model): default=4, help_text="Max Team size" ) path_length = models.PositiveSmallIntegerField( - default=15, - help_text="Length of the path: The amount of codes each time will have to find before the end.", + default=13, + help_text="Length of the path: The amount of codes each time will have to find before the end. (not including start/end)", ) starting_location = models.ForeignKey( - QrCode, on_delete=models.PROTECT, related_name="starting_location" + QrCode, + on_delete=models.PROTECT, + related_name="starting_location", + blank=True, + null=True, + help_text="(Optional) A specified starting location for the hunt. All teams will have to scan this QR code as their first.", ) ending_location = models.ForeignKey( - QrCode, on_delete=models.PROTECT, related_name="ending_location" + QrCode, + on_delete=models.PROTECT, + related_name="ending_location", + blank=True, + help_text="(Optional) A specified ending location for the hunt. All teams will get as their last location.", ) middle_locations = models.ManyToManyField( QrCode, From 665450313ec563c1444ca380764a62dd7469cc7b Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Dec 2023 18:36:40 +0000 Subject: [PATCH 23/71] format --- core/admin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/admin.py b/core/admin.py index 215583b..3fb86e3 100644 --- a/core/admin.py +++ b/core/admin.py @@ -122,12 +122,11 @@ def url(self, qr): class HuntAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): - # todo: add warning if admin attempts to change path length or middle locations when hunt has started (will basically re-randomize the hunt (I THINK)) - - #extras = 1 if obj.starting_location else 0 - #extras += 1 if obj.ending_location else 0 - + + # extras = 1 if obj.starting_location else 0 + # extras += 1 if obj.ending_location else 0 + if obj.path_length > obj.middle_locations.count(): messages.warning( request, @@ -140,6 +139,7 @@ def save_model(self, request, obj, form, change): # ) super().save_model(request, obj, form, change) + class UserAdmin(UserAdmin_): readonly_fields = ( "username", From a42184a0fb52bc033544afc1f6b3307968e08630 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 13:49:45 -0500 Subject: [PATCH 24/71] fix: assigning hunt as bool --- core/context_processors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/context_processors.py b/core/context_processors.py index ade5f7f..db16049 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -6,7 +6,7 @@ def start(request): now = timezone.now() - if hunt := Hunt.current_hunt() is not None: + if (hunt := Hunt.current_hunt()) is not None: event_start = hunt.start event_end = hunt.end elif Hunt.next_hunt() is not None: From 29f0dae09913c4466735962d27b59ef5675e2a9d Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 13:52:52 -0500 Subject: [PATCH 25/71] remove useless assignment --- core/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/admin.py b/core/admin.py index 3fb86e3..c42e9eb 100644 --- a/core/admin.py +++ b/core/admin.py @@ -113,7 +113,7 @@ def url(self, qr): if qr.id: return format_html( mark_safe('{}'), - (url := reverse("qr", kwargs=dict(key=qr.key))), + reverse("qr", kwargs=dict(key=qr.key)), _("Link to Hint Page"), ) else: From 49d7986e0c6025a2cebd96bbfe67dd1d03b72b63 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 14:57:22 -0500 Subject: [PATCH 26/71] remove useless assignment --- core/templates/core/qr.html | 2 +- core/views/qr.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/templates/core/qr.html b/core/templates/core/qr.html index 55d4dac..5f38983 100644 --- a/core/templates/core/qr.html +++ b/core/templates/core/qr.html @@ -65,7 +65,7 @@ {% endif %} {% else %} - {% ending_block qr.hunt %} + {% ending_block request.user.team.hunt %} You found Derek! Now, solve the entirety of the logic puzzle to find which of the 5 SAC members in question this locker belongs to here! Oh, and close the locker, a dead Derek smells bad. {% translate 'Oh ho? What lieth there? Tis the light at the end of the tunnel!
Congratulations valiant scavenger and thank you for playing!' %} {% endif %} diff --git a/core/views/qr.py b/core/views/qr.py index b8b1805..fa9f01b 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -106,7 +106,6 @@ def qr(request, key): context = dict(first=False) context["qr"] = qr = get_object_or_404(QrCode, key=key) context["qr"]: QrCode - context["hunt"]: Hunt = qr.hunt codes = QrCode.code_pks(request.user.team) if qr.id not in codes: context["offpath"] = True @@ -131,7 +130,7 @@ def qr_first(request): # if request.user.team.current_qr_i != 0: # messages.error(request, _("You are not on the first QR code.")) # return redirect(reverse("qr_current")) - context["qr"] = QrCode.codes(request.user.team)[0] + context["qr"] = QrCode.codes(request.user.team).first() codes = QrCode.code_pks(request.user.team) context["nextqr"] = QrCode.objects.get(id=codes[0]) context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) @@ -145,10 +144,9 @@ def qr_first(request): def qr_current(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[request.user.team.current_qr_i] - codes = QrCode.code_pks(request.user.team) + context["qr"] = codes = QrCode.codes(request.user.team)[request.user.team.current_qr_i] context["nextqr"] = ( - None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) + None if codes.count() <= (j := i + 1) else codes[j] ) context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) return render(request, "core/qr.html", context=context) From 13dc668397b1f89184a7de09772bc556e73430d3 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 15:14:10 -0500 Subject: [PATCH 27/71] fix typo --- core/templates/core/qr.html | 1 - 1 file changed, 1 deletion(-) diff --git a/core/templates/core/qr.html b/core/templates/core/qr.html index 5f38983..e3a8077 100644 --- a/core/templates/core/qr.html +++ b/core/templates/core/qr.html @@ -66,7 +66,6 @@ {% endif %} {% else %} {% ending_block request.user.team.hunt %} - You found Derek! Now, solve the entirety of the logic puzzle to find which of the 5 SAC members in question this locker belongs to here! Oh, and close the locker, a dead Derek smells bad. {% translate 'Oh ho? What lieth there? Tis the light at the end of the tunnel!
Congratulations valiant scavenger and thank you for playing!' %} {% endif %} From 56ec5cdcd049f8de091d2f543d1d0c9cc945b9d8 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 15:30:08 -0500 Subject: [PATCH 28/71] fix typo --- core/templatetags/qr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templatetags/qr.py b/core/templatetags/qr.py index 21b5fd7..9a7fda2 100644 --- a/core/templatetags/qr.py +++ b/core/templatetags/qr.py @@ -27,7 +27,7 @@ def ending_block(hunt): return format_html( hunt.ending_text.replace( match.group(0), - '{}'.format(hunt.ending_form, ending_text), + '{}'.format(hunt.form_url, ending_text), ) ) return hunt.ending_text From a479ae5b8e99ed0a748eccb07a9189e27bac1ca7 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 15:31:29 -0500 Subject: [PATCH 29/71] fix change to list --- core/views/qr.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index fa9f01b..e74cc00 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -144,9 +144,10 @@ def qr_first(request): def qr_current(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) - context["qr"] = codes = QrCode.codes(request.user.team)[request.user.team.current_qr_i] + codes = QrCode.codes(request.user.team) + context["qr"] = codes[i] context["nextqr"] = ( - None if codes.count() <= (j := i + 1) else codes[j] + None if len(codes) <= (j := i + 1) else codes[j] ) context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) return render(request, "core/qr.html", context=context) @@ -159,7 +160,7 @@ def qr_current(request): def qr_catalog(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] + context["qr"] = QrCode.codes(request.user.team).all()[: request.user.team.current_qr_i] return render(request, "core/qr_catalog.html", context=context) From 5114158853901bc0f702b44f6e30e61e7161c133 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Dec 2023 21:09:12 +0000 Subject: [PATCH 30/71] format --- core/views/qr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index e74cc00..8e2535a 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -146,9 +146,7 @@ def qr_current(request): context = dict(first=i == 0, current=True) codes = QrCode.codes(request.user.team) context["qr"] = codes[i] - context["nextqr"] = ( - None if len(codes) <= (j := i + 1) else codes[j] - ) + context["nextqr"] = None if len(codes) <= (j := i + 1) else codes[j] context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) return render(request, "core/qr.html", context=context) @@ -160,7 +158,9 @@ def qr_current(request): def qr_catalog(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team).all()[: request.user.team.current_qr_i] + context["qr"] = QrCode.codes(request.user.team).all()[ + : request.user.team.current_qr_i + ] return render(request, "core/qr_catalog.html", context=context) From 3fc3d801d0d19fe7b06366e881ce00b60e7aa838 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 16:40:55 -0500 Subject: [PATCH 31/71] what --- core/templates/core/qr.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/templates/core/qr.html b/core/templates/core/qr.html index e3a8077..5e2d5c4 100644 --- a/core/templates/core/qr.html +++ b/core/templates/core/qr.html @@ -68,6 +68,6 @@ {% ending_block request.user.team.hunt %} {% translate 'Oh ho? What lieth there? Tis the light at the end of the tunnel!
Congratulations valiant scavenger and thank you for playing!' %} {% endif %} - + {% endif %} {% endblock %} From a6f64fc619f583d813611cfc189bc6468954202d Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 16:41:53 -0500 Subject: [PATCH 32/71] typehinting --- core/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index 3d08c79..38d0483 100644 --- a/core/models.py +++ b/core/models.py @@ -2,10 +2,12 @@ import random import secrets +from typing import List from django.contrib.auth.models import AbstractUser from django.core.exceptions import ValidationError from django.db import models +from django.db.models import QuerySet from django.db.models.signals import pre_save from django.dispatch import receiver from django.utils import timezone @@ -75,7 +77,7 @@ def __str__(self): return f"{self.id} {self.short or self.location}" @classmethod - def codes(cls, team: "Team"): + def codes(cls, team: "Team") -> List[QrCode]: pks = QrCode.code_pks(team) return [QrCode.objects.get(id=a) for a in pks] From 8de9654bcaa325cd4a39e82412a117e33f08b448 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 16:45:03 -0500 Subject: [PATCH 33/71] fix reference to old/new Queryset --- core/views/qr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/views/qr.py b/core/views/qr.py index 8e2535a..177bcac 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -158,7 +158,7 @@ def qr_current(request): def qr_catalog(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team).all()[ + context["qr"] = QrCode.codes(request.user.team)[ : request.user.team.current_qr_i ] return render(request, "core/qr_catalog.html", context=context) From bfa56b2a4317d9262ba2e14d454489b051badb84 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 16:46:25 -0500 Subject: [PATCH 34/71] little TODO --- core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index 38d0483..589c1b3 100644 --- a/core/models.py +++ b/core/models.py @@ -77,7 +77,7 @@ def __str__(self): return f"{self.id} {self.short or self.location}" @classmethod - def codes(cls, team: "Team") -> List[QrCode]: + def codes(cls, team: "Team") -> List[QrCode]: # todo: have this return an ordered QuerySet instead of a list pks = QrCode.code_pks(team) return [QrCode.objects.get(id=a) for a in pks] From 54118710d9dc6433abab59903669d6b48c1ba6a6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Dec 2023 21:47:26 +0000 Subject: [PATCH 35/71] format --- core/models.py | 4 +++- core/views/qr.py | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/models.py b/core/models.py index 589c1b3..aae8ec1 100644 --- a/core/models.py +++ b/core/models.py @@ -77,7 +77,9 @@ def __str__(self): return f"{self.id} {self.short or self.location}" @classmethod - def codes(cls, team: "Team") -> List[QrCode]: # todo: have this return an ordered QuerySet instead of a list + def codes( + cls, team: "Team" + ) -> List[QrCode]: # todo: have this return an ordered QuerySet instead of a list pks = QrCode.code_pks(team) return [QrCode.objects.get(id=a) for a in pks] diff --git a/core/views/qr.py b/core/views/qr.py index 177bcac..fa7f8e2 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -158,9 +158,7 @@ def qr_current(request): def qr_catalog(request): i = request.user.team.current_qr_i context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[ - : request.user.team.current_qr_i - ] + context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] return render(request, "core/qr_catalog.html", context=context) From afa8ed8af5fd6492785f88081b847cfb461599f8 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 17:10:35 -0500 Subject: [PATCH 36/71] added checking for skipping --- core/templates/core/qr.html | 6 +++++- core/views/qr.py | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/templates/core/qr.html b/core/templates/core/qr.html index 5e2d5c4..4f7043d 100644 --- a/core/templates/core/qr.html +++ b/core/templates/core/qr.html @@ -69,5 +69,9 @@ {% translate 'Oh ho? What lieth there? Tis the light at the end of the tunnel!
Congratulations valiant scavenger and thank you for playing!' %} {% endif %} - {% endif %} + {% else %} { + +{% translate 'Incorrect QR code, please read your hint again or contact an organizer' %} + +{% endif %} {% endblock %} diff --git a/core/views/qr.py b/core/views/qr.py index 177bcac..ff08d0d 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -104,10 +104,14 @@ def wrapped(*args, **kwargs): @during_hunt def qr(request, key): context = dict(first=False) - context["qr"] = qr = get_object_or_404(QrCode, key=key) context["qr"]: QrCode + context["qr"] = qr = get_object_or_404(QrCode, key=key) codes = QrCode.code_pks(request.user.team) - if qr.id not in codes: + if qr.id != codes[request.user.team.current_qr_i]: + """ + Either the user skipped ahead (is on path) or they found a random qr code (not on path) + Either way... not allowed + """ context["offpath"] = True return render(request, "core/qr.html", context=context) i = codes.index(qr.id) @@ -115,8 +119,7 @@ def qr(request, key): None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) ) context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) - # TODO: check if they skipped? - request.user.team.update_current_qr_i(i) + request.user.team.update_current_qr_i(i + 1) return render(request, "core/qr.html", context=context) From 3f2d8a43bac75193ebcf10799d1f7e629465c2d7 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 17:39:10 -0500 Subject: [PATCH 37/71] allow user to reload the page lol --- core/views/qr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/views/qr.py b/core/views/qr.py index 6e54c5c..8b7f96b 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -107,7 +107,9 @@ def qr(request, key): context["qr"]: QrCode context["qr"] = qr = get_object_or_404(QrCode, key=key) codes = QrCode.code_pks(request.user.team) - if qr.id != codes[request.user.team.current_qr_i]: + if qr.id == codes[request.user.team.current_qr_i-1]: # the user reloaded the page after advancing + return redirect(reverse("qr_current")) + elif qr.id != codes[request.user.team.current_qr_i]: """ Either the user skipped ahead (is on path) or they found a random qr code (not on path) Either way... not allowed From 4b01d1f25bd0db76e52bf8de25ee5354f540d764 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 18:12:22 -0500 Subject: [PATCH 38/71] comments for calc time --- core/static/core/countdown.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/static/core/countdown.js b/core/static/core/countdown.js index 5373540..33b4d57 100644 --- a/core/static/core/countdown.js +++ b/core/static/core/countdown.js @@ -11,6 +11,13 @@ function assertClass(classList, className, contains) { } const newCalculateTime = (start, end, startsPre, startsPost, endsPre, endsPost, ended) => () => { + /* + starts: before start + ends: after start before end (during) + ended: after end + + + */ let started = false let finished = false let d = Math.floor((end - Date.now()) / 1000) From 9d5dd6a1a8ec3b9e116c96f26f7e15b1296c7799 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Mon, 4 Dec 2023 23:13:46 +0000 Subject: [PATCH 39/71] format --- core/views/qr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/views/qr.py b/core/views/qr.py index 8b7f96b..18ac9c4 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -107,7 +107,9 @@ def qr(request, key): context["qr"]: QrCode context["qr"] = qr = get_object_or_404(QrCode, key=key) codes = QrCode.code_pks(request.user.team) - if qr.id == codes[request.user.team.current_qr_i-1]: # the user reloaded the page after advancing + if ( + qr.id == codes[request.user.team.current_qr_i - 1] + ): # the user reloaded the page after advancing return redirect(reverse("qr_current")) elif qr.id != codes[request.user.team.current_qr_i]: """ From cba3ac0f566fcd0817503be6d6e2eea3075f10ba Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 20:06:03 -0500 Subject: [PATCH 40/71] Updates context_processors to support all ranges of events (will refactor later) --- core/context_processors.py | 49 +++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index db16049..cb9faf7 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,12 +1,33 @@ import datetime -from core.models import Hunt +from django.contrib import messages +from django.db.models import Func, F, DurationField, Case, DateTimeField, When from django.utils import timezone +from core.models import Hunt + -def start(request): +def start(request) -> dict: + """ + returns info about the current/closest hunt + return dict: + START: start time of the current/closest hunt + START_BEFORE: bool, if the current time is before the start time + START_UNTIL: timedelta, time until the start time + END: end time of the current/closest hunt + END_BEFORE: bool, if the current time is before the end time + END_UNTIL: timedelta, time until the end time + """ now = timezone.now() - if (hunt := Hunt.current_hunt()) is not None: + if Hunt.objects.count() == 0: + messages.add_message( + request, + messages.WARNING, + "No hunts are in the database. Please contact an admin to add a hunt to the database", + ) + event_start = now + datetime.timedelta(weeks=75) + event_end = event_start + datetime.timedelta(hours=3) + elif (hunt := Hunt.current_hunt()) is not None: event_start = hunt.start event_end = hunt.end elif Hunt.next_hunt() is not None: @@ -14,11 +35,23 @@ def start(request): event_start = hunt.start event_end = hunt.end else: - print("WARNING: No hunt is scheduled") - print("Please add a hunt in the future to the database") - event_start = now + datetime.timedelta(weeks=75) - event_end = event_start + datetime.timedelta(hours=3) - + # at least one + closest_hunt = Hunt.objects.annotate( + selected_time=Case( # if we should use the start or end time (if it's in the future or past) + When(start__lt=now, then=F('end')), + default=F('start'), + output_field=DateTimeField(), + ) + ).annotate( + time_difference=Func( + F('selected_time') - now, + function='ABS', + output_field=DurationField(), + ) + ).order_by('time_difference').first() + event_start = closest_hunt.start + event_end = closest_hunt.end + return dict( START=event_start, START_BEFORE=event_start > now, From 2b64529e885da2f5318b61b25f605411203ade19 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 22:19:31 -0500 Subject: [PATCH 41/71] refactor: rename nextqr to nexthint fix: redirect all (qr code) views to /first if user is on first (and redir from first to current if not) + optimized qr_first adds on_qr to show if on qr page --- core/views/qr.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index 8b7f96b..33b0c2c 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -1,4 +1,3 @@ -import datetime from functools import wraps from queue import LifoQueue @@ -116,8 +115,9 @@ def qr(request, key): """ context["offpath"] = True return render(request, "core/qr.html", context=context) + context["on_qr"] = True i = codes.index(qr.id) - context["nextqr"] = ( + context["nexthint"] = ( None if len(codes) <= (j := i + 1) else QrCode.objects.get(id=codes[j]) ) context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) @@ -132,13 +132,12 @@ def qr(request, key): def qr_first(request): context = dict(first=True) # check if the user is on the first qr code - # if request.user.team.current_qr_i != 0: - # messages.error(request, _("You are not on the first QR code.")) - # return redirect(reverse("qr_current")) - context["qr"] = QrCode.codes(request.user.team).first() - codes = QrCode.code_pks(request.user.team) - context["nextqr"] = QrCode.objects.get(id=codes[0]) - context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + if request.user.team.current_qr_i != 0: + messages.info(request, _("You are not on the first QR code.")) + return redirect(reverse("qr_current")) + codes = QrCode.codes(request.user.team) + context["nexthint"] = codes[0] + #context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) return render(request, "core/qr.html", context=context) @@ -148,10 +147,13 @@ def qr_first(request): @during_hunt def qr_current(request): i = request.user.team.current_qr_i - context = dict(first=i == 0, current=True) + first_ = i == 0 + if first_: + return redirect(reverse("qr_first")) + context = dict(first=first_, current=True) codes = QrCode.codes(request.user.team) context["qr"] = codes[i] - context["nextqr"] = None if len(codes) <= (j := i + 1) else codes[j] + context["nexthint"] = None if len(codes) <= (j := i + 1) else codes[j] context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) return render(request, "core/qr.html", context=context) @@ -162,8 +164,10 @@ def qr_current(request): @during_hunt def qr_catalog(request): i = request.user.team.current_qr_i - context = dict(first=i == 0, current=True) - context["qr"] = QrCode.codes(request.user.team)[: request.user.team.current_qr_i] + if i == 0: + return redirect(reverse("qr_first")) + context = dict(current=True) + context["qr"] = QrCode.codes(request.user.team)[: i] # i + 1? return render(request, "core/qr_catalog.html", context=context) From c9f84b6a0173b7213d478e340133e06f00f9d183 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 5 Dec 2023 03:20:55 +0000 Subject: [PATCH 42/71] format --- core/context_processors.py | 29 +++++++++++++++++------------ core/views/qr.py | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index cb9faf7..3086b84 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -36,22 +36,27 @@ def start(request) -> dict: event_end = hunt.end else: # at least one - closest_hunt = Hunt.objects.annotate( - selected_time=Case( # if we should use the start or end time (if it's in the future or past) - When(start__lt=now, then=F('end')), - default=F('start'), - output_field=DateTimeField(), + closest_hunt = ( + Hunt.objects.annotate( + selected_time=Case( # if we should use the start or end time (if it's in the future or past) + When(start__lt=now, then=F("end")), + default=F("start"), + output_field=DateTimeField(), + ) ) - ).annotate( - time_difference=Func( - F('selected_time') - now, - function='ABS', - output_field=DurationField(), + .annotate( + time_difference=Func( + F("selected_time") - now, + function="ABS", + output_field=DurationField(), ) - ).order_by('time_difference').first() + ) + .order_by("time_difference") + .first() + ) event_start = closest_hunt.start event_end = closest_hunt.end - + return dict( START=event_start, START_BEFORE=event_start > now, diff --git a/core/views/qr.py b/core/views/qr.py index b02af6a..80d864f 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -139,7 +139,7 @@ def qr_first(request): return redirect(reverse("qr_current")) codes = QrCode.codes(request.user.team) context["nexthint"] = codes[0] - #context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) + # context["logic_hint"] = LogicPuzzleHint.get_clue(request.user.team) return render(request, "core/qr.html", context=context) @@ -169,7 +169,7 @@ def qr_catalog(request): if i == 0: return redirect(reverse("qr_first")) context = dict(current=True) - context["qr"] = QrCode.codes(request.user.team)[: i] # i + 1? + context["qr"] = QrCode.codes(request.user.team)[:i] # i + 1? return render(request, "core/qr_catalog.html", context=context) From e47d052a09b457626903bf7baba86a363d326b2d Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 22:33:01 -0500 Subject: [PATCH 43/71] fix typo --- core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index aae8ec1..8e64c8b 100644 --- a/core/models.py +++ b/core/models.py @@ -253,7 +253,7 @@ def __str__(self): return self.name @classmethod - def lastest_hunt(cls) -> Hunt | None: + def latest_hunt(cls) -> Hunt | None: """ Returns the latest hunt, ended or not. :return: Hunt or None From ffae7bf2dbc67815884c5153e178e44989ec3227 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Mon, 4 Dec 2023 23:23:04 -0500 Subject: [PATCH 44/71] style: improved class naming fix: broken css --- core/static/core/base.css | 12 ++++++------ core/static/core/countdown.js | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/static/core/base.css b/core/static/core/base.css index 4fee4ae..12c58c3 100644 --- a/core/static/core/base.css +++ b/core/static/core/base.css @@ -74,15 +74,15 @@ body > nav { background: var(--cd-bg); } -#countdown.soon-very1 { +#countdown.ending-5m { color: #ac8300; } -#countdown.soon-very2 { +#countdown.ending-1m { color: #d56500; } -#countdown.soon-very3 { +#countdown.ending-10s { color: #f23749; } @@ -113,11 +113,11 @@ a:visited { list-style: none; padding: 1rem; margin: 0.5rem; -.} +} .imgicon { height: 3rem; -.} +} .qrcode { padding: 1rem; @@ -246,7 +246,7 @@ input[type=submit]:active { background: rgba(255, 255, 255, 0.20) } -input:placeholder { +input::placeholder { color: #ccc; } diff --git a/core/static/core/countdown.js b/core/static/core/countdown.js index 33b4d57..b943642 100644 --- a/core/static/core/countdown.js +++ b/core/static/core/countdown.js @@ -51,6 +51,9 @@ const newCalculateTime = (start, end, startsPre, startsPost, endsPre, endsPost, assertClass(e.classList, "soon-very2", d < 60 && d >= 10) assertClass(e.classList, "soon-very3", d < 10) } + assertClass(e.classList, "ending-5m", display < 60*5 && display >= 60) + assertClass(e.classList, "ending-1m", display < 60 && display >= 10) + assertClass(e.classList, "ending-10s", display < 10) // TODO: js i18n startsPre = startsPre.startsWith('__') ? '' : startsPre startsPost = startsPost.startsWith('__') ? '' : startsPost From cba568616b49171159ccfa1160f9e03d0c4c63fd Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:01:35 -0500 Subject: [PATCH 45/71] FINALLY fixes timer THANK YOU TO SEAN MY GOAT Co-authored-by: Sean Zhao --- core/static/core/countdown.js | 105 +++++++++++---------- core/templates/core/base.html | 171 +++++++++++++++++----------------- 2 files changed, 139 insertions(+), 137 deletions(-) diff --git a/core/static/core/countdown.js b/core/static/core/countdown.js index b943642..322fc62 100644 --- a/core/static/core/countdown.js +++ b/core/static/core/countdown.js @@ -1,66 +1,69 @@ -const e = document.getElementById("countdown") +const countdown = document.getElementById("countdown") const eTextPre = document.getElementById("countdown-text-pre") const eTextPost = document.getElementById("countdown-text-post") -function assertClass(classList, className, contains) { - if (contains) { - if (!classList.contains(className)) classList.add(className) - } else { - if (classList.contains(className)) classList.remove(className) - } +function updateColours(classList, className, contains) { + if (contains) { + if (!classList.contains(className)) classList.add(className) + } else { + if (classList.contains(className)) classList.remove(className) + } } -const newCalculateTime = (start, end, startsPre, startsPost, endsPre, endsPost, ended) => () => { - /* - starts: before start - ends: after start before end (during) - ended: after end +const sanitizeStart = (text) => text.startsWith('__') ? '' : text; +const sanitizeEnd = (text) => text.startsWith('__') ? '' : text; +const newCalculateTime = (start, end, beforeStartPre, beforeStartPost, endsPre, endsPost, endedPre, endedPost) => () => { + /* + beforeStart: before start + ends: after start before end (during) + ended: after end - */ - let started = false - let finished = false - let d = Math.floor((end - Date.now()) / 1000) - //console.log("Initial d:", d); - if (d < 0) { - d = Math.floor((start - Date.now()) / 1000); - finished = d < 0 - started = true - } + */ + let started = false + let finished = false + let tillStart = Math.floor((start - Date.now()) / 1000) + let tillEnd = Math.floor((end - Date.now()) / 1000); + //console.log("Initial display:", display); + if (tillStart < 0) { // has started + started = true + } + if (tillEnd < 0) { // has ended + finished = true + } + let display = started ? tillEnd : tillStart + if (display < 0 && finished) { + display = Math.abs(display) + } /*console.log("End:", end); console.log("Start:", start); console.log("Now:", Date.now()); console.log("d:", d);*/ - const day = Math.floor(d / 86400) - const hour = Math.floor((d - day * 86400) / 3600) - const minute = Math.floor((d - day * 86400 - hour * 3600) / 60) - const second = d % 60 - let s = '' - if (day == 1) s += '1 day, ' - else if (day == 0) {} - else { - s += `${day} days, ` - } - s += ('0' + hour).slice(-2) + ':' - s += ('0' + minute).slice(-2) + ':' - s += ('0' + second).slice(-2) - e.innerText = s - if (d > 0) { - assertClass(e.classList, "soon-very1", d < 60*5 && d >= 60) - assertClass(e.classList, "soon-very2", d < 60 && d >= 10) - assertClass(e.classList, "soon-very3", d < 10) - } - assertClass(e.classList, "ending-5m", display < 60*5 && display >= 60) - assertClass(e.classList, "ending-1m", display < 60 && display >= 10) - assertClass(e.classList, "ending-10s", display < 10) - // TODO: js i18n - startsPre = startsPre.startsWith('__') ? '' : startsPre - startsPost = startsPost.startsWith('__') ? '' : startsPost - endsPre = endsPre.endsWith('__') ? '' : endsPre - endsPost = endsPost.endsWith('__') ? '' : endsPost - eTextPre.innerText = started ? startsPre : (finished ? ended : endsPre) - eTextPost.innerText = started ? startsPost : (finished ? ended : endsPost) + const day = Math.floor(display / 86400) + const hour = Math.floor((display - day * 86400) / 3600) + const minute = Math.floor((display - day * 86400 - hour * 3600) / 60) + const second = display % 60 + let s = '' + if (day === 1) { + s += '1 day, ' + } else if (day === 0) { + } else { + s += `${day} days, ` + } + s += ('0' + hour).slice(-2) + ':' + s += ('0' + minute).slice(-2) + ':' + s += ('0' + second).slice(-2) + console.log("s:", s); + countdown.innerText = s + if (display > 0) { // before the end + updateColours(countdown.classList, "ending-5m", display < 60 * 5 && display >= 60) + updateColours(countdown.classList, "ending-1m", display < 60 && display >= 10) + updateColours(countdown.classList, "ending-10s", display < 10) + } + // TODO: js i18n + eTextPre.innerText = started ? (finished ? endedPre : sanitizeEnd(endsPre)) : sanitizeStart(beforeStartPre) + eTextPost.innerText = started ? (finished ? endedPost : sanitizeEnd(endsPost)) : sanitizeStart(beforeStartPost) } export default newCalculateTime diff --git a/core/templates/core/base.html b/core/templates/core/base.html index 59e9775..9dfa032 100644 --- a/core/templates/core/base.html +++ b/core/templates/core/base.html @@ -3,99 +3,98 @@ {% load base %} - - {% block title %}{% endblock %} - - - - - - - - {% if START_BEFORE or END_BEFORE %} - - {% endif %} - {% block head_end %}{% endblock %} - - -
- - -
- {% comment %} TODO: consider merging header and title {% endcomment %} - {% if messages %} -
    - {% for message in messages %} - - {% if message.level == DEFAULT_MESSAGE_LEVELS.DEBUG %} - Debug: - {% elif message.level == DEFAULT_MESSAGE_LEVELS.INFO %} - Info: - {% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} - Success: - {% elif message.level == DEFAULT_MESSAGE_LEVELS.WARNING %} - Warning: - {% elif message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} - error - Error: - {% endif %} - {{ message }} - - {% endfor %} -
- {% endif %} -
-
-
-

{% block header %}{% endblock %}

- {% block body %}{% endblock %} -
-
- {% if START_BEFORE or END_BEFORE %} - + setInterval(calculateTime, 1000) + + {% block head_end %}{% endblock %} + + +
+ + +
+ {% comment %} TODO: consider merging header and title {% endcomment %} + {% if messages %} +
    + {% for message in messages %} + + {% if message.level == DEFAULT_MESSAGE_LEVELS.DEBUG %} + Debug: + {% elif message.level == DEFAULT_MESSAGE_LEVELS.INFO %} + Info: + {% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} + Success: + {% elif message.level == DEFAULT_MESSAGE_LEVELS.WARNING %} + Warning: + {% elif message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} + error + Error: + {% endif %} + {{ message }} + + {% endfor %} +
{% endif %} - {% comment %} NOTE: (below) workaround for fixed positon {% endcomment %} -
+
+
+

{% block header %}{% endblock %}

+ {% block body %}{% endblock %} +
+
+ +{% comment %} NOTE: (below) workaround for fixed position {% endcomment %} + - + From a004d1878c58fa5da4a22c5c9e4461fc952256d5 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:03:43 -0500 Subject: [PATCH 46/71] renames nextqr to nexthint as it's simplythe hint not the qr result --- core/templates/core/qr.html | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/core/templates/core/qr.html b/core/templates/core/qr.html index 4f7043d..ee28d80 100644 --- a/core/templates/core/qr.html +++ b/core/templates/core/qr.html @@ -40,21 +40,21 @@ {% if not offpath %}
- {% if nextqr %} + {% if nexthint %} {% if first %} {% translate "your first hint is:" %}
{% else %} {% translate "your next hint is:" %}
{% endif %} - {% else %} - {% comment %} TODO: some cool colourful heart {% endcomment %} +{# {% else %}#} + {% comment %} TODO: some cool colourful heart (this means that they finished)f {% endcomment %} {% endif %}
- {% if nextqr %} - {% hint nextqr request.user.team as hint %} + {% if nexthint %} + {% hint nexthint request.user.team as hint %} {{ hint|mistune }} {% if request.user.is_superuser %} -
({{ nextqr }} change) +
({{ nexthint }} change) {% endif %} {% if logic_hint %} @@ -69,8 +69,7 @@ {% translate 'Oh ho? What lieth there? Tis the light at the end of the tunnel!
Congratulations valiant scavenger and thank you for playing!' %} {% endif %}
- {% else %} { - + {% else %} {% translate 'Incorrect QR code, please read your hint again or contact an organizer' %} {% endif %} From 11adf06f7a2b42159142e7242ac0dfb9717922b8 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:05:06 -0500 Subject: [PATCH 47/71] adds global hunt processor (displays current or closest (ended or upcoming) hunt refactor: moved closest_hunt to Hunt.closest_hunt() --- core/context_processors.py | 27 +++++++-------------------- core/models.py | 31 ++++++++++++++++++++++--------- wlmac-scavenger/settings.py | 1 + 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index 3086b84..b14204a 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,11 +1,16 @@ import datetime +from typing import Dict from django.contrib import messages -from django.db.models import Func, F, DurationField, Case, DateTimeField, When from django.utils import timezone from core.models import Hunt +def hunt(request) -> Dict[str, Hunt]: + """ + returns the current hunt + """ + return dict(hunt=Hunt.current_hunt() or Hunt.closest_hunt()) def start(request) -> dict: """ @@ -35,25 +40,7 @@ def start(request) -> dict: event_start = hunt.start event_end = hunt.end else: - # at least one - closest_hunt = ( - Hunt.objects.annotate( - selected_time=Case( # if we should use the start or end time (if it's in the future or past) - When(start__lt=now, then=F("end")), - default=F("start"), - output_field=DateTimeField(), - ) - ) - .annotate( - time_difference=Func( - F("selected_time") - now, - function="ABS", - output_field=DurationField(), - ) - ) - .order_by("time_difference") - .first() - ) + closest_hunt = Hunt.closest_hunt() event_start = closest_hunt.start event_end = closest_hunt.end diff --git a/core/models.py b/core/models.py index 8e64c8b..5caa7c0 100644 --- a/core/models.py +++ b/core/models.py @@ -7,12 +7,12 @@ from django.contrib.auth.models import AbstractUser from django.core.exceptions import ValidationError from django.db import models -from django.db.models import QuerySet from django.db.models.signals import pre_save from django.dispatch import receiver from django.utils import timezone from django.utils.html import format_html +from django.db.models import Func, F, DurationField, Case, DateTimeField, When def generate_invite_code(): return secrets.token_hex(4) @@ -253,15 +253,28 @@ def __str__(self): return self.name @classmethod - def latest_hunt(cls) -> Hunt | None: - """ - Returns the latest hunt, ended or not. - :return: Hunt or None - """ - try: - Hunt.objects.filter(start__lte=timezone.now()).order_by("start").last() - except IndexError: + def closest_hunt(cls) -> Hunt | None: + now = timezone.now() + closest_hunt = ( + Hunt.objects.annotate( + selected_time=Case( # if we should use the start or end time (if it's in the future or past) + When(start__lt=now, then=F("end")), + default=F("start"), + output_field=DateTimeField(), + ) + ) + .annotate( + time_difference=Func( + F("selected_time") - now, + function="ABS", + output_field=DurationField(), + ) + ) + .order_by("time_difference") + ) + if closest_hunt is None: return None + return closest_hunt.first() @classmethod def current_hunt(cls) -> Hunt | None: diff --git a/wlmac-scavenger/settings.py b/wlmac-scavenger/settings.py index d99f813..691a11d 100644 --- a/wlmac-scavenger/settings.py +++ b/wlmac-scavenger/settings.py @@ -47,6 +47,7 @@ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "core.context_processors.start", + "core.context_processors.hunt", ], }, }, From 4ba0938b6a76b7d695fc551a79b040cb5d24a71d Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:05:27 -0500 Subject: [PATCH 48/71] style: fmt --- core/context_processors.py | 2 ++ core/models.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/context_processors.py b/core/context_processors.py index b14204a..cb9ba94 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -6,12 +6,14 @@ from core.models import Hunt + def hunt(request) -> Dict[str, Hunt]: """ returns the current hunt """ return dict(hunt=Hunt.current_hunt() or Hunt.closest_hunt()) + def start(request) -> dict: """ returns info about the current/closest hunt diff --git a/core/models.py b/core/models.py index 5caa7c0..2317eb1 100644 --- a/core/models.py +++ b/core/models.py @@ -14,6 +14,7 @@ from django.db.models import Func, F, DurationField, Case, DateTimeField, When + def generate_invite_code(): return secrets.token_hex(4) @@ -268,7 +269,7 @@ def closest_hunt(cls) -> Hunt | None: F("selected_time") - now, function="ABS", output_field=DurationField(), - ) + ) ) .order_by("time_difference") ) From a2f69d3b6e1e45b47c354e868d7ff2ad851044e3 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:12:57 -0500 Subject: [PATCH 49/71] oop, removed debug print --- core/static/core/countdown.js | 1 - 1 file changed, 1 deletion(-) diff --git a/core/static/core/countdown.js b/core/static/core/countdown.js index 322fc62..2294dad 100644 --- a/core/static/core/countdown.js +++ b/core/static/core/countdown.js @@ -54,7 +54,6 @@ const newCalculateTime = (start, end, beforeStartPre, beforeStartPost, endsPre, s += ('0' + hour).slice(-2) + ':' s += ('0' + minute).slice(-2) + ':' s += ('0' + second).slice(-2) - console.log("s:", s); countdown.innerText = s if (display > 0) { // before the end updateColours(countdown.classList, "ending-5m", display < 60 * 5 && display >= 60) From bfc9d03e2c0a0115a75a21d66c2d6bf8d628ea52 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:15:41 -0500 Subject: [PATCH 50/71] correct error message --- core/views/qr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index 80d864f..89d78e9 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -37,8 +37,8 @@ def upcoming_hunt_required(f): def wrapped(*args, **kwargs): request = args[0] if Hunt.current_hunt() is None and Hunt.next_hunt() is None: - messages.error( - request, _("No hunts are in the database, please contact an admin.") + messages.warning( + request, _("No future hunts are scheduled.") ) return redirect(reverse("index")) return f( From 20ac7eb22fea82811c2d716e06c5236c972741f2 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:22:14 -0500 Subject: [PATCH 51/71] I18N: remove eng "translations" and remake ja_JP --- core/locale/en_CA/LC_MESSAGES/django.mo | Bin 434 -> 0 bytes core/locale/en_CA/LC_MESSAGES/django.po | 282 ------------------------ core/locale/ja_JP/LC_MESSAGES/django.mo | Bin 2914 -> 2554 bytes core/locale/ja_JP/LC_MESSAGES/django.po | 111 ++++++---- 4 files changed, 74 insertions(+), 319 deletions(-) delete mode 100644 core/locale/en_CA/LC_MESSAGES/django.mo delete mode 100644 core/locale/en_CA/LC_MESSAGES/django.po diff --git a/core/locale/en_CA/LC_MESSAGES/django.mo b/core/locale/en_CA/LC_MESSAGES/django.mo deleted file mode 100644 index 5d3a7323185aeecba4ba0580f73a852a46437121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 434 zcmYk2%}&EG49CmYfr=An4ng9EGC<-mg@IPKvK893D%~WwjL@3WsZFLNo7jVJ;stmr zo&{$d#$Wl_iDf5tehzm(9n=nTgj^zfNQhXyATGj@eWXTCwj5`P{s8&Hy^VjP$F-Kd z%WB&iGM$1bbfUmf(C|uBqI41^XOJ5j6m$YgS5h#Nc)bU2;FF1;J_hl~O<^vTp^+}D zg5EAlO*6@eo5VDQWo2t, YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-03 14:46-0500\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Jason Cameron \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: .\core\admin.py:14 -msgid "Set selected users as a Location Setter" -msgstr "" - -#: .\core\admin.py:25 -msgid "Set selected users as a Logic Puzzle Setter" -msgstr "" - -#: .\core\admin.py:117 -msgid "Link to Hint Page" -msgstr "" - -#: .\core\templates\core\base.html:22 -msgid "starts in" -msgstr "" - -#: .\core\templates\core\base.html:23 -msgid "__startsPost" -msgstr "" - -#: .\core\templates\core\base.html:24 -msgid " __endsPre" -msgstr "Hurry Hurry!" - -#: .\core\templates\core\base.html:25 -msgid "remaining" -msgstr "remaining" - -#: .\core\templates\core\base.html:26 -msgid "scavenger hunt ended!" -msgstr "" - -#: .\core\templates\core\base.html:105 -msgid "Home" -msgstr "" - -#: .\core\templates\core\base.html:112 -msgid "Admin" -msgstr "" - -#: .\core\templates\core\base.html:117 -msgid "current" -msgstr "" - -#: .\core\templates\core\base.html:121 .\core\templates\core\logout.html:6 -msgid "Logout" -msgstr "" - -#: .\core\templates\core\base.html:128 -msgid "Login" -msgstr "" - -#: .\core\templates\core\credits.html:6 -msgid "Credits" -msgstr "" - -#: .\core\templates\core\credits.html:10 -msgid "credits" -msgstr "" - -#: .\core\templates\core\credits.html:14 -msgid "project manager" -msgstr "" - -#: .\core\templates\core\credits.html:19 -msgid "programming" -msgstr "" - -#: .\core\templates\core\credits.html:28 -msgid "content" -msgstr "" - -#: .\core\templates\core\credits.html:34 -msgid "UI/UX design" -msgstr "" - -#: .\core\templates\core\credits.html:41 -msgid "hints" -msgstr "" - -#: .\core\templates\core\credits.html:48 -msgid "support" -msgstr "" - -#: .\core\templates\core\gate.html:6 .\core\templates\core\index.html:6 -msgid "Scavenger Hunt" -msgstr "" - -#: .\core\templates\core\gate.html:11 -msgid "The hunt has not begun yet." -msgstr "" - -#: .\core\templates\core\gate.html:14 -#, python-format -msgid "" -"\n" -"Please login.\n" -msgstr "" - -#: .\core\templates\core\index.html:10 -#, python-format -msgid "" -"\n" -"welcome, %(username)s!\n" -msgstr "" - -#: .\core\templates\core\index.html:23 -#, python-format -msgid "" -"\n" -" the hunt has started! go decrypt your first " -"hint!\n" -" " -msgstr "" - -#: .\core\templates\core\index.html:29 -#, python-format -msgid "" -"\n" -" or, would you like to invite more team " -"members?\n" -" " -msgstr "" - -#: .\core\templates\core\index.html:37 -msgid "" -"\n" -" you're going solo\n" -" " -msgstr "" - -#: .\core\templates\core\index.html:41 -#, python-format -msgid "" -"\n" -" your team: %(team)s\n" -" " -msgstr "" - -#: .\core\templates\core\index.html:50 .\core\templates\core\team_join.html:10 -msgid "join a team" -msgstr "" - -#: .\core\templates\core\index.html:53 .\core\templates\core\team_new.html:10 -msgid "make a team" -msgstr "" - -#: .\core\templates\core\index.html:60 -msgid "go solo " -msgstr "" - -#: .\core\templates\core\logout.html:10 .\core\templates\core\logout.html:16 -msgid "logout" -msgstr "" - -#: .\core\templates\core\qr.html:10 -msgid "Hint" -msgstr "" - -#: .\core\templates\core\qr.html:33 -msgid "your team found:" -msgstr "" - -#: .\core\templates\core\qr.html:37 -msgid "change" -msgstr "" - -#: .\core\templates\core\qr.html:46 -msgid "your first hint is:" -msgstr "" - -#: .\core\templates\core\qr.html:48 -msgid "your next hint is:" -msgstr "" - -#: .\core\templates\core\qr.html:72 -msgid "" -"Oh ho? What lieth there? Tis the light at the end of the tunnel!
" -"Congratulations valiant scavenger and thank you for playing!" -msgstr "" - -#: .\core\templates\core\team_invite.html:7 -msgid "Invite Members" -msgstr "" - -#: .\core\templates\core\team_invite.html:11 -msgid "invite members" -msgstr "" - -#: .\core\templates\core\team_invite.html:33 -#, python-format -msgid "" -"\n" -" you may either\n" -" ,\n" -" share via invite code %(code)s,\n" -" or allow them to scan the QR code below:\n" -" " -msgstr "" - -#: .\core\templates\core\team_join.html:6 -msgid "Join a Team" -msgstr "" - -#: .\core\templates\core\team_join.html:14 -msgid "either enter your team's join code" -msgstr "" - -#: .\core\templates\core\team_join.html:18 -msgid "Join" -msgstr "" - -#: .\core\templates\core\team_join.html:20 -msgid "or, scan your team's QR code" -msgstr "" - -#: .\core\templates\core\team_new.html:6 -msgid "Make a Team" -msgstr "" - -#: .\core\templates\core\team_new.html:24 -msgid "create" -msgstr "" - -#: .\core\views\qr.py:28 -msgid "Please join a team or choose to go solo before getting a hint." -msgstr "" - -#: .\core\views\qr.py:66 -msgid "Contest has not started yet." -msgstr "" - -#: .\core\views\qr.py:71 -msgid "Contest has ended." -msgstr "" - -#: .\core\views\team.py:25 -msgid "" -"Since the hunt has already begun, switching teams is disallowed. If you need " -"to switch teams, please contact an admin." -msgstr "" - -#: .\core\views\team.py:45 -#, python-format -msgid "Joined team %(team_name)s" -msgstr "" - -#: .\core\views\team.py:51 -#, python-format -msgid "Team %(team_name)s is full." -msgstr "" - -#: .\core\views\team.py:69 -msgid "Since the hunt has already begun, making new teams is disallowed." -msgstr "" - -#: .\core\views\team.py:85 -#, python-format -msgid "Made team %(team_name)s" -msgstr "" diff --git a/core/locale/ja_JP/LC_MESSAGES/django.mo b/core/locale/ja_JP/LC_MESSAGES/django.mo index d9e955c4addfd8c766916811e71e52d85a6fcbcf..00d2ad3d848800f253062924240a66b73e4a2103 100644 GIT binary patch delta 803 zcmXxiPbdUo7{~Ep4$ zxQTji*Lr|j=)`)C`iN_c<1;F;chth4ScV}6WhJ(-zG>p7lz~C(7}nCCK^GUW8dotz zb=GZvn{3(`Kf(dL!ww9Ql@iUMo_C`Xo#vu-X02ItS>G&iqX}!M(t22hhqix#^YmYE z3di|8y|;y$cV@jrUNJYQ`9r<;B|Foi?(v&hfnxoCkM)L!pl zJzilK-XjGuH8lN?%Bs*d{0FZD=SP)LRTWB3QK$Xa-X??YU->u`!G4jnX|>}ONY#U6 zG^BIFc?=F1$HkP>+Gz=zLP;uA4TTP-LfhCx)5ipn@aMvlg^%!}<1d%&6k4M>r%(}p Mb9^`P>c`5mzw}N%`v3p{ delta 1154 zcmZ|NTSyd97{Kvk-Y;3Hc`2(|OS5_i0>eV3=q0E?3tze|n>)Hv?8eM0NEe-1vP_|H zu^zHJXi@Z#B_$TU1<_mY1s&HAl7a|Ag!+Huy!J6Ozw^zW`OevM&O-6m(%#QS86%3Q z;jZL9n5I-6o?pg;XiirufGxNYZEVGxxDDsA94j)E%ETQgb?Px2Tainh@I8fvw% zs#mF3H1c`z-uDyAKlp+&&?2tJEJl&M5{t10i*Prt#6wud>YeoSZf3Eb_m|PcG2Dp- z%)-EZn8o-iOhY=JMw$6ll!1nQM^OeELu#wnC`8Gc7U66>BTBv znaBpeUFw^wjx6nk95@NtM+uo_HMjg*i8b7EYULoY%HDxpjuNtRoK3}_^`boad)a}* z1`&^#Mx-kmHzFb1u;U>sZiWL!N6ZMDZC20OxX}~qvW$k15wXnn#;WSt_D;)=2fM5@ zTkNVPFEJvW(Rjo5P*Wgpq9MH_tuz=kqhY(*GF33>m9(2Bkc~S-Xyg%#zmEh4_WhR720vN^GIoDoZ8xXt6iQ?w3|py=FAm* Sd+E+h_3OS-`X}$?9{&e!O~dX0 diff --git a/core/locale/ja_JP/LC_MESSAGES/django.po b/core/locale/ja_JP/LC_MESSAGES/django.po index 17e831f..4fd3628 100644 --- a/core/locale/ja_JP/LC_MESSAGES/django.po +++ b/core/locale/ja_JP/LC_MESSAGES/django.po @@ -2,13 +2,14 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# +#: .\core\templates\core\base.html:22 #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-02 21:51-0500\n" +"POT-Creation-Date: 2023-12-05 00:21-0500\n" "PO-Revision-Date: 2022-12-16 20:24+EST\n" "Last-Translator: ใซใƒใ‚†ใ„ <+@nyiyui.ca>\n" "Language-Team: LANGUAGE \n" @@ -29,43 +30,43 @@ msgstr "" msgid "Link to Hint Page" msgstr "" -#: .\core\templates\core\base.html:22 +#: .\core\templates\core\base.html:21 msgid "starts in" msgstr "ใ‚ใจ" #: .\core\templates\core\base.html:23 -msgid "__startsPost" -msgstr "ใงๅง‹ใพใ‚‹ใซใ‚ƒ" +msgid "Only" +msgstr "" #: .\core\templates\core\base.html:24 -msgid "__endsPre" -msgstr "ใ‚ใจ" - -#: .\core\templates\core\base.html:25 msgid "remaining" msgstr "ใ ใ‚ˆใ€œ" +#: .\core\templates\core\base.html:25 +msgid "ended" +msgstr "" + #: .\core\templates\core\base.html:26 -msgid "scavenger hunt ended!" -msgstr "็ต‚ไบ†ใ€œ๏ผ" +msgid "ago" +msgstr "" -#: .\core\templates\core\base.html:105 +#: .\core\templates\core\base.html:104 msgid "Home" msgstr "ใƒ›ใƒผใƒ " -#: .\core\templates\core\base.html:112 +#: .\core\templates\core\base.html:111 msgid "Admin" msgstr "็ฎก็†" -#: .\core\templates\core\base.html:117 +#: .\core\templates\core\base.html:116 msgid "current" msgstr "ๆœ€ๆ–ฐใƒ’ใƒณใƒˆ" -#: .\core\templates\core\base.html:121 .\core\templates\core\logout.html:6 +#: .\core\templates\core\base.html:120 .\core\templates\core\logout.html:6 msgid "Logout" msgstr "ใƒญใ‚ฐใ‚ขใ‚ฆใƒˆ" -#: .\core\templates\core\base.html:128 +#: .\core\templates\core\base.html:127 msgid "Login" msgstr "" @@ -85,7 +86,7 @@ msgstr "" msgid "programming" msgstr "" -#: .\core\templates\core\credits.html:29 +#: .\core\templates\core\credits.html:28 msgid "content" msgstr "" @@ -93,11 +94,11 @@ msgstr "" msgid "UI/UX design" msgstr "" -#: .\core\templates\core\credits.html:39 +#: .\core\templates\core\credits.html:41 msgid "hints" msgstr "ใƒ’ใƒณใƒˆ" -#: .\core\templates\core\credits.html:46 +#: .\core\templates\core\credits.html:48 msgid "support" msgstr "" @@ -131,19 +132,24 @@ msgstr "" "\n" "%(username)sใ€ใ‚ˆใ†ใ“ใ๏ผ\n" -#: .\core\templates\core\index.html:23 -#, python-format +#: .\core\templates\core\index.html:25 +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| " the hunt has started! go decrypt your first " +#| "hint!\n" +#| " " msgid "" "\n" -" the hunt has started! go decrypt your first " -"hint!\n" +" %(hunt_name)s has started! go decrypt your first hint!\n" " " msgstr "" "\n" "ใŒใ‚‰ใใŸ้›†ใ‚ใ€้–‹ๅง‹๏ผๆœ€ๅˆใฎไบŒๆฌกๅ…ƒใ‚ณใƒผใƒ‰ใ‚’ๅฝ“ใฆ" "ใฆ๏ผ" -#: .\core\templates\core\index.html:29 +#: .\core\templates\core\index.html:31 #, fuzzy, python-format #| msgid "" #| "\n" @@ -159,7 +165,7 @@ msgstr "" "\n" "ๆœชใ ใƒกใƒณใƒใƒผๆ‹›ๅพ…ใ‚‚ๅ‡บๆฅใ‚‹ใ‚ˆใ€‚" -#: .\core\templates\core\index.html:37 +#: .\core\templates\core\index.html:39 msgid "" "\n" " you're going solo\n" @@ -168,7 +174,7 @@ msgstr "" "\n" "ใ‚ฝใƒญใƒ—ใƒฌใ‚ค" -#: .\core\templates\core\index.html:41 +#: .\core\templates\core\index.html:43 #, python-format msgid "" "\n" @@ -178,15 +184,15 @@ msgstr "" "\n" "ใƒใƒผใƒ ใƒ—ใƒฌใ‚ค๏ผš%(team)s" -#: .\core\templates\core\index.html:50 .\core\templates\core\team_join.html:10 +#: .\core\templates\core\index.html:52 .\core\templates\core\team_join.html:10 msgid "join a team" msgstr "ใƒใƒผใƒ ใซๅ‚ๅŠ " -#: .\core\templates\core\index.html:53 .\core\templates\core\team_new.html:10 +#: .\core\templates\core\index.html:55 .\core\templates\core\team_new.html:10 msgid "make a team" msgstr "ใƒใƒผใƒ ใ‚’ไฝœใ‚‹" -#: .\core\templates\core\index.html:60 +#: .\core\templates\core\index.html:62 msgid "go solo " msgstr "ใ‚ฝใƒญใƒ—ใƒฌใ‚ค" @@ -216,12 +222,16 @@ msgstr "ๆœ€ๅˆใฎใƒ’ใƒณใƒˆใฏ๏ผš" msgid "your next hint is:" msgstr "ๆฌกใฎใƒ’ใƒณใƒˆใฏ๏ผš" -#: .\core\templates\core\qr.html:72 +#: .\core\templates\core\qr.html:71 msgid "" "Oh ho? What lieth there? Tis the light at the end of the tunnel!
" "Congratulations valiant scavenger and thank you for playing!" msgstr "" +#: .\core\templates\core\qr.html:75 +msgid "Incorrect QR code, please read your hint again or contact an organizer" +msgstr "" + #: .\core\templates\core\team_invite.html:7 msgid "Invite Members" msgstr "ใƒใƒผใƒ ใซๆ‹›ๅพ…" @@ -284,15 +294,33 @@ msgstr "ใƒใƒผใƒ ใ‚’ไฝœใ‚‹" msgid "create" msgstr "ไฝœๆˆ" -#: .\core\views\qr.py:28 +#: .\core\views\qr.py:27 msgid "Please join a team or choose to go solo before getting a hint." msgstr "ใƒ’ใƒณใƒˆใ‚’่ฆ‹ใ‚‹ๅ‰ใซใ€ใ‚ฝใƒญใƒ—ใƒฌใ‚คใ™ใ‚‹ใ‹ใƒใƒผใƒ ใงใ™ใ‚‹ใ‹ๆฑบใ‚ใฆใซใ‚ƒ๏ผ" -#: .\core\views\qr.py:48 +#: .\core\views\qr.py:41 +msgid "No future hunts are scheduled." +msgstr "" + +#: .\core\views\qr.py:70 +msgid "No hunt is currently active." +msgstr "" + +#: .\core\views\qr.py:88 +#, fuzzy +#| msgid "Contest has not started yet." +msgid "Contest has ended." +msgstr "ๆœชใ ๅง‹ใพใฃใฆใชใ„ใ‚ˆใ€œ" + +#: .\core\views\qr.py:93 msgid "Contest has not started yet." msgstr "ๆœชใ ๅง‹ใพใฃใฆใชใ„ใ‚ˆใ€œ" -#: .\core\views\team.py:24 +#: .\core\views\qr.py:138 +msgid "You are not on the first QR code." +msgstr "" + +#: .\core\views\team.py:26 #, fuzzy #| msgid "Since the hunt has already begun, switching teams is disallowed." msgid "" @@ -300,25 +328,34 @@ msgid "" "to switch teams, please contact an admin." msgstr "ใ‚‚ใ†ๅง‹ใพใฃใฆใ‚‹ใ‹ใ‚‰ใ€ใƒใƒผใƒ ๅค‰ใˆใ‚‹ใฎใฏใƒ€ใƒกใƒ€ใƒกใ ใ‹ใ‚‰ใญใ€‚" -#: .\core\views\team.py:44 +#: .\core\views\team.py:46 #, python-format msgid "Joined team %(team_name)s" msgstr "ใƒใƒผใƒ %(team_name)sใซๅ‚ๅŠ ใ—ใŸใซใ‚ƒ" -#: .\core\views\team.py:50 +#: .\core\views\team.py:52 #, python-format msgid "Team %(team_name)s is full." msgstr "ใƒใƒผใƒ %(team_name)sใฏใ‚‚ใ†ๆบ€ๆฏใ ใ‚ˆ" -#: .\core\views\team.py:67 +#: .\core\views\team.py:71 msgid "Since the hunt has already begun, making new teams is disallowed." msgstr "ใ‚‚ใ†ๅง‹ใพใฃใŸใ‹ใ‚‰ใ€ๆ–ฐใ—ใ„ใƒใƒผใƒ ไฝœใ‚‹ใฎใฏใƒ€ใƒกใ ใ‚ˆใ€‚" -#: .\core\views\team.py:83 +#: .\core\views\team.py:87 #, python-format msgid "Made team %(team_name)s" msgstr "ใƒใƒผใƒ %(team_name)sใ‚’ใคใใฃใŸใ‚ˆใ€‚" +#~ msgid "__startsPost" +#~ msgstr "ใงๅง‹ใพใ‚‹ใซใ‚ƒ" + +#~ msgid "__endsPre" +#~ msgstr "ใ‚ใจ" + +#~ msgid "scavenger hunt ended!" +#~ msgstr "็ต‚ไบ†ใ€œ๏ผ" + #~ msgid "ends in:" #~ msgstr "็ต‚ไบ†ๆ™‚้–“๏ผš" From 5417e7f97d97213bc49208d4ebbb831720d1b76f Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Tue, 5 Dec 2023 05:23:14 +0000 Subject: [PATCH 52/71] format --- core/views/qr.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/views/qr.py b/core/views/qr.py index 89d78e9..b2432a3 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -37,9 +37,7 @@ def upcoming_hunt_required(f): def wrapped(*args, **kwargs): request = args[0] if Hunt.current_hunt() is None and Hunt.next_hunt() is None: - messages.warning( - request, _("No future hunts are scheduled.") - ) + messages.warning(request, _("No future hunts are scheduled.")) return redirect(reverse("index")) return f( *args, **kwargs From e8f520d9bd26899c448b549cbacb31f2039a392b Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:36:37 -0500 Subject: [PATCH 53/71] updated hunt context proc to show if there is a future hunt refactor: renamed a bunch of the start hunt contexts --- core/context_processors.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index cb9ba94..3684a06 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,5 +1,4 @@ import datetime -from typing import Dict from django.contrib import messages from django.utils import timezone @@ -7,11 +6,17 @@ from core.models import Hunt -def hunt(request) -> Dict[str, Hunt]: +def hunt(request) -> dict: """ - returns the current hunt + returns: + hunt: the current hunt or the closest hunt + FUTURE_HUNT_EXISTS: bool, if a future hunt exists """ - return dict(hunt=Hunt.current_hunt() or Hunt.closest_hunt()) + + return dict( + hunt=Hunt.current_hunt() or Hunt.closest_hunt(), + FUTURE_HUNT_EXISTS=Hunt.next_hunt() is not None, + ) def start(request) -> dict: @@ -19,11 +24,12 @@ def start(request) -> dict: returns info about the current/closest hunt return dict: START: start time of the current/closest hunt - START_BEFORE: bool, if the current time is before the start time - START_UNTIL: timedelta, time until the start time + BEFORE_START: bool, if the current time is before the start time + TIME_UNTIL_START: timedelta, time until the start time END: end time of the current/closest hunt - END_BEFORE: bool, if the current time is before the end time - END_UNTIL: timedelta, time until the end time + BEFORE_END: bool, if the current time is before the end time + TIME_UNTIL_END: timedelta, time until the end time + IN_HUNT: bool, if the current time is between the start and end time """ now = timezone.now() if Hunt.objects.count() == 0: @@ -48,9 +54,10 @@ def start(request) -> dict: return dict( START=event_start, - START_BEFORE=event_start > now, - START_UNTIL=event_start - now, + BEFORE_START=event_start > now, + TIME_UNTIL_START=event_start - now, END=event_end, - END_BEFORE=event_end > now, - END_UNTIL=event_end - now, + BEFORE_END=event_end > now, + TIME_UNTIL_END=event_end - now, + IN_HUNT=(event_start < now < event_end), ) From bdfd4903e3d4c149c9f4d4e65214edb6fc4b4753 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 00:40:14 -0500 Subject: [PATCH 54/71] fix: index shows scav started after end added a page if no future scav hunts are scheduled + prev refactor style: fmt --- core/context_processors.py | 2 +- core/templates/core/index.html | 13 +++++++++++-- core/views/qr.py | 4 +--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/context_processors.py b/core/context_processors.py index 3684a06..fa4ec8e 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -12,7 +12,7 @@ def hunt(request) -> dict: hunt: the current hunt or the closest hunt FUTURE_HUNT_EXISTS: bool, if a future hunt exists """ - + return dict( hunt=Hunt.current_hunt() or Hunt.closest_hunt(), FUTURE_HUNT_EXISTS=Hunt.next_hunt() is not None, diff --git a/core/templates/core/index.html b/core/templates/core/index.html index e5c0b3c..0daae7d 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -18,8 +18,10 @@ {% block body %} -{% if request.user.in_team and not START_BEFORE %} - {% url 'qr_first' as first_url %} + {% if IN_HUNT or FUTURE_HUNT_EXISTS %} +{% if request.user.in_team and IN_HUNT %} + {% url 'qr_first' as first_url %} {% comment %}todo: add logic to swich text to /current{% endcomment %} + {% url 'qr_current' as current_url %} {% blocktranslate %} {{ hunt_name }} has started! go decrypt your first hint! {% endblocktranslate %} @@ -63,4 +65,11 @@

{% endif %} {% endif %} +{% else %} +

+ {% blocktranslate %} + no future scavenger hunt is scheduled, please check back later + {% endblocktranslate %} + {% endif %} +

{% endblock %} diff --git a/core/views/qr.py b/core/views/qr.py index 89d78e9..b2432a3 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -37,9 +37,7 @@ def upcoming_hunt_required(f): def wrapped(*args, **kwargs): request = args[0] if Hunt.current_hunt() is None and Hunt.next_hunt() is None: - messages.warning( - request, _("No future hunts are scheduled.") - ) + messages.warning(request, _("No future hunts are scheduled.")) return redirect(reverse("index")) return f( *args, **kwargs From 648c1a1d12559c76d83666ad711a8ffcd2d9c0c2 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 01:09:54 -0500 Subject: [PATCH 55/71] I rly can't nix --- flake.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/flake.nix b/flake.nix index c6bb215..47ad2f3 100644 --- a/flake.nix +++ b/flake.nix @@ -21,5 +21,17 @@ ]; }; }); + + packages = forAllSystems (system: { + default = pkgs.${system}.mkShellNoCC { + packages = with pkgs.${system}; [ + poetry + (python311.withPackages + (pp: with pp; [ black isort mypy types-requests django-stubs ])) + gettext + nixfmt + ]; + }; + }); }; } From bc922804a146457a1d57aa8ea5735d3284bfbcb1 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 09:32:16 -0500 Subject: [PATCH 56/71] fix: error on creating new hunt's --- core/admin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/admin.py b/core/admin.py index c42e9eb..60b429c 100644 --- a/core/admin.py +++ b/core/admin.py @@ -126,7 +126,7 @@ def save_model(self, request, obj, form, change): # extras = 1 if obj.starting_location else 0 # extras += 1 if obj.ending_location else 0 - + super().save_model(request, obj, form, change) if obj.path_length > obj.middle_locations.count(): messages.warning( request, @@ -137,7 +137,6 @@ def save_model(self, request, obj, form, change): # raise ValidationError( # "The path length is longer than the amount of locations. Please increase the amount of locations or decrease the path length." # ) - super().save_model(request, obj, form, change) class UserAdmin(UserAdmin_): From 759f9ce934743ac6fe650f4bd5a27220249150b2 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 09:33:06 -0500 Subject: [PATCH 57/71] fix: wrong settings for middle locations / ending_location --- core/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index 2317eb1..eb7a3f4 100644 --- a/core/models.py +++ b/core/models.py @@ -226,13 +226,16 @@ class Hunt(models.Model): on_delete=models.PROTECT, related_name="ending_location", blank=True, + null=True, help_text="(Optional) A specified ending location for the hunt. All teams will get as their last location.", ) middle_locations = models.ManyToManyField( QrCode, related_name="hunt", help_text="Possible locations that are not the start or end", - blank=True, + null=False, + blank=False, + ) testers = models.ManyToManyField( User, From 5ce37795b43280c28e86c29de3d2eb13d1280926 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 09:33:54 -0500 Subject: [PATCH 58/71] remove null for M2M --- core/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/models.py b/core/models.py index eb7a3f4..7ffadc6 100644 --- a/core/models.py +++ b/core/models.py @@ -233,9 +233,7 @@ class Hunt(models.Model): QrCode, related_name="hunt", help_text="Possible locations that are not the start or end", - null=False, blank=False, - ) testers = models.ManyToManyField( User, From eb83b0c31c404994bda27116700e5fbb6cf65ba8 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 09:34:35 -0500 Subject: [PATCH 59/71] add migration for prev commits --- ...023_alter_hunt_ending_location_and_more.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 core/migrations/0023_alter_hunt_ending_location_and_more.py diff --git a/core/migrations/0023_alter_hunt_ending_location_and_more.py b/core/migrations/0023_alter_hunt_ending_location_and_more.py new file mode 100644 index 0000000..c61d8a9 --- /dev/null +++ b/core/migrations/0023_alter_hunt_ending_location_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 4.1.13 on 2023-12-05 14:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0022_remove_hunt_early_access_users_hunt_testers"), + ] + + operations = [ + migrations.AlterField( + model_name="hunt", + name="ending_location", + field=models.ForeignKey( + blank=True, + help_text="(Optional) A specified ending location for the hunt. All teams will get as their last location.", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="ending_location", + to="core.qrcode", + ), + ), + migrations.AlterField( + model_name="hunt", + name="middle_locations", + field=models.ManyToManyField( + help_text="Possible locations that are not the start or end", + related_name="hunt", + to="core.qrcode", + ), + ), + migrations.AlterField( + model_name="hunt", + name="path_length", + field=models.PositiveSmallIntegerField( + default=13, + help_text="Length of the path: The amount of codes each time will have to find before the end. (not including start/end)", + ), + ), + migrations.AlterField( + model_name="hunt", + name="starting_location", + field=models.ForeignKey( + blank=True, + help_text="(Optional) A specified starting location for the hunt. All teams will have to scan this QR code as their first.", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="starting_location", + to="core.qrcode", + ), + ), + ] From 1470434b51d68f8b0954d4780cc961053d2b8e78 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 10:57:53 -0500 Subject: [PATCH 60/71] =?UTF-8?q?feat:=20allow=20isolating=20teams=20PER?= =?UTF-8?q?=20EVENT=20=F0=9F=98=B2=F0=9F=98=B2=F0=9F=98=B2=F0=9F=98=B2=20(?= =?UTF-8?q?still=20needs=20some=20fixing)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: add toggle for making teams post start (maybe make it a jsonfield --- core/models.py | 68 +++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/core/models.py b/core/models.py index 7ffadc6..f6e6607 100644 --- a/core/models.py +++ b/core/models.py @@ -7,13 +7,12 @@ from django.contrib.auth.models import AbstractUser from django.core.exceptions import ValidationError from django.db import models -from django.db.models.signals import pre_save +from django.db.models import Func, F, DurationField, Case, DateTimeField, When +from django.db.models.signals import m2m_changed from django.dispatch import receiver from django.utils import timezone from django.utils.html import format_html -from django.db.models import Func, F, DurationField, Case, DateTimeField, When - def generate_invite_code(): return secrets.token_hex(4) @@ -22,21 +21,22 @@ def generate_invite_code(): class User(AbstractUser): metropolis_id = models.IntegerField() refresh_token = models.CharField(max_length=128) - team = models.ForeignKey( - "Team", - related_name="members", - on_delete=models.SET_NULL, - blank=True, - null=True, - ) + + @property + def current_team(self) -> Team | None: + """Returns the team that the user is currently on for the current or upcoming hunt. + If the user is not on a team, return None""" + + current_ = Hunt.current_hunt() + next_ = Hunt.next_hunt() + if current_ is None and next_ is None: + return None + return self.teams.get(hunt=current_) if current_ else self.teams.get(hunt=next_) @property def in_team(self) -> bool: - try: - _ = self.team.solo - return True - except AttributeError: - return False + """Returns True if the user is in a team for the current or upcoming hunt.""" + return self.current_team is not None def generate_hint_key(): @@ -136,28 +136,23 @@ class Meta: class Team(models.Model): - """note, a user can currently be in multiple teams, in the future limit this to one per (class: Hunt)""" - # owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name="teams_ownership") potentially add this later id = models.AutoField(primary_key=True) name = models.CharField(max_length=64, unique=True, null=True) - is_active = models.BooleanField(default=True) is_open = models.BooleanField( default=False ) # todo use this field to have a club-like page so you can join an open team (future feature) current_qr_i = models.IntegerField(default=0) solo = models.BooleanField(default=False) + members = models.ManyToManyField( + related_name="teams", related_query_name="teams", to=User + ) hunt = models.ForeignKey("Hunt", on_delete=models.CASCADE, related_name="teams") def update_current_qr_i(self, i: int): self.current_qr_i = max(self.current_qr_i, i) self.save() - @property - def members(self): - """Returns all members of the team, it's a related manager so to convert to queryset use .all() or filter it.""" - return User.objects.filter(team=str(self.id)) - @property def is_full(self): return self.members.count() >= self.hunt.max_team_size @@ -250,6 +245,10 @@ class Hunt(models.Model): "e.g. {{this form}}, users will only see 'this form' but can click it to get to the form specified above", max_length=250, ) + allow_creation_post_start = models.BooleanField( + default=False, + help_text="Allow users to create teams after the hunt has started", + ) def __str__(self): return self.name @@ -365,14 +364,15 @@ def get_clue(cls, team: Team) -> str | None: return None -@receiver(pre_save, sender=User) -def remove_empty_teams(sender, instance: User, **kwargs): - obj = User.objects.get(id=instance.id) # get the current object from the database - if obj.team is not None and obj.team != instance.team: - print("switching teams") - # raise ValueError( - # "User cannot be in multiple teams at the same time. Please leave your current team before joining a new one." - - if instance.team.members.count() == 0: - print("deleting team") - obj.team.delete() +@receiver(m2m_changed, sender=Team) +def remove_empty_teams(sender, instance: Team, action, **kwargs): + print(sender, instance, action, kwargs) + if action == "post_clear": + try: + instance.delete() + except: + pass + elif action == "post_remove": + if instance.is_empty(): + print("Deleting empty team: ", instance.name) + instance.delete() From e973ba29a2a7d7c5345a9ad165d841e0d5779868 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 10:59:25 -0500 Subject: [PATCH 61/71] ADMIN redo admin actions add filters for teams --- core/admin.py | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/core/admin.py b/core/admin.py index 60b429c..06c6642 100644 --- a/core/admin.py +++ b/core/admin.py @@ -11,24 +11,22 @@ @admin.action( permissions=["change"], - description=_("Set selected users as a Location Setter"), + description=_("Set selected users as a Logic Puzzle Setter"), ) -def set_as_location_setter(modeladmin, request, queryset: QuerySet[User]): +def set_as_logic_setter(modeladmin, request, queryset: QuerySet[User]): for user in queryset: user.is_staff = True # set their group to location setter - user.groups.add(Group.objects.get(name="Location Setter")) + user.groups.add(Group.objects.get_or_create(name="Logic Logic Puzzle Setters")) @admin.action( permissions=["change"], - description=_("Set selected users as a Logic Puzzle Setter"), + description=_("Remove selected users as a Logic Puzzle Setter"), ) -def set_as_logic_setter(modeladmin, request, queryset: QuerySet[User]): +def remove_as_logic_setter(modeladmin, request, queryset: QuerySet[User]): for user in queryset: - user.is_staff = True - # set their group to location setter - user.groups.add(Group.objects.get(name="Logic Logic Puzzle Setters")) + user.groups.remove(Group.objects.get(name="Logic Logic Puzzle Setters")) class HintsInLine(admin.StackedInline): @@ -54,26 +52,13 @@ class LogicPuzzleAdmin(admin.ModelAdmin): ordering = ("qr_index",) -@admin.action(description="Mark selected teams as inactive") -def set_inactive(modeladmin, request, queryset): - if request.user.is_superuser: - queryset.update(is_active=False) - - -@admin.action(description="Mark selected teams as active") -def set_active( - modeladmin, request, queryset -): # note you could probably remove this one. - if request.user.is_superuser: - queryset.update(is_active=True) - - class TeamAdmin(admin.ModelAdmin): readonly_fields = ("path", "members") inlines = [ InviteInLine, ] - actions = [set_inactive, set_active] + search_fields = ("name", "members__username") + list_filter = ("hunt",) @admin.display(description="Path") def path(self, team): @@ -146,7 +131,7 @@ class UserAdmin(UserAdmin_): "last_name", "email", ) - actions = [set_as_location_setter, set_as_logic_setter] + actions = [set_as_logic_setter, remove_as_logic_setter] admin_field = list(UserAdmin_.fieldsets) admin_field[0][1]["fields"] = ( "username", From 57ef8111080a28d7b61710373cd39e8a458c69a9 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Tue, 5 Dec 2023 11:02:00 -0500 Subject: [PATCH 62/71] optimize imports --- core/admin.py | 4 +- core/management/commands/init.py | 2 +- core/management/commands/new_event.py | 3 +- core/migrations/0001_initial.py | 5 +-- .../0003_remove_team_members_user_team.py | 2 +- .../0004_qrcode_short_alter_invite_code.py | 3 +- .../0007_qrcode_code_alter_invite_code.py | 3 +- ...e_code_alter_qrcode_key_alter_team_name.py | 3 +- ...15_alter_hint_options_alter_invite_team.py | 2 +- core/migrations/0017_alter_user_team.py | 2 +- ...unt_logicpuzzlehint_belongs_to_and_more.py | 6 +-- ...023_alter_hunt_ending_location_and_more.py | 2 +- ...eam_is_active_remove_user_team_and_more.py | 38 +++++++++++++++++++ core/static/core/overcast.svg | 2 +- core/templates/core/base.html | 2 +- core/templatetags/base.py | 1 - core/templatetags/md.py | 2 +- core/urls.py | 1 + core/views/auth.py | 20 +++++----- core/views/index.py | 2 +- core/views/team.py | 11 ++---- 21 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 core/migrations/0024_remove_team_is_active_remove_user_team_and_more.py diff --git a/core/admin.py b/core/admin.py index 06c6642..b55ec51 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,10 +1,10 @@ from django.contrib import admin, messages from django.contrib.auth.admin import UserAdmin as UserAdmin_ from django.contrib.auth.models import Group +from django.db.models import QuerySet +from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ -from django.urls import reverse -from django.db.models import QuerySet from .forms import * diff --git a/core/management/commands/init.py b/core/management/commands/init.py index 4fae3b6..a26ae08 100644 --- a/core/management/commands/init.py +++ b/core/management/commands/init.py @@ -1,5 +1,5 @@ -from django.core.management import BaseCommand from django.contrib.auth.models import Group, Permission +from django.core.management import BaseCommand from core import models diff --git a/core/management/commands/new_event.py b/core/management/commands/new_event.py index 3ecb6b8..1c8b667 100644 --- a/core/management/commands/new_event.py +++ b/core/management/commands/new_event.py @@ -2,11 +2,12 @@ import os import time -from core.models import Team from django.core.management import BaseCommand from django.core.serializers import serialize from django.core.serializers.json import DjangoJSONEncoder +from core.models import Team + class LazyEncoder(DjangoJSONEncoder): def default(self, obj): diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 44cd690..45e4f26 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,11 +1,10 @@ # Generated by Django 4.1.3 on 2022-12-01 06:08 -from django.conf import settings import django.contrib.auth.models import django.contrib.auth.validators -from django.db import migrations, models -import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0003_remove_team_members_user_team.py b/core/migrations/0003_remove_team_members_user_team.py index 4395342..af046e1 100644 --- a/core/migrations/0003_remove_team_members_user_team.py +++ b/core/migrations/0003_remove_team_members_user_team.py @@ -1,7 +1,7 @@ # Generated by Django 4.1.3 on 2022-12-01 06:27 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0004_qrcode_short_alter_invite_code.py b/core/migrations/0004_qrcode_short_alter_invite_code.py index 2b0db47..609643f 100644 --- a/core/migrations/0004_qrcode_short_alter_invite_code.py +++ b/core/migrations/0004_qrcode_short_alter_invite_code.py @@ -1,8 +1,9 @@ # Generated by Django 4.1.3 on 2022-12-01 06:32 -import core.models from django.db import migrations, models +import core.models + class Migration(migrations.Migration): dependencies = [ diff --git a/core/migrations/0007_qrcode_code_alter_invite_code.py b/core/migrations/0007_qrcode_code_alter_invite_code.py index 5c36b94..1ae4a6b 100644 --- a/core/migrations/0007_qrcode_code_alter_invite_code.py +++ b/core/migrations/0007_qrcode_code_alter_invite_code.py @@ -1,8 +1,9 @@ # Generated by Django 4.1.3 on 2022-12-05 14:41 -import core.models from django.db import migrations, models +import core.models + def gen_hint_keys(apps, schema_editor): QRCode = apps.get_model("core", "qrcode") diff --git a/core/migrations/0008_alter_invite_code_alter_qrcode_key_alter_team_name.py b/core/migrations/0008_alter_invite_code_alter_qrcode_key_alter_team_name.py index f62d793..530d374 100644 --- a/core/migrations/0008_alter_invite_code_alter_qrcode_key_alter_team_name.py +++ b/core/migrations/0008_alter_invite_code_alter_qrcode_key_alter_team_name.py @@ -1,8 +1,9 @@ # Generated by Django 4.1.3 on 2022-12-05 19:56 -import core.models from django.db import migrations, models +import core.models + class Migration(migrations.Migration): dependencies = [ diff --git a/core/migrations/0015_alter_hint_options_alter_invite_team.py b/core/migrations/0015_alter_hint_options_alter_invite_team.py index 98ca8eb..82d97d5 100644 --- a/core/migrations/0015_alter_hint_options_alter_invite_team.py +++ b/core/migrations/0015_alter_hint_options_alter_invite_team.py @@ -1,7 +1,7 @@ # Generated by Django 4.1.3 on 2022-12-13 00:16 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0017_alter_user_team.py b/core/migrations/0017_alter_user_team.py index e9c620e..72b8498 100644 --- a/core/migrations/0017_alter_user_team.py +++ b/core/migrations/0017_alter_user_team.py @@ -1,7 +1,7 @@ # Generated by Django 4.1.4 on 2022-12-19 01:25 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py b/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py index 695ff3e..9edac03 100644 --- a/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py +++ b/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py @@ -1,10 +1,10 @@ # Generated by Django 4.1.13 on 2023-12-02 20:46 +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models from django.utils import timezone import core.models -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion def create_hunt(apps, schema_editor): diff --git a/core/migrations/0023_alter_hunt_ending_location_and_more.py b/core/migrations/0023_alter_hunt_ending_location_and_more.py index c61d8a9..dba6ca4 100644 --- a/core/migrations/0023_alter_hunt_ending_location_and_more.py +++ b/core/migrations/0023_alter_hunt_ending_location_and_more.py @@ -1,7 +1,7 @@ # Generated by Django 4.1.13 on 2023-12-05 14:34 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/core/migrations/0024_remove_team_is_active_remove_user_team_and_more.py b/core/migrations/0024_remove_team_is_active_remove_user_team_and_more.py new file mode 100644 index 0000000..669a65b --- /dev/null +++ b/core/migrations/0024_remove_team_is_active_remove_user_team_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.13 on 2023-12-05 15:54 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0023_alter_hunt_ending_location_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="team", + name="is_active", + ), + migrations.RemoveField( + model_name="user", + name="team", + ), + migrations.AddField( + model_name="hunt", + name="allow_creation_post_start", + field=models.BooleanField( + default=False, + help_text="Allow users to create teams after the hunt has started", + ), + ), + migrations.AddField( + model_name="team", + name="members", + field=models.ManyToManyField( + related_name="teams", + related_query_name="teams", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/core/static/core/overcast.svg b/core/static/core/overcast.svg index 2040775..6e7d457 100644 --- a/core/static/core/overcast.svg +++ b/core/static/core/overcast.svg @@ -1,4 +1,4 @@ - +

{% block header %}{% endblock %}