From 4d8b2ba4ee13c3205b52b0fdd3493953d1156bb8 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 10 Dec 2022 22:04:47 -0500 Subject: [PATCH 1/6] feat: added /current page note: untested. --- scavenger2022/core/models.py | 16 ++++++++++------ scavenger2022/core/urls.py | 1 + scavenger2022/core/views/qr.py | 23 +++++++++++++++++++++-- scavenger2022/scavenger2022/settings.py | 4 ++-- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/scavenger2022/core/models.py b/scavenger2022/core/models.py index 9e7e65a..dd364a9 100644 --- a/scavenger2022/core/models.py +++ b/scavenger2022/core/models.py @@ -100,10 +100,8 @@ def next_code(self) -> QrCode: @property def members(self): - return User.objects.filter(team=self) - - def is_solo(self): - return self.members.count() == 1 + """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)) def is_full(self): return self.members.count() >= settings.MAX_TEAM_SIZE @@ -113,8 +111,8 @@ def join(self, user: User): return if self.is_full(): raise IndexError("Team is full") - self.members.add(user) - self.save() + user.team = self + user.save() def invites(self): return Invite.objects.filter(team=self) @@ -122,6 +120,12 @@ def invites(self): def __str__(self): return str(self.name) + # def delete(self, *args, **kwargs): + # print('Deleting instance 1:', User.objects.all().first().team) + # print('Deleting instance:', User.objects.all().first()) + # self.member.all().update(team=None, chosen=False) + # super().delete(*args, **kwargs) + def generate_invite_code(): return secrets.token_hex(3) diff --git a/scavenger2022/core/urls.py b/scavenger2022/core/urls.py index 6010b21..3e48591 100644 --- a/scavenger2022/core/urls.py +++ b/scavenger2022/core/urls.py @@ -9,6 +9,7 @@ path("logout/", auth.account_logout, name="account_logout"), path("qr/", qr.qr, name="qr"), path("first", qr.qr_first, name="qr_first"), + path("current", qr.qr_current, name="qr_current"), path("team/join/", team.join, name="join"), path("team/new", team.make, name="team_new"), path("team/solo", team.solo, name="team_solo"), diff --git a/scavenger2022/core/views/qr.py b/scavenger2022/core/views/qr.py index 2d74ddc..340b4f8 100644 --- a/scavenger2022/core/views/qr.py +++ b/scavenger2022/core/views/qr.py @@ -50,7 +50,7 @@ def wrapped(*args, **kwargs): def qr(request, key): context = dict(first=False) context["qr"] = qr = get_object_or_404(QrCode, key=key) - i = (codes := QrCode.code_pks(request.user)).index(qr.id) + 1 + i = (codes := QrCode.code_pks(request.user.team)).index(qr.id) + 1 context["nextqr"] = None if len(codes) <= i else QrCode.objects.get(id=codes[i]) return render(request, "core/qr.html", context=context) @@ -62,6 +62,25 @@ def qr(request, key): def qr_first(request): context = dict(first=True) context["qr"] = qr = QrCode.codes(request.user)[0] - i = (codes := QrCode.code_pks(request.user)).index(qr.id) + 1 + i = (codes := QrCode.code_pks(request.user.team)).index( + qr.id + ) + 1 # todo this might not be the best way to do this as we are just adding 1 to the index of the first qr code + context["nextqr"] = None if len(codes) <= i else QrCode.objects.get(id=codes[i]) + return render(request, "core/qr.html", context=context) + + +@login_required +@require_http_methods(("GET", "POST")) +@team_required +@after_cutoff +def qr_current(request): + context = dict(first=False) + print(request.user.first_name) + print(request.user.team) + cQR = request.user.team.current_qr_code + context["qr"] = qr = QrCode.objects.get(id=cQR) + i = (codes := QrCode.code_pks(request.user)).index( + qr.id + ) + 1 # note: this might not work, just coping implementation from qr_first context["nextqr"] = None if len(codes) <= i else QrCode.objects.get(id=codes[i]) return render(request, "core/qr.html", context=context) diff --git a/scavenger2022/scavenger2022/settings.py b/scavenger2022/scavenger2022/settings.py index 6b3ad3a..a649343 100644 --- a/scavenger2022/scavenger2022/settings.py +++ b/scavenger2022/scavenger2022/settings.py @@ -129,5 +129,5 @@ except IOError: raise TypeError("local_settings.py not found") -SECRET_KEY # set in local_settings.py -CUTOFF # same here +# SECRET_KEY # set in local_settings.py +# CUTOFF # same here From be0f30503d98435bb1d19075094489f89eaedd76 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 12 Dec 2022 18:10:17 -0500 Subject: [PATCH 2/6] added logic puzzle stuff. --- scavenger2022/core/admin.py | 8 ++- ...0013_logicpuzzlehint_alter_qrcode_notes.py | 38 +++++++++++++ scavenger2022/core/models.py | 53 +++++++++++++++---- scavenger2022/core/views/qr.py | 5 +- scavenger2022/scavenger2022/settings.py | 4 +- 5 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 scavenger2022/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py diff --git a/scavenger2022/core/admin.py b/scavenger2022/core/admin.py index 96b42fc..ac954a3 100644 --- a/scavenger2022/core/admin.py +++ b/scavenger2022/core/admin.py @@ -4,7 +4,6 @@ from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _l from django.urls import reverse -from .models import * from .forms import * @@ -19,6 +18,12 @@ class InviteInLine(admin.StackedInline): readonly_fields = ("invites",) +class LogicPuzzleAdmin(admin.ModelAdmin): + list_display = ("qr_index", "hint",) + search_fields = ("hint", "qr_index",) + ordering = ("qr_index",) + + class TeamAdmin(admin.ModelAdmin): readonly_fields = ("current_qr_code", "path") inlines = [ @@ -68,3 +73,4 @@ class UserAdmin(UserAdmin_): admin.site.register(User, UserAdmin) admin.site.register(Team, TeamAdmin) admin.site.register(QrCode, QrCodeAdmin) +admin.site.register(LogicPuzzleHint, LogicPuzzleAdmin) diff --git a/scavenger2022/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py b/scavenger2022/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py new file mode 100644 index 0000000..a12b205 --- /dev/null +++ b/scavenger2022/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py @@ -0,0 +1,38 @@ +# Generated by Django 4.1.3 on 2022-12-12 22:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0012_remove_user_chosen"), + ] + + operations = [ + migrations.CreateModel( + name="LogicPuzzleHint", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "hint", + models.TextField( + help_text="Hint for the logic puzzle", max_length=1024 + ), + ), + ("notes", models.TextField(blank=True, help_text="Internal notes")), + ( + "qr_index", + models.IntegerField( + help_text="The index of the QR code that this hint is for, that is everyone on their nth QR code will get same same hint (starting from 1)", + unique=True, + ), + ), + ], + ), + migrations.AlterField( + model_name="qrcode", + name="notes", + field=models.TextField(blank=True, help_text="Internal notes"), + ), + ] diff --git a/scavenger2022/core/models.py b/scavenger2022/core/models.py index dd364a9..0712a16 100644 --- a/scavenger2022/core/models.py +++ b/scavenger2022/core/models.py @@ -1,9 +1,9 @@ import random import secrets +from django.conf import settings from django.contrib.auth.models import AbstractUser from django.db import models -from django.conf import settings class User(AbstractUser): @@ -28,9 +28,7 @@ class QrCode(models.Model): max_length=1024, help_text="Location of the QR code. Be specific—it's internal", ) - notes = models.TextField( - help_text="Internal notes", - ) + notes = models.TextField(help_text="Internal notes", blank=True) key = models.CharField(max_length=64, unique=True, default=generate_hint_key) def __str__(self): @@ -117,15 +115,16 @@ def join(self, user: User): def invites(self): return Invite.objects.filter(team=self) + def get_qr_nth(self): + """Get the total amount of qr codes the team has completed""" + print( + int(self.completed_qr_codes.count()) + 1 + ) # todo remove. this is just for debugging + return int(self.completed_qr_codes.count()) + 1 + def __str__(self): return str(self.name) - # def delete(self, *args, **kwargs): - # print('Deleting instance 1:', User.objects.all().first().team) - # print('Deleting instance:', User.objects.all().first()) - # self.member.all().update(team=None, chosen=False) - # super().delete(*args, **kwargs) - def generate_invite_code(): return secrets.token_hex(3) @@ -135,3 +134,37 @@ class Invite(models.Model): invites = models.IntegerField(default=0) team = models.ForeignKey(Team, on_delete=models.CASCADE) code = models.CharField(max_length=32, unique=True) + + +# class Hunt(models.Model): +# id = models.AutoField(primary_key=True) +# name = models.CharField(max_length=64) +# start = models.DateTimeField() +# end = models.DateTimeField() +# is_active = models.BooleanField(default=False) +# team_size = models.IntegerField(default=4, help_text="Max Team size") +# final_qr_id = models.IntegerField(null=True, blank=True) +# +# def __str__(self): +# return self.name + + +class LogicPuzzleHint(models.Model): + id = models.AutoField(primary_key=True) + hint = models.TextField( + max_length=1024, + help_text="Hint for the logic puzzle", + ) + notes = models.TextField(help_text="Internal notes", blank=True) + qr_index = models.IntegerField( + help_text="The index of the QR code that this hint is for, that is everyone on their nth QR code will get same same hint (starting from 1)", + unique=True, + ) + + # belongs_to = models.ForeignKey(Hunt, related_name="logic_puzzle_hunt", on_delete=models.CASCADE) + + def __str__(self): + return str(self.hint) + + def get_hint(self, team: Team): + return self.objects.get(qr_index=team.get_qr_nth()).hint diff --git a/scavenger2022/core/views/qr.py b/scavenger2022/core/views/qr.py index 340b4f8..b21daf2 100644 --- a/scavenger2022/core/views/qr.py +++ b/scavenger2022/core/views/qr.py @@ -7,7 +7,7 @@ from django.views.decorators.http import require_http_methods from django.shortcuts import render, redirect, reverse, get_object_or_404 from django.utils.translation import gettext as _ -from ..models import QrCode +from ..models import QrCode, LogicPuzzleHint def team_required(f): @@ -52,6 +52,7 @@ def qr(request, key): context["qr"] = qr = get_object_or_404(QrCode, key=key) i = (codes := QrCode.code_pks(request.user.team)).index(qr.id) + 1 context["nextqr"] = None if len(codes) <= i else QrCode.objects.get(id=codes[i]) + context["logic_hint"] = LogicPuzzleHint.get_hint(request.user.team) return render(request, "core/qr.html", context=context) @@ -66,6 +67,7 @@ def qr_first(request): qr.id ) + 1 # todo this might not be the best way to do this as we are just adding 1 to the index of the first qr code context["nextqr"] = None if len(codes) <= i else QrCode.objects.get(id=codes[i]) + context["logic_hint"] = LogicPuzzleHint.get_hint(request.user.team) return render(request, "core/qr.html", context=context) @@ -83,4 +85,5 @@ def qr_current(request): qr.id ) + 1 # note: this might not work, just coping implementation from qr_first context["nextqr"] = None if len(codes) <= i else QrCode.objects.get(id=codes[i]) + context["logic_hint"] = LogicPuzzleHint.get_hint(request.user.team) return render(request, "core/qr.html", context=context) diff --git a/scavenger2022/scavenger2022/settings.py b/scavenger2022/scavenger2022/settings.py index a649343..30b580f 100644 --- a/scavenger2022/scavenger2022/settings.py +++ b/scavenger2022/scavenger2022/settings.py @@ -1,6 +1,7 @@ import os from pathlib import Path +from typing import Final # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -121,7 +122,8 @@ me_url=f"{base_url}/api/me/internal", scope="me_meta internal", ) -MAX_TEAM_SIZE = 4 +MAX_TEAM_SIZE: Final[int] = 4 +FINAL_QR_ID: Final[int] = 0 try: with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f: From 1bc84a434abf4df92a00fccd1afc2f3e20882f5b Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 12 Dec 2022 18:49:12 -0500 Subject: [PATCH 3/6] fixed team/solo check --- scavenger2022/core/admin.py | 10 ++++++++-- scavenger2022/core/models.py | 8 ++++++++ scavenger2022/core/templates/core/base.html | 7 ++----- scavenger2022/core/templates/core/index.html | 2 +- scavenger2022/core/views/team.py | 3 ++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/scavenger2022/core/admin.py b/scavenger2022/core/admin.py index ac954a3..becc6b7 100644 --- a/scavenger2022/core/admin.py +++ b/scavenger2022/core/admin.py @@ -19,8 +19,14 @@ class InviteInLine(admin.StackedInline): class LogicPuzzleAdmin(admin.ModelAdmin): - list_display = ("qr_index", "hint",) - search_fields = ("hint", "qr_index",) + list_display = ( + "qr_index", + "hint", + ) + search_fields = ( + "hint", + "qr_index", + ) ordering = ("qr_index",) diff --git a/scavenger2022/core/models.py b/scavenger2022/core/models.py index 0712a16..2649eec 100644 --- a/scavenger2022/core/models.py +++ b/scavenger2022/core/models.py @@ -13,6 +13,14 @@ class User(AbstractUser): "Team", related_name="members", on_delete=models.CASCADE, blank=True, null=True ) + @property + def in_team(self) -> bool: + try: + _ = self.team.solo + return True + except AttributeError: + return False + def generate_hint_key(): return secrets.token_urlsafe(48) diff --git a/scavenger2022/core/templates/core/base.html b/scavenger2022/core/templates/core/base.html index 43b2279..654ab66 100644 --- a/scavenger2022/core/templates/core/base.html +++ b/scavenger2022/core/templates/core/base.html @@ -52,10 +52,7 @@ Metropolis logo Metropolis - - Doodles - Scavenger Hunt (2022) @@ -78,8 +75,8 @@ {% if request.user.is_staff %} {% translate "Admin" %} {% endif %} - {% if request.user.team is not None %} - {% if request.user.team.solo %} + {% if request.user.in_team %} + {% if not request.user.team.solo %} Team: {{ request.user.team.name }} {% else %} Solo diff --git a/scavenger2022/core/templates/core/index.html b/scavenger2022/core/templates/core/index.html index dc2bad9..1d456e3 100644 --- a/scavenger2022/core/templates/core/index.html +++ b/scavenger2022/core/templates/core/index.html @@ -9,7 +9,7 @@ {% if not request.user.is_authenticated %} Please login. {% else %} - {% if request.user.team is not None %} + {% if not request.user.in_team %}
  • Join a team: either scan your team leader's QR code or click the link
  • Make a team
  • diff --git a/scavenger2022/core/views/team.py b/scavenger2022/core/views/team.py index 2f19145..95a162a 100644 --- a/scavenger2022/core/views/team.py +++ b/scavenger2022/core/views/team.py @@ -86,6 +86,7 @@ def make(request): @login_required def solo(q): - q.user.team = Team(solo=True).save() + q.user.team = Team(solo=True) + q.user.team.save() q.user.save() return redirect(reverse("index")) From 0407a911ab166fe8e7d04334e8f6a3b9fb2d89a4 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 12 Dec 2022 19:18:08 -0500 Subject: [PATCH 4/6] added merge migration. style: renamed CUTON -> END renamed CUTOFF -> START both for simplicity also removed some useless imports --- scavenger2022/core/context_processors.py | 14 +++---- .../migrations/0014_merge_20221212_1857.py | 13 ++++++ ...15_alter_hint_options_alter_invite_team.py | 27 ++++++++++++ scavenger2022/core/models.py | 2 +- scavenger2022/core/static/core/base.css | 2 +- scavenger2022/core/templates/core/base.html | 42 +++++++++---------- scavenger2022/core/templatetags/qr.py | 1 - scavenger2022/core/views/qr.py | 24 +++++------ scavenger2022/core/views/team.py | 4 +- scavenger2022/scavenger2022/settings.py | 6 +-- 10 files changed, 87 insertions(+), 48 deletions(-) create mode 100644 scavenger2022/core/migrations/0014_merge_20221212_1857.py create mode 100644 scavenger2022/core/migrations/0015_alter_hint_options_alter_invite_team.py diff --git a/scavenger2022/core/context_processors.py b/scavenger2022/core/context_processors.py index 5acf991..77dad5f 100644 --- a/scavenger2022/core/context_processors.py +++ b/scavenger2022/core/context_processors.py @@ -2,12 +2,12 @@ import datetime -def cutoff(request): +def start(request): return dict( - CUTOFF=(cutoff := settings.CUTOFF), - CUTOFF_BEFORE=cutoff > datetime.datetime.utcnow(), - CUTOFF_UNTIL=cutoff - datetime.datetime.utcnow(), - CUTON=(cuton := settings.CUTON), - CUTON_BEFORE=cuton > datetime.datetime.utcnow(), - CUTON_UNTIL=cuton - datetime.datetime.utcnow(), + 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(), ) diff --git a/scavenger2022/core/migrations/0014_merge_20221212_1857.py b/scavenger2022/core/migrations/0014_merge_20221212_1857.py new file mode 100644 index 0000000..120a7de --- /dev/null +++ b/scavenger2022/core/migrations/0014_merge_20221212_1857.py @@ -0,0 +1,13 @@ +# Generated by Django 4.1.3 on 2022-12-12 23:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0013_logicpuzzlehint_alter_qrcode_notes"), + ("core", "0013_remove_team_completed_qr_codes_and_more"), + ] + + operations = [] diff --git a/scavenger2022/core/migrations/0015_alter_hint_options_alter_invite_team.py b/scavenger2022/core/migrations/0015_alter_hint_options_alter_invite_team.py new file mode 100644 index 0000000..d1452b0 --- /dev/null +++ b/scavenger2022/core/migrations/0015_alter_hint_options_alter_invite_team.py @@ -0,0 +1,27 @@ +# Generated by Django 4.1.3 on 2022-12-13 00:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0014_merge_20221212_1857"), + ] + + operations = [ + migrations.AlterModelOptions( + name="hint", + options={"permissions": [("view_before_start", "Play game before start")]}, + ), + migrations.AlterField( + model_name="invite", + name="team", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="invites", + to="core.team", + ), + ), + ] diff --git a/scavenger2022/core/models.py b/scavenger2022/core/models.py index 035a586..4eb6418 100644 --- a/scavenger2022/core/models.py +++ b/scavenger2022/core/models.py @@ -78,7 +78,7 @@ def __str__(self): return self.hint class Meta: - permissions = [("view_before_cutoff", "Play game before cutoff")] + permissions = [("view_before_start", "Play game before start")] class Team(models.Model): diff --git a/scavenger2022/core/static/core/base.css b/scavenger2022/core/static/core/base.css index a3c54c0..7461d34 100644 --- a/scavenger2022/core/static/core/base.css +++ b/scavenger2022/core/static/core/base.css @@ -41,7 +41,7 @@ body > nav { padding: 4px; } -#cutoff-header { +#start-header { text-align: center; } diff --git a/scavenger2022/core/templates/core/base.html b/scavenger2022/core/templates/core/base.html index 2a84a23..a28af01 100644 --- a/scavenger2022/core/templates/core/base.html +++ b/scavenger2022/core/templates/core/base.html @@ -9,20 +9,20 @@ - {% if CUTOFF_BEFORE or CUTON_BEFORE %} + {% if START_BEFORE or END_BEFORE %} {% endif %} @@ -88,18 +88,18 @@ - {% if CUTOFF_BEFORE %} -