From 9b82f76f2ac8e6d549927a5a629881230c8ee528 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sat, 2 Dec 2023 14:52:47 -0500 Subject: [PATCH 01/22] Added set as location settter action renamed l_ to _ --- core/admin.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/core/admin.py b/core/admin.py index 0ceebec..ba09d98 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,10 +1,34 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as UserAdmin_ +from django.contrib.auth.models import Group from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _l +from django.utils.translation import gettext_lazy as _ from django.urls import reverse +from django.db.models import QuerySet + from .forms import * +@admin.action( + permissions=["change"], + description=_("Set selected users as a Location Setter"), +) +def set_as_location_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")) + + +@admin.action( + permissions=["change"], + description=_("Set selected users as a Logic Puzzle Setter"), +) +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="Logic Logic Puzzle Setters")) + class HintsInLine(admin.StackedInline): model = Hint @@ -70,7 +94,7 @@ def url(self, qr): return format_html( mark_safe('{}'), (url := reverse("qr", kwargs=dict(key=qr.key))), - _l("Link to Hint Page"), + _("Link to Hint Page"), ) else: return "" @@ -83,6 +107,7 @@ class UserAdmin(UserAdmin_): "last_name", "email", ) + actions = [set_as_location_setter, set_as_logic_setter] admin_field = list(UserAdmin_.fieldsets) admin_field[0][1]["fields"] = ( "username", From b4d0765b3c008e62733382522b9f1a9a0f0301a9 Mon Sep 17 00:00:00 2001 From: Jason Cameron Date: Sat, 2 Dec 2023 15:56:34 -0500 Subject: [PATCH 02/22] Added Hunt model to have continuity throughout hunts. Style: Format --- .idea/misc.xml | 4 +- .idea/workspace.xml | 58 ++++--- core/admin.py | 22 ++- core/migrations/0001_initial.py | 49 ++++-- .../0004_qrcode_short_alter_invite_code.py | 4 +- ...e_code_alter_qrcode_key_alter_team_name.py | 4 +- core/migrations/0009_team_solo.py | 5 +- ...0013_logicpuzzlehint_alter_qrcode_notes.py | 13 +- ...unt_logicpuzzlehint_belongs_to_and_more.py | 153 ++++++++++++++++++ core/models.py | 75 +++++++-- core/templates/core/credits.html | 12 +- core/templates/core/qr.html | 3 +- core/templatetags/qr.py | 14 ++ core/views/auth.py | 6 +- core/views/index.py | 4 +- core/views/qr.py | 3 +- core/views/team.py | 3 +- 17 files changed, 365 insertions(+), 67 deletions(-) create mode 100644 core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py diff --git a/.idea/misc.xml b/.idea/misc.xml index 995cec4..b1ea2f1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - - \ No newline at end of file + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 92f74f9..8a03b4c 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,10 +4,10 @@ - + - + - { + "keyToString": { + "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": "C:/Users/jason/projects/metro/venv", + "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": "settings.sync", + "vue.rearranger.settings.migration": "true" } -}]]> +} @@ -153,6 +160,10 @@ + + + + - @@ -300,7 +319,8 @@ - diff --git a/core/admin.py b/core/admin.py index ba09d98..1656957 100644 --- a/core/admin.py +++ b/core/admin.py @@ -8,6 +8,7 @@ from .forms import * + @admin.action( permissions=["change"], description=_("Set selected users as a Location Setter"), @@ -77,12 +78,22 @@ class TeamAdmin(admin.ModelAdmin): @admin.display(description="Path") def path(self, team): return "\n".join( - map(lambda pk: str(QrCode.objects.get(id=pk)), QrCode.code_pks(team)) + map( + lambda pk: str(QrCode.objects.get(id=pk)), + QrCode.code_pks(team), + ) ) class QrCodeAdmin(admin.ModelAdmin): - fields = ["short", "location", "notes", "key", "image_tag", "image_url"] + fields = [ + "short", + "location", + "notes", + "key", + "image_tag", + "image_url", + ] readonly_fields = ["url", "key", "image_tag"] list_display = ["location", "url"] inlines = [HintsInLine] @@ -115,7 +126,10 @@ class UserAdmin(UserAdmin_): fieldsets = tuple( admin_field + [ - ("Metropolis Integration (OAuth)", dict(fields=["metropolis_id"])), + ( + "Metropolis Integration (OAuth)", + dict(fields=["metropolis_id"]), + ), ("Game", dict(fields=["team"])), ] ) @@ -125,3 +139,5 @@ class UserAdmin(UserAdmin_): admin.site.register(Team, TeamAdmin) admin.site.register(QrCode, QrCodeAdmin) admin.site.register(LogicPuzzleHint, LogicPuzzleAdmin) +admin.site.register(Hunt) + diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index b3d4039..44cd690 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -28,11 +28,16 @@ class Migration(migrations.Migration): verbose_name="ID", ), ), - ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "password", + models.CharField(max_length=128, verbose_name="password"), + ), ( "last_login", models.DateTimeField( - blank=True, null=True, verbose_name="last login" + blank=True, + null=True, + verbose_name="last login", ), ), ( @@ -61,19 +66,25 @@ class Migration(migrations.Migration): ( "first_name", models.CharField( - blank=True, max_length=150, verbose_name="first name" + blank=True, + max_length=150, + verbose_name="first name", ), ), ( "last_name", models.CharField( - blank=True, max_length=150, verbose_name="last name" + blank=True, + max_length=150, + verbose_name="last name", ), ), ( "email", models.EmailField( - blank=True, max_length=254, verbose_name="email address" + blank=True, + max_length=254, + verbose_name="email address", ), ), ( @@ -95,7 +106,8 @@ class Migration(migrations.Migration): ( "date_joined", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" + default=django.utils.timezone.now, + verbose_name="date joined", ), ), ("metropolis_id", models.IntegerField()), @@ -135,7 +147,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name="QrCode", fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "id", + models.AutoField(primary_key=True, serialize=False), + ), ( "location", models.CharField( @@ -152,21 +167,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name="Team", fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "id", + models.AutoField(primary_key=True, serialize=False), + ), ("name", models.CharField(max_length=64, unique=True)), ("is_active", models.BooleanField(default=True)), ("is_open", models.BooleanField(default=False)), - ("current_qr_code", models.IntegerField(blank=True, null=True)), + ( + "current_qr_code", + models.IntegerField(blank=True, null=True), + ), ( "completed_qr_codes", models.ManyToManyField( - blank=True, related_name="completed_qr_codes", to="core.qrcode" + blank=True, + related_name="completed_qr_codes", + to="core.qrcode", ), ), ( "members", models.ManyToManyField( - related_name="team", to=settings.AUTH_USER_MODEL + related_name="team", + to=settings.AUTH_USER_MODEL, ), ), ], @@ -188,7 +212,8 @@ class Migration(migrations.Migration): ( "team", models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="core.team" + on_delete=django.db.models.deletion.CASCADE, + to="core.team", ), ), ], diff --git a/core/migrations/0004_qrcode_short_alter_invite_code.py b/core/migrations/0004_qrcode_short_alter_invite_code.py index 9160175..2b0db47 100644 --- a/core/migrations/0004_qrcode_short_alter_invite_code.py +++ b/core/migrations/0004_qrcode_short_alter_invite_code.py @@ -24,7 +24,9 @@ class Migration(migrations.Migration): model_name="invite", name="code", field=models.CharField( - default=core.models.generate_invite_code, max_length=32, unique=True + default=core.models.generate_invite_code, + max_length=32, + unique=True, ), ), ] 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 07094ea..f62d793 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 @@ -19,7 +19,9 @@ class Migration(migrations.Migration): model_name="qrcode", name="key", field=models.CharField( - default=core.models.generate_hint_key, max_length=64, unique=True + default=core.models.generate_hint_key, + max_length=64, + unique=True, ), ), migrations.AlterField( diff --git a/core/migrations/0009_team_solo.py b/core/migrations/0009_team_solo.py index aeaaece..1f947d2 100644 --- a/core/migrations/0009_team_solo.py +++ b/core/migrations/0009_team_solo.py @@ -5,7 +5,10 @@ class Migration(migrations.Migration): dependencies = [ - ("core", "0008_alter_invite_code_alter_qrcode_key_alter_team_name"), + ( + "core", + "0008_alter_invite_code_alter_qrcode_key_alter_team_name", + ), ] operations = [ diff --git a/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py b/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py index 7ca9dc2..95f8b20 100644 --- a/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py +++ b/core/migrations/0013_logicpuzzlehint_alter_qrcode_notes.py @@ -12,14 +12,21 @@ class Migration(migrations.Migration): migrations.CreateModel( name="LogicPuzzleHint", fields=[ - ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "id", + models.AutoField(primary_key=True, serialize=False), + ), ( "hint", models.TextField( - help_text="Hint for the logic puzzle", max_length=1024 + help_text="Hint for the logic puzzle", + max_length=1024, ), ), - ("notes", models.TextField(blank=True, help_text="Internal notes")), + ( + "notes", + models.TextField(blank=True, help_text="Internal notes"), + ), ( "qr_index", models.IntegerField( 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 new file mode 100644 index 0000000..769f895 --- /dev/null +++ b/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py @@ -0,0 +1,153 @@ +# Generated by Django 4.1.13 on 2023-12-02 20:46 +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): + # Create a Hunt object if needed + from core.models import Hunt, QrCode + + if QrCode.objects.count() == 0: + return + # COULD ERROR IF ONLY ONE QR OBJ EXISTS + Hunt.objects.get_or_create(name="First Hunt", start=timezone.now() - timezone.timedelta(days=2), end=timezone.now(), + starting_location=QrCode.objects.first(), ending_location=QrCode.objects.last(), + form_url="https://forms.gle/eAghxHxWRiXWgeKF9", + ending_text="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.") + +def undo_create_hunt(apps, schema_editor): + # Create a Hunt object if needed + from core.models import Hunt + try: + Hunt.objects.get(name="First Hunt").delete() + except Hunt.DoesNotExist: + pass + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0018_qrcode_image_url"), + ] + + operations = [ + migrations.AlterField( + model_name="qrcode", + name="key", + field=models.CharField( + default=core.models.generate_hint_key, + help_text="Key to access the hint, used in the QR code ", + max_length=64, + unique=True, + ), + ), + migrations.CreateModel( + name="Hunt", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=64)), + ("start", models.DateTimeField()), + ("end", models.DateTimeField()), + ( + "team_size", + models.PositiveSmallIntegerField( + 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.", + ), + ), + ( + "form_url", + models.URLField( + blank=True, + help_text="Google form to fill out after the hunt", + null=True, + ), + ), + ( + "ending_text", + models.TextField( + help_text="Text to display after the hunt is over. If you want to include a url (e.g. a google form) at the end use text inside of double curly brackets {{ }} to show where the form will go. The text inside the brackets is what will be shown to the user. e.g. {{this form}}, users will only see 'this form' but can click it to get to the form specified above", + max_length=250, + ), + ), + ( + "early_access_users", + models.ManyToManyField( + help_text="Users that can access this hunt before it starts", + related_name="early_access_users", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "ending_location", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="ending_location", + to="core.qrcode", + ), + ), + ( + "starting_location", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="starting_location", + to="core.qrcode", + ), + ), + ], + ), + migrations.RunPython(create_hunt, reverse_code=undo_create_hunt), + migrations.AddField( + model_name="logicpuzzlehint", + name="belongs_to", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="logic_puzzle", + to="core.hunt", + ), + preserve_default=False, + ), + migrations.AddField( + model_name="team", + name="hunt", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="teams", + to="core.hunt", + ), + preserve_default=False, + ), + migrations.AddConstraint( + model_name="hunt", + constraint=models.CheckConstraint( + check=models.Q(("start__lt", models.F("end"))), name="start_before_end" + ), + ), + migrations.AddConstraint( + model_name="hunt", + constraint=models.CheckConstraint( + check=models.Q( + ("starting_location", models.F("ending_location")), _negated=True + ), + name="start_not_equal_end", + ), + ), + migrations.AddConstraint( + model_name="hunt", + constraint=models.CheckConstraint( + check=models.Q( + ("ending_text__contains", "{{"), ("ending_text__contains", "}}") + ), + name="form_in_ending_text", + ), + ), + ] diff --git a/core/models.py b/core/models.py index b64e45b..25aa13a 100644 --- a/core/models.py +++ b/core/models.py @@ -17,7 +17,11 @@ 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 + "Team", + related_name="members", + on_delete=models.SET_NULL, + blank=True, + null=True, ) @property @@ -125,6 +129,7 @@ class Team(models.Model): ) # 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) + 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) @@ -170,17 +175,59 @@ class Invite(models.Model): 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 Hunt(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=64) + start = models.DateTimeField() + end = models.DateTimeField() + team_size = models.PositiveSmallIntegerField(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.") + starting_location = models.ForeignKey( + QrCode, on_delete=models.PROTECT, related_name="starting_location" + ) + ending_location = models.ForeignKey( + QrCode, on_delete=models.PROTECT, related_name="ending_location" + ) + early_access_users = models.ManyToManyField( + User, + related_name="early_access_users", + help_text="Users that can access this hunt before it starts", + ) + form_url = models.URLField(help_text="Google form to fill out after the hunt", null=True, blank=True) + ending_text = models.TextField( + help_text="Text to display after the hunt is over. If you want to include a url (e.g. a google form) at the end use text inside of double curly brackets {{ }} to show where the form will go. " + "The text inside the brackets is what will be shown to the user. " + "e.g. {{this form}}, users will only see 'this form' but can click it to get to the form specified above", + max_length=250, + ) + + def __str__(self): + return self.name + + class Meta: + constraints = [ + models.CheckConstraint( + check=models.Q(start__lt=models.F("end")), + name="start_before_end", + ), + # starting location cannot be the same as ending + models.CheckConstraint( + check=~models.Q(starting_location=models.F("ending_location")), + name="start_not_equal_end", + ), + # make sure ending_text contains {{ }} for the form + models.CheckConstraint( + check=models.Q(ending_text__contains="{{") & models.Q( + ending_text__contains="}}" + ), + name="form_in_ending_text", + ), + # Ensure there isn't a different hunt running in that timespan + models.CheckConstraint( + check=models.Q(start__gt=models.F("end")), + name="no_overlapping_hunts", # todo check if this works + ), + ] class LogicPuzzleHint(models.Model): @@ -195,7 +242,9 @@ class LogicPuzzleHint(models.Model): unique=True, ) - # belongs_to = models.ForeignKey(Hunt, related_name="logic_puzzle_hunt", on_delete=models.CASCADE) + belongs_to = models.ForeignKey( + Hunt, related_name="logic_puzzle", on_delete=models.CASCADE + ) def __str__(self): return str(self.hint) diff --git a/core/templates/core/credits.html b/core/templates/core/credits.html index 4167623..2e9c08b 100644 --- a/core/templates/core/credits.html +++ b/core/templates/core/credits.html @@ -18,12 +18,12 @@

{% translate 'project manager' %}

{% translate 'programming' %}

    -
  • nyiyui
  • -
  • Jason Cameron
  • -
  • Jimmy Liu
  • -
  • Glen Lin
  • -
  • Joshua Wang
  • -
  • Chelsea Wong
  • +
  • Jason Cameron
  • +
  • nyiyui
  • +
  • Jimmy Liu
  • +
  • Glen Lin
  • +
  • Joshua Wang
  • +
  • Chelsea Wong

{% translate 'content' %}

diff --git a/core/templates/core/qr.html b/core/templates/core/qr.html index e85c1d9..55d4dac 100644 --- a/core/templates/core/qr.html +++ b/core/templates/core/qr.html @@ -52,7 +52,7 @@ {% if nextqr %} {% hint nextqr request.user.team as hint %} - {{ hint |mistune }} + {{ hint|mistune }} {% if request.user.is_superuser %}
({{ nextqr }} change) {% endif %} @@ -65,6 +65,7 @@ {% endif %} {% else %} + {% ending_block qr.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/templatetags/qr.py b/core/templatetags/qr.py index 24cb00c..ee5bdb8 100644 --- a/core/templatetags/qr.py +++ b/core/templatetags/qr.py @@ -1,3 +1,5 @@ +import re + from django import template from django.urls import reverse from django.utils.html import format_html @@ -13,3 +15,15 @@ def hint(qr, team): @register.simple_tag def join_url(code): return format_html("%s%s" % (reverse("join"), f"?code={code}")) + + +@register.simple_tag +def ending_block(hunt): + pattern = r"{{(.*?)}}" + match = re.search(pattern, hunt.ending_text) + + if match: + ending_text = match.group(1).strip() + return format_html( + hunt.ending_text.replace(match.group(0), '{}'.format(hunt.ending_form, ending_text))) + return hunt.ending_text diff --git a/core/views/auth.py b/core/views/auth.py index ac3f64b..a607051 100644 --- a/core/views/auth.py +++ b/core/views/auth.py @@ -31,7 +31,8 @@ def pkce1(q): code_challenge = code_challenge.rstrip("=") code_challenge_method = "S256" return dict( - code_challenge=code_challenge, code_challenge_method=code_challenge_method + code_challenge=code_challenge, + code_challenge_method=code_challenge_method, ) @@ -94,7 +95,8 @@ def oauth_auth(q): access_token = s2d["access_token"] refresh_token = s2d["refresh_token"] q3 = requests.get( - settings.YASOI["me_url"], headers={"Authorization": f"Bearer {access_token}"} + settings.YASOI["me_url"], + headers={"Authorization": f"Bearer {access_token}"}, ) q3.raise_for_status() s3d = q3.json() diff --git a/core/views/index.py b/core/views/index.py index 065b8bd..db5a8f2 100644 --- a/core/views/index.py +++ b/core/views/index.py @@ -9,7 +9,9 @@ @require_http_methods(["GET"]) def index(q): return render( - q, "core/index.html" if q.user.is_authenticated else "core/gate.html", {} + q, + "core/index.html" if q.user.is_authenticated else "core/gate.html", + {}, ) diff --git a/core/views/qr.py b/core/views/qr.py index 5f500cb..88a0546 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -38,7 +38,7 @@ def after_start(f): def wrapped(*args, **kwargs): request = args[0] if ( - not request.user.has_perm("core.view_before_start") + not request.user.has_perm("core.view_before_start")# or current_hunt. todo impl and settings.START > datetime.datetime.now() ): messages.error( @@ -58,6 +58,7 @@ def wrapped(*args, **kwargs): 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 diff --git a/core/views/team.py b/core/views/team.py index 9b45d66..62dfc9c 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -37,7 +37,8 @@ def join(request): invite.invites += 1 invite.save() messages.success( - request, _("Joined team %(team_name)s") % dict(team_name=team.name) + request, + _("Joined team %(team_name)s") % dict(team_name=team.name), ) return redirect("/") else: From aa581aca386a561a17fb1e41a01503a574981f45 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 2 Dec 2023 20:57:55 +0000 Subject: [PATCH 03/22] format --- core/admin.py | 1 - ...unt_logicpuzzlehint_belongs_to_and_more.py | 17 ++++++++++++---- core/models.py | 20 +++++++++++-------- core/templatetags/qr.py | 6 +++++- core/views/qr.py | 4 +++- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/core/admin.py b/core/admin.py index 1656957..22e3e2b 100644 --- a/core/admin.py +++ b/core/admin.py @@ -140,4 +140,3 @@ class UserAdmin(UserAdmin_): admin.site.register(QrCode, QrCodeAdmin) admin.site.register(LogicPuzzleHint, LogicPuzzleAdmin) admin.site.register(Hunt) - 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 769f895..c61d716 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 @@ -6,6 +6,7 @@ from django.db import migrations, models import django.db.models.deletion + def create_hunt(apps, schema_editor): # Create a Hunt object if needed from core.models import Hunt, QrCode @@ -13,19 +14,27 @@ def create_hunt(apps, schema_editor): if QrCode.objects.count() == 0: return # COULD ERROR IF ONLY ONE QR OBJ EXISTS - Hunt.objects.get_or_create(name="First Hunt", start=timezone.now() - timezone.timedelta(days=2), end=timezone.now(), - starting_location=QrCode.objects.first(), ending_location=QrCode.objects.last(), - form_url="https://forms.gle/eAghxHxWRiXWgeKF9", - ending_text="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.") + Hunt.objects.get_or_create( + name="First Hunt", + start=timezone.now() - timezone.timedelta(days=2), + end=timezone.now(), + starting_location=QrCode.objects.first(), + ending_location=QrCode.objects.last(), + form_url="https://forms.gle/eAghxHxWRiXWgeKF9", + ending_text="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.", + ) + def undo_create_hunt(apps, schema_editor): # Create a Hunt object if needed from core.models import Hunt + try: Hunt.objects.get(name="First Hunt").delete() except Hunt.DoesNotExist: pass + class Migration(migrations.Migration): dependencies = [ ("core", "0018_qrcode_image_url"), diff --git a/core/models.py b/core/models.py index 25aa13a..084bd6a 100644 --- a/core/models.py +++ b/core/models.py @@ -181,7 +181,10 @@ class Hunt(models.Model): start = models.DateTimeField() end = models.DateTimeField() team_size = models.PositiveSmallIntegerField(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.") + 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.", + ) starting_location = models.ForeignKey( QrCode, on_delete=models.PROTECT, related_name="starting_location" ) @@ -193,11 +196,13 @@ class Hunt(models.Model): related_name="early_access_users", help_text="Users that can access this hunt before it starts", ) - form_url = models.URLField(help_text="Google form to fill out after the hunt", null=True, blank=True) + form_url = models.URLField( + help_text="Google form to fill out after the hunt", null=True, blank=True + ) ending_text = models.TextField( help_text="Text to display after the hunt is over. If you want to include a url (e.g. a google form) at the end use text inside of double curly brackets {{ }} to show where the form will go. " - "The text inside the brackets is what will be shown to the user. " - "e.g. {{this form}}, users will only see 'this form' but can click it to get to the form specified above", + "The text inside the brackets is what will be shown to the user. " + "e.g. {{this form}}, users will only see 'this form' but can click it to get to the form specified above", max_length=250, ) @@ -217,15 +222,14 @@ class Meta: ), # make sure ending_text contains {{ }} for the form models.CheckConstraint( - check=models.Q(ending_text__contains="{{") & models.Q( - ending_text__contains="}}" - ), + check=models.Q(ending_text__contains="{{") + & models.Q(ending_text__contains="}}"), name="form_in_ending_text", ), # Ensure there isn't a different hunt running in that timespan models.CheckConstraint( check=models.Q(start__gt=models.F("end")), - name="no_overlapping_hunts", # todo check if this works + name="no_overlapping_hunts", # todo check if this works ), ] diff --git a/core/templatetags/qr.py b/core/templatetags/qr.py index ee5bdb8..21b5fd7 100644 --- a/core/templatetags/qr.py +++ b/core/templatetags/qr.py @@ -25,5 +25,9 @@ def ending_block(hunt): if match: ending_text = match.group(1).strip() return format_html( - hunt.ending_text.replace(match.group(0), '{}'.format(hunt.ending_form, ending_text))) + hunt.ending_text.replace( + match.group(0), + '{}'.format(hunt.ending_form, ending_text), + ) + ) return hunt.ending_text diff --git a/core/views/qr.py b/core/views/qr.py index 88a0546..4a7c1fa 100644 --- a/core/views/qr.py +++ b/core/views/qr.py @@ -38,7 +38,9 @@ def after_start(f): def wrapped(*args, **kwargs): request = args[0] if ( - not request.user.has_perm("core.view_before_start")# or current_hunt. todo impl + not request.user.has_perm( + "core.view_before_start" + ) # or current_hunt. todo impl and settings.START > datetime.datetime.now() ): messages.error( From 55ea37115783b6097d9b19833f61dd6bda30c2e0 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 16:59:35 -0500 Subject: [PATCH 04/22] updated lock. --- .idea/workspace.xml | 2 +- poetry.lock | 86 +++++++++++++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8a03b4c..58da910 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -325,4 +325,4 @@ - \ No newline at end of file + diff --git a/poetry.lock b/poetry.lock index c7a44b1..a0d6cae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -235,34 +235,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.6" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:0f27acb55a4e77b9be8d550d762b0513ef3fc658cd3eb15110ebbcbd626db12c"}, - {file = "cryptography-41.0.6-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ae236bb8760c1e55b7a39b6d4d32d2279bc6c7c8500b7d5a13b6fb9fc97be35b"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afda76d84b053923c27ede5edc1ed7d53e3c9f475ebaf63c68e69f1403c405a8"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da46e2b5df770070412c46f87bac0849b8d685c5f2679771de277a422c7d0b86"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff369dd19e8fe0528b02e8df9f2aeb2479f89b1270d90f96a63500afe9af5cae"}, - {file = "cryptography-41.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b648fe2a45e426aaee684ddca2632f62ec4613ef362f4d681a9a6283d10e079d"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5daeb18e7886a358064a68dbcaf441c036cbdb7da52ae744e7b9207b04d3908c"}, - {file = "cryptography-41.0.6-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:068bc551698c234742c40049e46840843f3d98ad7ce265fd2bd4ec0d11306596"}, - {file = "cryptography-41.0.6-cp37-abi3-win32.whl", hash = "sha256:2132d5865eea673fe6712c2ed5fb4fa49dba10768bb4cc798345748380ee3660"}, - {file = "cryptography-41.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:48783b7e2bef51224020efb61b42704207dde583d7e371ef8fc2a5fb6c0aabc7"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8efb2af8d4ba9dbc9c9dd8f04d19a7abb5b49eab1f3694e7b5a16a5fc2856f5c"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5a550dc7a3b50b116323e3d376241829fd326ac47bc195e04eb33a8170902a9"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:85abd057699b98fce40b41737afb234fef05c67e116f6f3650782c10862c43da"}, - {file = "cryptography-41.0.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f39812f70fc5c71a15aa3c97b2bbe213c3f2a460b79bd21c40d033bb34a9bf36"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:742ae5e9a2310e9dade7932f9576606836ed174da3c7d26bc3d3ab4bd49b9f65"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:35f3f288e83c3f6f10752467c48919a7a94b7d88cc00b0668372a0d2ad4f8ead"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d03186af98b1c01a4eda396b137f29e4e3fb0173e30f885e27acec8823c1b09"}, - {file = "cryptography-41.0.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b27a7fd4229abef715e064269d98a7e2909ebf92eb6912a9603c7e14c181928c"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:398ae1fc711b5eb78e977daa3cbf47cec20f2c08c5da129b7a296055fbb22aed"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7e00fb556bda398b99b0da289ce7053639d33b572847181d6483ad89835115f6"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:60e746b11b937911dc70d164060d28d273e31853bb359e2b2033c9e93e6f3c43"}, - {file = "cryptography-41.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3288acccef021e3c3c10d58933f44e8602cf04dba96d9796d70d537bb2f4bbc4"}, - {file = "cryptography-41.0.6.tar.gz", hash = "sha256:422e3e31d63743855e43e5a6fcc8b4acab860f560f9321b0ee6269cc7ed70cc3"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -299,14 +299,46 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""} argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] +[[package]] +name = "django-ckeditor" +version = "6.7.0" +description = "Django admin CKEditor integration." +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-ckeditor-6.7.0.tar.gz", hash = "sha256:0489f7a6ae93360d328f77cea17c04891103cbdfa6c962af386bbe47e811671b"}, + {file = "django_ckeditor-6.7.0-py3-none-any.whl", hash = "sha256:73399fb8f56f565e7519b57adbd7c585623db2cdc8c75666f56918d3eecf7906"}, +] + +[package.dependencies] +Django = ">=3.2" +django-js-asset = ">=2.0" + +[[package]] +name = "django-js-asset" +version = "2.1.0" +description = "script tag with additional attributes for django.forms.Media" +optional = false +python-versions = ">=3.8" +files = [ + {file = "django_js_asset-2.1.0-py3-none-any.whl", hash = "sha256:36a3a4dd6e9efc895fb127d13126020f6ec1ec9469ad42878d42143f22495d90"}, + {file = "django_js_asset-2.1.0.tar.gz", hash = "sha256:be6f69ae5c4865617aa7726c48eddb64089a1e7d4ea7d22a35a3beb8282020f6"}, +] + +[package.dependencies] +django = ">=3.2" + +[package.extras] +tests = ["coverage"] + [[package]] name = "fontawesomefree" -version = "6.4.2" +version = "6.5.1" description = "Font Awesome Free" optional = false python-versions = "*" files = [ - {file = "fontawesomefree-6.4.2-py3-none-any.whl", hash = "sha256:ceaff7efc4fc39dadff3c3ff7298a7a1095403110d373f22456bb1c34ab6db02"}, + {file = "fontawesomefree-6.5.1-py3-none-any.whl", hash = "sha256:8dec4a2ee37bf8e53379ebbf0d38cefec4e06c7c9227f59af39dbb7b1632817b"}, ] [[package]] @@ -420,4 +452,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "5d15a06c7fcb991942f6f40e272f9691417e462f42d36e02fabe1baf2bb75556" +content-hash = "aee0a61f76c65794400278d18a29f5e43425b4de4019238ab81fbf9e3896060f" From 7da49e4b45b6cccbaa701a3acb3f3e7a2525885b Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 17:18:33 -0500 Subject: [PATCH 05/22] simplified form logic hehe :D (updated default team name added a current_hunt classmethod to Hunt --- .idea/workspace.xml | 36 ++++++++++++++++++++++++------------ core/forms.py | 12 ++---------- core/models.py | 9 ++++++++- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 58da910..4740f12 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,10 +4,11 @@
- - + - + + + - { + "keyToString": { + "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": "C:/Users/jason/projects/metro/venv", + "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": "vcs.Git", + "vue.rearranger.settings.migration": "true" } -}]]> +} @@ -319,7 +320,15 @@ @@ -367,7 +376,8 @@ - diff --git a/README.md b/README.md index 809890b..634dc5a 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,11 @@ then run ```bash python manage.py init ``` + +## Before you start a new hunt +```bash +python manage.py new_event +``` +if you want to retain the old teams, import the old teams into the new event + + diff --git a/core/management/commands/new_event.py b/core/management/commands/new_event.py new file mode 100644 index 0000000..34559aa --- /dev/null +++ b/core/management/commands/new_event.py @@ -0,0 +1,39 @@ +import json +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 + + +class LazyEncoder(DjangoJSONEncoder): + def default(self, obj): + return super().default(obj) + + +class Command(BaseCommand): + def __init__(self, *args, **kwargs): + super(Command, self).__init__(*args, **kwargs) + + help = "Prepares the database for the scavenger hunt. (Nukes all teams)" + + def handle(self, *args, **options): + # Loop groups + try: + rawData = serialize('json', Team.objects.all(), cls=DjangoJSONEncoder) + currentHunt: Team = Team.objects.first().hunt + with open(f'{currentHunt.name}_teams.json', 'w+') as f: + json.dump(rawData, f, cls=LazyEncoder, indent=6) + path = os.path.realpath(f.name) + print(f"Exported data successfully to {path}") + print("You can now import this data into another database using the loaddata command.") + time.sleep(2) + print("Now nuking all teams...") + Team.objects.all().delete() + time.sleep(1) + print("Nuked all teams successfully.") + except Exception as e: + print(e) + print("Error exporting data, try using the dumpdata command instead.") From ee55b89b6e42f57a0376d43c2463b8e3decbec0e Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 3 Dec 2023 00:13:42 +0000 Subject: [PATCH 13/22] format --- core/management/commands/new_event.py | 52 ++++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/core/management/commands/new_event.py b/core/management/commands/new_event.py index 34559aa..3ecb6b8 100644 --- a/core/management/commands/new_event.py +++ b/core/management/commands/new_event.py @@ -9,31 +9,33 @@ class LazyEncoder(DjangoJSONEncoder): - def default(self, obj): - return super().default(obj) + def default(self, obj): + return super().default(obj) class Command(BaseCommand): - def __init__(self, *args, **kwargs): - super(Command, self).__init__(*args, **kwargs) - - help = "Prepares the database for the scavenger hunt. (Nukes all teams)" - - def handle(self, *args, **options): - # Loop groups - try: - rawData = serialize('json', Team.objects.all(), cls=DjangoJSONEncoder) - currentHunt: Team = Team.objects.first().hunt - with open(f'{currentHunt.name}_teams.json', 'w+') as f: - json.dump(rawData, f, cls=LazyEncoder, indent=6) - path = os.path.realpath(f.name) - print(f"Exported data successfully to {path}") - print("You can now import this data into another database using the loaddata command.") - time.sleep(2) - print("Now nuking all teams...") - Team.objects.all().delete() - time.sleep(1) - print("Nuked all teams successfully.") - except Exception as e: - print(e) - print("Error exporting data, try using the dumpdata command instead.") + def __init__(self, *args, **kwargs): + super(Command, self).__init__(*args, **kwargs) + + help = "Prepares the database for the scavenger hunt. (Nukes all teams)" + + def handle(self, *args, **options): + # Loop groups + try: + rawData = serialize("json", Team.objects.all(), cls=DjangoJSONEncoder) + currentHunt: Team = Team.objects.first().hunt + with open(f"{currentHunt.name}_teams.json", "w+") as f: + json.dump(rawData, f, cls=LazyEncoder, indent=6) + path = os.path.realpath(f.name) + print(f"Exported data successfully to {path}") + print( + "You can now import this data into another database using the loaddata command." + ) + time.sleep(2) + print("Now nuking all teams...") + Team.objects.all().delete() + time.sleep(1) + print("Nuked all teams successfully.") + except Exception as e: + print(e) + print("Error exporting data, try using the dumpdata command instead.") From 502c02226643e59f8bd6a33ad12f73d471dc074c Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 19:24:48 -0500 Subject: [PATCH 14/22] format. --- .idea/workspace.xml | 66 ++++++++++++++++----------- core/management/commands/new_event.py | 4 +- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index a93f04c..d0f3ed6 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,8 +4,7 @@ - - + @@ -79,29 +78,30 @@ - { - "keyToString": { - "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": "C:/Users/jason/projects/metro/venv", - "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": "vcs.Git", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -328,12 +328,23 @@ + + diff --git a/core/management/commands/new_event.py b/core/management/commands/new_event.py index 3ecb6b8..4daf323 100644 --- a/core/management/commands/new_event.py +++ b/core/management/commands/new_event.py @@ -16,9 +16,9 @@ def default(self, obj): class Command(BaseCommand): def __init__(self, *args, **kwargs): super(Command, self).__init__(*args, **kwargs) - + help = "Prepares the database for the scavenger hunt. (Nukes all teams)" - + def handle(self, *args, **options): # Loop groups try: From 92931a90b1dcc1136e263981cd47c0c495c0857b Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 21:07:25 -0500 Subject: [PATCH 15/22] im foolish but I want to commit for the future --- core/tasks.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 core/tasks.py diff --git a/core/tasks.py b/core/tasks.py new file mode 100644 index 0000000..a510cd6 --- /dev/null +++ b/core/tasks.py @@ -0,0 +1,16 @@ +import threading +import time + + +class CheckIfEmptyThread(threading.Thread): + def __init__(self, team, **kwargs): + self.team = team + super(CheckIfEmptyThread, self).__init__(**kwargs) + + def run(self): + print(f"Starting thread for team {self.team.name} with {self.team.members.count()}", flush=True) + time.sleep(3) + print(f"Finished sleeping for team {self.team.name} with {self.team.members.count()}", flush=True) + if self.team.is_empty: + self.team.delete() + print(f"Deleted empty team {self.team.name}", flush=True) From 8225255cb23739ed36526d8c1ea561e33c4dd48d Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 21:30:55 -0500 Subject: [PATCH 16/22] feat: added members to team admin --- core/admin.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/admin.py b/core/admin.py index 22e3e2b..6da3688 100644 --- a/core/admin.py +++ b/core/admin.py @@ -69,7 +69,7 @@ def set_active( class TeamAdmin(admin.ModelAdmin): - readonly_fields = ("path",) + readonly_fields = ("path", "members") inlines = [ InviteInLine, ] @@ -83,6 +83,15 @@ def path(self, team): QrCode.code_pks(team), ) ) + + @admin.display(description="Members") + def members(self, team): + return "\n".join( + map( + lambda user: str(user), + team.members.all(), + ) + ) class QrCodeAdmin(admin.ModelAdmin): From a2dcd83f6299f6c36aa0f6b8417a988e7cf90ab9 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 21:33:35 -0500 Subject: [PATCH 17/22] Updated timezone and removed the configs in settings.py in favor of Hunt settings Fixed team logic for Hunts renamed a field --- .idea/workspace.xml | 74 ++++++++++++------- ...emove_hunt_form_in_ending_text_and_more.py | 32 ++++++++ core/models.py | 55 ++++++++------ core/views/team.py | 26 ++++--- wlmac-scavenger/settings.py | 12 +-- 5 files changed, 129 insertions(+), 70 deletions(-) create mode 100644 core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d0f3ed6..37d1749 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,10 +4,12 @@ - + + + - { + "keyToString": { + "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": "C:/Users/jason/projects/metro/venv", + "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": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable", + "ts.external.directory.path": "C:\\Users\\jason\\AppData\\Local\\Programs\\PyCharm Professional\\plugins\\javascript-impl\\jsLanguageServicesImpl\\external", + "vue.rearranger.settings.migration": "true" } -}]]> +} @@ -336,7 +339,23 @@ @@ -389,7 +408,10 @@ - diff --git a/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py b/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py new file mode 100644 index 0000000..8977cf0 --- /dev/null +++ b/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.13 on 2023-12-03 02:29 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="hunt", + name="form_in_ending_text", + ), + migrations.RenameField( + model_name="hunt", + old_name="team_size", + new_name="max_team_size", + ), + migrations.AlterField( + model_name="hunt", + name="early_access_users", + field=models.ManyToManyField( + blank=True, + help_text="Users that can access this hunt before it starts", + related_name="early_access_users", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/core/models.py b/core/models.py index 1b81103..0121b48 100644 --- a/core/models.py +++ b/core/models.py @@ -3,10 +3,11 @@ import random import secrets -from django.conf import settings 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.dispatch import receiver from django.utils import timezone from django.utils.html import format_html @@ -33,8 +34,6 @@ def in_team(self) -> bool: return True except AttributeError: return False - - def generate_hint_key(): return secrets.token_urlsafe(48) @@ -82,12 +81,12 @@ def codes(cls, team: "Team"): def code_pks(cls, team: "Team"): r = random.Random(team.id) pks = [a["pk"] for a in QrCode.objects.all().values("pk")] - pks = pks[: settings.PATH_LENGTH] + pks = pks[: team.hunt.path_length] r.shuffle(pks) - if isinstance((pk := settings.ALWAYS_LAST_QR_PK), int): + 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 := settings.ALWAYS_FIRST_QR_PK), int): + 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 :] return pks @@ -144,8 +143,12 @@ def members(self): @property def is_full(self): - return self.members.count() >= settings.MAX_TEAM_SIZE - + return self.members.count() >= self.hunt.max_team_size + + @property + def is_empty(self): + return self.members.count() == 0 + def join(self, user: User): if user in self.members.all(): return @@ -171,7 +174,6 @@ def save(self, *args, **kwargs): Invite.objects.create(team=self, code=generate_invite_code()) return data - class Invite(models.Model): invites = models.IntegerField(default=0) team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="invites") @@ -181,9 +183,9 @@ class Invite(models.Model): class Hunt(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=64) - start = models.DateTimeField() - end = models.DateTimeField() - team_size = models.PositiveSmallIntegerField(default=4, help_text="Max Team size") + start = models.DateTimeField(blank=False, null=False) + end = models.DateTimeField(blank=False, null=False) + max_team_size = models.PositiveSmallIntegerField(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.", @@ -198,6 +200,7 @@ class Hunt(models.Model): User, related_name="early_access_users", help_text="Users that can access this hunt before it starts", + blank=True, ) form_url = models.URLField( help_text="Google form to fill out after the hunt", null=True, blank=True @@ -215,7 +218,7 @@ def __str__(self): @classmethod def current_hunt(cls): try: - return cls.objects.get(start__lt=timezone.now(), end__gt=timezone.now()) + return cls.objects.get(start__lte=timezone.now(), end__gte=timezone.now()) except cls.DoesNotExist: return None @@ -224,10 +227,10 @@ def clean(self): Due to how this was designed, it is not possible to have multiple hunts running at the same time. This method prevents that from happening. """ - overlapping_events = self.objects.filter( - start_date__lte=self.start, end_date__gte=self.end + + overlapping_events = Hunt.objects.filter( + start__lte=self.start, end__gte=self.end # todo fix ).exclude(pk=self.pk) - if overlapping_events.exists(): raise ValidationError( "This event overlaps with existing events. Please choose a different time. Or Delete the other event." @@ -244,13 +247,6 @@ class Meta: check=~models.Q(starting_location=models.F("ending_location")), name="start_not_equal_end", ), - # make sure ending_text contains {{ }} for the form - models.CheckConstraint( - check=models.Q(ending_text__contains="{{") - & models.Q(ending_text__contains="}}"), - name="form_in_ending_text", - ), - # Ensure there isn't a different hunt running in that timespan ] @@ -287,3 +283,16 @@ def get_clue(cls, team: Team) -> str | None: return hint.hint except cls.DoesNotExist: 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() diff --git a/core/views/team.py b/core/views/team.py index 62dfc9c..e1bd5bf 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -3,13 +3,13 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib import messages -from django.http import HttpResponseBadRequest +from django.http import HttpResponseBadRequest, HttpRequest from django.shortcuts import redirect, render from django.urls import reverse from django.views.decorators.http import require_http_methods from django.utils.translation import gettext as _ -from ..models import Team, Invite, generate_invite_code +from ..models import Team, Invite, generate_invite_code, Hunt from ..forms import TeamJoinForm, TeamMakeForm from .qr import team_required @@ -17,10 +17,13 @@ @login_required @require_http_methods(["GET", "POST"]) def join(request): - if settings.START < datetime.datetime.now() and not request.user.team is None: + if settings.START < datetime.datetime.now() and request.user.team is not None: messages.error( request, - _("Since the hunt has already begun, switching teams is disallowed."), + _( + "Since the hunt has already begun, switching teams is disallowed. " + "If you need to switch teams, please contact an admin." + ), ) return redirect(reverse("index")) if request.method == "POST": @@ -67,11 +70,11 @@ def make(request): if request.method == "POST": form = TeamMakeForm(request.POST) if form.is_valid(): - form.save() - team = request.user.team = form.instance + raw: Team = form.save(commit=False) + raw.hunt = Hunt.current_hunt() + raw.save() + request.user.team = raw request.user.save() - invite = Invite(team=team, code=generate_invite_code()) - invite.save() messages.success( request, _("Made team %(team_name)s") @@ -84,9 +87,9 @@ def make(request): @login_required -def solo(q): - q.user.team = (team := Team(solo=True)) - team.save() +def solo(q: HttpRequest): + team = Team.objects.create(solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team") + q.user.team = team q.user.save() return redirect(reverse("index")) @@ -96,4 +99,5 @@ def solo(q): @team_required def invite(q): invites = Invite.objects.filter(team=q.user.team).values_list("code", flat=True) + print(invites) # todo fix return render(q, "core/team_invite.html", context=dict(invites=invites)) diff --git a/wlmac-scavenger/settings.py b/wlmac-scavenger/settings.py index f2644e1..0cedf49 100644 --- a/wlmac-scavenger/settings.py +++ b/wlmac-scavenger/settings.py @@ -92,7 +92,7 @@ LANGUAGE_CODE = "en" -TIME_ZONE = "UTC" +TIME_ZONE = "America/Toronto" USE_I18N = True @@ -113,15 +113,7 @@ scope="me_meta internal", ) -MAX_TEAM_SIZE: Final[int] = 4 # max # of people per team -ALWAYS_LAST_QR_PK: Final[ - int -] = 1 # the pk of the last qr code (that all teams must go to last) -ALWAYS_FIRST_QR_PK: Final[ - int -] = 2 # the pk of the first qr code (that all teams must go to first) -HINTS_GROUP_PK: Final[int] = 1 # the pk of the hints group (defined in init.py) -PATH_LENGTH: Final[int] = 15 # how many locations each team must ind +HINTS_GROUP_PK: Final[int] = 1 # the pk of the hints group (defined in commands/init.py) try: with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f: From e5f850ec0411158213b9b53a1f2dac1ecffaea05 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 3 Dec 2023 02:34:34 +0000 Subject: [PATCH 18/22] format --- core/admin.py | 2 +- core/management/commands/new_event.py | 4 +-- ...emove_hunt_form_in_ending_text_and_more.py | 2 +- core/models.py | 15 ++++++---- core/tasks.py | 28 +++++++++++-------- core/views/team.py | 4 ++- wlmac-scavenger/settings.py | 4 ++- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/core/admin.py b/core/admin.py index 6da3688..7517b50 100644 --- a/core/admin.py +++ b/core/admin.py @@ -83,7 +83,7 @@ def path(self, team): QrCode.code_pks(team), ) ) - + @admin.display(description="Members") def members(self, team): return "\n".join( diff --git a/core/management/commands/new_event.py b/core/management/commands/new_event.py index 4daf323..3ecb6b8 100644 --- a/core/management/commands/new_event.py +++ b/core/management/commands/new_event.py @@ -16,9 +16,9 @@ def default(self, obj): class Command(BaseCommand): def __init__(self, *args, **kwargs): super(Command, self).__init__(*args, **kwargs) - + help = "Prepares the database for the scavenger hunt. (Nukes all teams)" - + def handle(self, *args, **options): # Loop groups try: diff --git a/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py b/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py index 8977cf0..94fd791 100644 --- a/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py +++ b/core/migrations/0020_remove_hunt_form_in_ending_text_and_more.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ ("core", "0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more"), ] - + operations = [ migrations.RemoveConstraint( model_name="hunt", diff --git a/core/models.py b/core/models.py index 0121b48..026501d 100644 --- a/core/models.py +++ b/core/models.py @@ -34,6 +34,8 @@ def in_team(self) -> bool: return True except AttributeError: return False + + def generate_hint_key(): return secrets.token_urlsafe(48) @@ -144,11 +146,11 @@ def members(self): @property def is_full(self): return self.members.count() >= self.hunt.max_team_size - + @property def is_empty(self): return self.members.count() == 0 - + def join(self, user: User): if user in self.members.all(): return @@ -174,6 +176,7 @@ def save(self, *args, **kwargs): Invite.objects.create(team=self, code=generate_invite_code()) return data + class Invite(models.Model): invites = models.IntegerField(default=0) team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="invites") @@ -185,7 +188,9 @@ class Hunt(models.Model): name = models.CharField(max_length=64) start = models.DateTimeField(blank=False, null=False) end = models.DateTimeField(blank=False, null=False) - max_team_size = models.PositiveSmallIntegerField(default=4, help_text="Max Team size") + max_team_size = models.PositiveSmallIntegerField( + 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.", @@ -227,7 +232,7 @@ def clean(self): Due to how this was designed, it is not possible to have multiple hunts running at the same time. This method prevents that from happening. """ - + overlapping_events = Hunt.objects.filter( start__lte=self.start, end__gte=self.end # todo fix ).exclude(pk=self.pk) @@ -292,7 +297,7 @@ def remove_empty_teams(sender, instance: User, **kwargs): 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() diff --git a/core/tasks.py b/core/tasks.py index a510cd6..d3fcbc5 100644 --- a/core/tasks.py +++ b/core/tasks.py @@ -3,14 +3,20 @@ class CheckIfEmptyThread(threading.Thread): - def __init__(self, team, **kwargs): - self.team = team - super(CheckIfEmptyThread, self).__init__(**kwargs) - - def run(self): - print(f"Starting thread for team {self.team.name} with {self.team.members.count()}", flush=True) - time.sleep(3) - print(f"Finished sleeping for team {self.team.name} with {self.team.members.count()}", flush=True) - if self.team.is_empty: - self.team.delete() - print(f"Deleted empty team {self.team.name}", flush=True) + def __init__(self, team, **kwargs): + self.team = team + super(CheckIfEmptyThread, self).__init__(**kwargs) + + def run(self): + print( + f"Starting thread for team {self.team.name} with {self.team.members.count()}", + flush=True, + ) + time.sleep(3) + print( + f"Finished sleeping for team {self.team.name} with {self.team.members.count()}", + flush=True, + ) + if self.team.is_empty: + self.team.delete() + print(f"Deleted empty team {self.team.name}", flush=True) diff --git a/core/views/team.py b/core/views/team.py index e1bd5bf..f9cfcdb 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -88,7 +88,9 @@ def make(request): @login_required def solo(q: HttpRequest): - team = Team.objects.create(solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team") + team = Team.objects.create( + solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team" + ) q.user.team = team q.user.save() return redirect(reverse("index")) diff --git a/wlmac-scavenger/settings.py b/wlmac-scavenger/settings.py index 0cedf49..87b2aec 100644 --- a/wlmac-scavenger/settings.py +++ b/wlmac-scavenger/settings.py @@ -113,7 +113,9 @@ scope="me_meta internal", ) -HINTS_GROUP_PK: Final[int] = 1 # the pk of the hints group (defined in commands/init.py) +HINTS_GROUP_PK: Final[ + int +] = 1 # the pk of the hints group (defined in commands/init.py) try: with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f: From 3e929af103820d203abd678514115f36db57e31f Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 21:58:52 -0500 Subject: [PATCH 19/22] updated translation files --- .idea/dataSources.local.xml | 9 + .../inspectionProfiles/profiles_settings.xml | 1 + .idea/workspace.xml | 81 ++++++--- core/locale/ja_JP/LC_MESSAGES/django.po | 166 +++++++++++------- 4 files changed, 168 insertions(+), 89 deletions(-) create mode 100644 .idea/dataSources.local.xml diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml new file mode 100644 index 0000000..aed4b13 --- /dev/null +++ b/.idea/dataSources.local.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml index 105ce2d..dd4c951 100644 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -1,5 +1,6 @@ + diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 37d1749..300f42e 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,12 +4,13 @@ - + + + - - { - "keyToString": { - "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": "C:/Users/jason/projects/metro/venv", - "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": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable", - "ts.external.directory.path": "C:\\Users\\jason\\AppData\\Local\\Programs\\PyCharm Professional\\plugins\\javascript-impl\\jsLanguageServicesImpl\\external", - "vue.rearranger.settings.migration": "true" + +}]]> @@ -355,7 +363,23 @@ @@ -387,7 +411,6 @@ - @@ -411,7 +434,9 @@ - diff --git a/core/locale/ja_JP/LC_MESSAGES/django.po b/core/locale/ja_JP/LC_MESSAGES/django.po index 05d4230..17e831f 100644 --- a/core/locale/ja_JP/LC_MESSAGES/django.po +++ b/core/locale/ja_JP/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-12-17 20:51+0000\n" +"POT-Creation-Date: 2023-12-02 21:51-0500\n" "PO-Revision-Date: 2022-12-16 20:24+EST\n" "Last-Translator: にぃゆい <+@nyiyui.ca>\n" "Language-Team: LANGUAGE \n" @@ -17,55 +17,103 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: core/templates/core/base.html:22 +#: .\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 +#: .\core\templates\core\base.html:23 msgid "__startsPost" msgstr "で始まるにゃ" -#: core/templates/core/base.html:24 +#: .\core\templates\core\base.html:24 msgid "__endsPre" msgstr "あと" -#: core/templates/core/base.html:25 +#: .\core\templates\core\base.html:25 msgid "remaining" msgstr "だよ〜" -#: core/templates/core/base.html:26 +#: .\core\templates\core\base.html:26 msgid "scavenger hunt ended!" msgstr "終了〜!" -#: core/templates/core/base.html:148 +#: .\core\templates\core\base.html:105 msgid "Home" msgstr "ホーム" -#: core/templates/core/base.html:155 +#: .\core\templates\core\base.html:112 msgid "Admin" msgstr "管理" -#: core/templates/core/base.html:161 +#: .\core\templates\core\base.html:117 msgid "current" msgstr "最新ヒント" -#: core/templates/core/base.html:166 core/templates/core/logout.html:6 +#: .\core\templates\core\base.html:121 .\core\templates\core\logout.html:6 msgid "Logout" msgstr "ログアウト" -#: core/templates/core/gate.html:6 core/templates/core/index.html:6 +#: .\core\templates\core\base.html:128 +msgid "Login" +msgstr "" + +#: .\core\templates\core\credits.html:6 +msgid "Credits" +msgstr "Credits" + +#: .\core\templates\core\credits.html:10 +msgid "credits" +msgstr "credits" + +#: .\core\templates\core\credits.html:14 +msgid "project manager" +msgstr "" + +#: .\core\templates\core\credits.html:19 +msgid "programming" +msgstr "" + +#: .\core\templates\core\credits.html:29 +msgid "content" +msgstr "" + +#: .\core\templates\core\credits.html:34 +msgid "UI/UX design" +msgstr "" + +#: .\core\templates\core\credits.html:39 +msgid "hints" +msgstr "ヒント" + +#: .\core\templates\core\credits.html:46 +msgid "support" +msgstr "" + +#: .\core\templates\core\gate.html:6 .\core\templates\core\index.html:6 #, fuzzy #| msgid "scavenger hunt ended!" msgid "Scavenger Hunt" msgstr "終了〜!" -#: core/templates/core/gate.html:11 +#: .\core\templates\core\gate.html:11 #, fuzzy #| msgid "Contest has not started yet." msgid "The hunt has not begun yet." msgstr "未だ始まってないよ〜" -#: core/templates/core/gate.html:14 +#: .\core\templates\core\gate.html:14 #, python-format msgid "" "\n" @@ -74,7 +122,7 @@ msgstr "" "\n" "ログインしてね。\n" -#: core/templates/core/index.html:10 +#: .\core\templates\core\index.html:10 #, python-format msgid "" "\n" @@ -83,7 +131,7 @@ msgstr "" "\n" "%(username)s、ようこそ!\n" -#: core/templates/core/index.html:22 +#: .\core\templates\core\index.html:23 #, python-format msgid "" "\n" @@ -95,7 +143,7 @@ msgstr "" "がらくた集め、開始!最初の二次元コードを当て" "て!" -#: core/templates/core/index.html:28 +#: .\core\templates\core\index.html:29 #, fuzzy, python-format #| msgid "" #| "\n" @@ -111,7 +159,7 @@ msgstr "" "\n" "未だメンバー招待も出来るよ。" -#: core/templates/core/index.html:35 +#: .\core\templates\core\index.html:37 msgid "" "\n" " you're going solo\n" @@ -120,7 +168,7 @@ msgstr "" "\n" "ソロプレイ" -#: core/templates/core/index.html:39 +#: .\core\templates\core\index.html:41 #, python-format msgid "" "\n" @@ -130,59 +178,59 @@ msgstr "" "\n" "チームプレイ:%(team)s" -#: core/templates/core/index.html:47 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:50 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/logout.html:10 core/templates/core/logout.html:16 +#: .\core\templates\core\logout.html:10 .\core\templates\core\logout.html:16 #, fuzzy #| msgid "Logout" 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 +#: .\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 #, fuzzy, python-format #| msgid "" #| "\n" @@ -194,74 +242,79 @@ msgstr "チームに招待" #| " " msgid "" "\n" -" you may either\n" -" ,\n" -" invite via code (%(invite.code)s),\n" -" or allow them to 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 "" "\n" "するか、下" "記の二次元コードをスキャンしてにゃ:" -#: core/templates/core/team_join.html:6 +#: .\core\templates\core\team_join.html:6 #, fuzzy #| msgid "join a team" 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 #, fuzzy #| msgid "make a team" msgid "Make a Team" msgstr "チームを作る" -#: core/templates/core/team_new.html:27 +#: .\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:48 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\team.py:24 +#, fuzzy +#| msgid "Since the hunt has already begun, switching teams is disallowed." +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:44 #, python-format msgid "Joined team %(team_name)s" msgstr "チーム%(team_name)sに参加したにゃ" -#: core/views/team.py:46 +#: .\core\views\team.py:50 #, python-format msgid "Team %(team_name)s is full." msgstr "チーム%(team_name)sはもう満杯だよ" -#: core/views/team.py:63 +#: .\core\views\team.py:67 msgid "Since the hunt has already begun, making new teams is disallowed." msgstr "もう始まったから、新しいチーム作るのはダメだよ。" -#: core/views/team.py:76 +#: .\core\views\team.py:83 #, python-format msgid "Made team %(team_name)s" msgstr "チーム%(team_name)sをつくったよ。" @@ -269,12 +322,6 @@ msgstr "チーム%(team_name)sをつくったよ。" #~ msgid "ends in:" #~ msgstr "終了時間:" -#~ msgid "Credits" -#~ msgstr "Credits" - -#~ msgid "credits" -#~ msgstr "credits" - #~ msgid "" #~ "\n" #~ "project manager: ApocalypseCalculator
\n" @@ -292,8 +339,5 @@ msgstr "チーム%(team_name)sをつくったよ。" #~ "support: project metropolis and " #~ "SAC
\n" -#~ msgid "hints" -#~ msgstr "ヒント" - #~ msgid "thank you for playing!" #~ msgstr "やってくれてありがとう!(*˘︶˘*).。.:*♡" From d3606e832c8e8bf13fb7a8f8d8117e18a8e48ba5 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 22:00:52 -0500 Subject: [PATCH 20/22] fixes invites reformat. --- core/admin.py | 1 + core/models.py | 3 +++ core/tasks.py | 22 ---------------------- core/views/team.py | 16 +++++++++++----- 4 files changed, 15 insertions(+), 27 deletions(-) delete mode 100644 core/tasks.py diff --git a/core/admin.py b/core/admin.py index 7517b50..960cf81 100644 --- a/core/admin.py +++ b/core/admin.py @@ -149,3 +149,4 @@ class UserAdmin(UserAdmin_): admin.site.register(QrCode, QrCodeAdmin) admin.site.register(LogicPuzzleHint, LogicPuzzleAdmin) admin.site.register(Hunt) +admin.site.register(Invite) diff --git a/core/models.py b/core/models.py index 026501d..9442669 100644 --- a/core/models.py +++ b/core/models.py @@ -181,6 +181,9 @@ class Invite(models.Model): invites = models.IntegerField(default=0) team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="invites") code = models.CharField(max_length=32, unique=True) + + def __str__(self): + return str(self.team.name) class Hunt(models.Model): diff --git a/core/tasks.py b/core/tasks.py deleted file mode 100644 index d3fcbc5..0000000 --- a/core/tasks.py +++ /dev/null @@ -1,22 +0,0 @@ -import threading -import time - - -class CheckIfEmptyThread(threading.Thread): - def __init__(self, team, **kwargs): - self.team = team - super(CheckIfEmptyThread, self).__init__(**kwargs) - - def run(self): - print( - f"Starting thread for team {self.team.name} with {self.team.members.count()}", - flush=True, - ) - time.sleep(3) - print( - f"Finished sleeping for team {self.team.name} with {self.team.members.count()}", - flush=True, - ) - if self.team.is_empty: - self.team.delete() - print(f"Deleted empty team {self.team.name}", flush=True) diff --git a/core/views/team.py b/core/views/team.py index f9cfcdb..a4f0e12 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -61,7 +61,7 @@ def join(request): @login_required @require_http_methods(["GET", "POST"]) def make(request): - if settings.START < datetime.datetime.now() and not request.user.team is None: + if settings.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."), @@ -75,6 +75,9 @@ def make(request): raw.save() request.user.team = raw request.user.save() + Invite.objects.get_or_create( + team=raw, code=generate_invite_code(), invites=0 + ) messages.success( request, _("Made team %(team_name)s") @@ -88,9 +91,9 @@ def make(request): @login_required def solo(q: HttpRequest): - team = Team.objects.create( - solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team" - ) + team = Team.objects.create( + solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team" + ) q.user.team = team q.user.save() return redirect(reverse("index")) @@ -101,5 +104,8 @@ def solo(q: HttpRequest): @team_required def invite(q): invites = Invite.objects.filter(team=q.user.team).values_list("code", flat=True) - print(invites) # todo fix + if invites.count() == 0: + print("No invites found, creating one") + Invite.objects.create(team=q.user.team, code=generate_invite_code(), invites=0) + return invite(q) return render(q, "core/team_invite.html", context=dict(invites=invites)) From b8232c2ea048bfeb7df4bdf3364f1d6f386f6278 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 2 Dec 2023 22:04:51 -0500 Subject: [PATCH 21/22] fixed tabs -> spaces --- .idea/workspace.xml | 60 ++++++++++++++++++++++----------------------- core/views/team.py | 16 ++++++------ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 300f42e..fb9a53b 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -82,37 +82,37 @@
- { + "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": "C:/Users/jason/projects/metro/venv", + "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" } -}]]> +} diff --git a/core/views/team.py b/core/views/team.py index a4f0e12..2ea014d 100644 --- a/core/views/team.py +++ b/core/views/team.py @@ -61,7 +61,7 @@ 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: + if settings.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."), @@ -76,7 +76,7 @@ def make(request): request.user.team = raw request.user.save() Invite.objects.get_or_create( - team=raw, code=generate_invite_code(), invites=0 + team=raw, code=generate_invite_code(), invites=0 ) messages.success( request, @@ -91,9 +91,9 @@ def make(request): @login_required def solo(q: HttpRequest): - team = Team.objects.create( - solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team" - ) + team = Team.objects.create( + solo=True, hunt=Hunt.current_hunt(), name=f"{q.user.username}'s Solo Team" + ) q.user.team = team q.user.save() return redirect(reverse("index")) @@ -105,7 +105,7 @@ def solo(q: HttpRequest): def invite(q): invites = Invite.objects.filter(team=q.user.team).values_list("code", flat=True) if invites.count() == 0: - print("No invites found, creating one") - Invite.objects.create(team=q.user.team, code=generate_invite_code(), invites=0) - return invite(q) + print("No invites found, creating one") + Invite.objects.create(team=q.user.team, code=generate_invite_code(), invites=0) + return invite(q) return render(q, "core/team_invite.html", context=dict(invites=invites)) From e97bb6c0f8a0d9f43065db0375109aa0bc0fec9f Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 3 Dec 2023 03:05:45 +0000 Subject: [PATCH 22/22] format --- core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/models.py b/core/models.py index 9442669..8f84743 100644 --- a/core/models.py +++ b/core/models.py @@ -181,7 +181,7 @@ class Invite(models.Model): invites = models.IntegerField(default=0) team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="invites") code = models.CharField(max_length=32, unique=True) - + def __str__(self): return str(self.team.name)