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/misc.xml b/.idea/misc.xml
index 1de81fd..b1ea2f1 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,8 +3,5 @@
-
-
-
-
-
\ No newline at end of file
+
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 8a7cce1..97adc6f 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,11 +4,13 @@
-
-
+
+
-
-
+
+
+
+
@@ -21,6 +23,7 @@
@@ -39,7 +42,7 @@
@@ -55,12 +58,12 @@
-
-
+
+
-
-
+
+
@@ -90,41 +93,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"
}
-}]]>
+}
@@ -171,13 +170,8 @@
-
-
-
-
-
- C:\Users\jason\AppData\Roaming\Subversion
+
@@ -195,8 +189,6 @@
-
-
@@ -310,12 +302,103 @@
1701540841563
-
+
+
+ 1701554376823
+
+
+
+ 1701554376823
+
+
+
+ 1701555513478
+
+
+
+ 1701555513478
+
+
+
+ 1701555773047
+
+
+
+ 1701555773047
+
+
+
+ 1701555945329
+
+
+
+ 1701555945329
+
+
+
+ 1701556767105
+
+
+
+ 1701556767105
+
+
+
+ 1701558361459
+
+
+
+ 1701558361459
+
+
+
+ 1701562355578
+
+
+
+ 1701562355578
+
+
+
+ 1701563092633
+
+
+
+ 1701563092633
+
+
+
+ 1701569251853
+
+
+
+ 1701569251854
+
+
+
+ 1701570659162
+
+
+
+ 1701570659163
+
+
+
+ 1701570821725
+
+
+
+ 1701570821725
+
+
+
+
+
@@ -339,7 +422,6 @@
-
@@ -353,7 +435,21 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/admin.py b/core/admin.py
index 0ceebec..960cf81 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -1,11 +1,36 @@
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
extra = 2
@@ -44,7 +69,7 @@ def set_active(
class TeamAdmin(admin.ModelAdmin):
- readonly_fields = ("path",)
+ readonly_fields = ("path", "members")
inlines = [
InviteInLine,
]
@@ -53,12 +78,31 @@ 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),
+ )
+ )
+
+ @admin.display(description="Members")
+ def members(self, team):
+ return "\n".join(
+ map(
+ lambda user: str(user),
+ team.members.all(),
+ )
)
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]
@@ -70,7 +114,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 +127,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",
@@ -90,7 +135,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"])),
]
)
@@ -100,3 +148,5 @@ class UserAdmin(UserAdmin_):
admin.site.register(Team, TeamAdmin)
admin.site.register(QrCode, QrCodeAdmin)
admin.site.register(LogicPuzzleHint, LogicPuzzleAdmin)
+admin.site.register(Hunt)
+admin.site.register(Invite)
diff --git a/core/forms.py b/core/forms.py
index 93e4578..b3397dd 100644
--- a/core/forms.py
+++ b/core/forms.py
@@ -7,17 +7,10 @@
class TeamMakeForm(forms.ModelForm):
class Meta:
widgets = dict(
- name=forms.TextInput(attrs={"placeholder": "youmas"}),
+ name=forms.TextInput(attrs={"placeholder": "Json's Team"}),
)
model = Team
- exclude = (
- "id",
- "members",
- "is_active",
- "is_open",
- "solo",
- "current_qr_i",
- )
+ fields = ("name",)
class TeamJoinForm(forms.Form):
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 "やってくれてありがとう!(*˘︶˘*).。.:*♡"
diff --git a/core/management/commands/new_event.py b/core/management/commands/new_event.py
new file mode 100644
index 0000000..3ecb6b8
--- /dev/null
+++ b/core/management/commands/new_event.py
@@ -0,0 +1,41 @@
+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.")
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..c61d716
--- /dev/null
+++ b/core/migrations/0019_alter_qrcode_key_hunt_logicpuzzlehint_belongs_to_and_more.py
@@ -0,0 +1,162 @@
+# 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/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..94fd791
--- /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 b64e45b..8f84743 100644
--- a/core/models.py
+++ b/core/models.py
@@ -3,9 +3,12 @@
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
@@ -17,7 +20,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
@@ -76,12 +83,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
@@ -125,6 +132,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)
@@ -132,12 +140,16 @@ def update_current_qr_i(self, i: int):
@property
def members(self):
- """Returns all members of the team, it's a related manager so to convert to queryset use .all() or filter it."""
+ """Returns all members of the team, it's a related manager so to convert to queryset use .all() or filter it."""
return User.objects.filter(team=str(self.id))
@property
def is_full(self):
- return self.members.count() >= 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():
@@ -152,16 +164,17 @@ def invites(self):
@property
def qr_len(self):
- """Amount of codes the team has completed (+1) (assuming no skips)"""
+ """Amount of codes the team has completed (+1) (assuming no skips)"""
return int(self.current_qr_i) + 1
def __str__(self):
return str(self.name)
def save(self, *args, **kwargs):
+ data = super().save(*args, **kwargs)
if self._state.adding: # only generate key on creation not on update
- Invite.objects.create(team=self, code=generate_invite_code()).save()
- return super().save(*args, **kwargs)
+ Invite.objects.create(team=self, code=generate_invite_code())
+ return data
class Invite(models.Model):
@@ -169,18 +182,80 @@ class Invite(models.Model):
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):
+ id = models.AutoField(primary_key=True)
+ 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"
+ )
+ 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",
+ 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",
+ max_length=250,
+ )
+
+ def __str__(self):
+ return self.name
+
+ @classmethod
+ def current_hunt(cls):
+ try:
+ return cls.objects.get(start__lte=timezone.now(), end__gte=timezone.now())
+ except cls.DoesNotExist:
+ return None
-# 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
+ 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)
+ if overlapping_events.exists():
+ raise ValidationError(
+ "This event overlaps with existing events. Please choose a different time. Or Delete the other event."
+ )
+
+ 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",
+ ),
+ ]
class LogicPuzzleHint(models.Model):
@@ -195,7 +270,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)
@@ -214,3 +291,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/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..21b5fd7 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,19 @@ 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..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")
+ not request.user.has_perm(
+ "core.view_before_start"
+ ) # or current_hunt. todo impl
and settings.START > datetime.datetime.now()
):
messages.error(
@@ -58,6 +60,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..2ea014d 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":
@@ -37,7 +40,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:
@@ -57,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."),
@@ -66,11 +70,14 @@ 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()
+ Invite.objects.get_or_create(
+ team=raw, code=generate_invite_code(), invites=0
+ )
messages.success(
request,
_("Made team %(team_name)s")
@@ -83,9 +90,11 @@ 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"))
@@ -95,4 +104,8 @@ def solo(q):
@team_required
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)
return render(q, "core/team_invite.html", context=dict(invites=invites))
diff --git a/poetry.lock b/poetry.lock
index a0d6cae..339a307 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -314,6 +314,16 @@ files = [
Django = ">=3.2"
django-js-asset = ">=2.0"
+[[package]]
+name = "django-impersonate"
+version = "1.9.2"
+description = "Django app to allow superusers to impersonate other users."
+optional = false
+python-versions = "*"
+files = [
+ {file = "django-impersonate-1.9.2.tar.gz", hash = "sha256:5eae7f1fe25ffd1af7fc7f338cb0c9b2d84bb874a95c3f07b65e2bcd1ee1f8b2"},
+]
+
[[package]]
name = "django-js-asset"
version = "2.1.0"
@@ -452,4 +462,5 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "aee0a61f76c65794400278d18a29f5e43425b4de4019238ab81fbf9e3896060f"
+content-hash = "9f7ca6a088c6b910bc55cc7a9368ce083a9279353d61dffbfb277e44472113bf"
+
diff --git a/pyproject.toml b/pyproject.toml
index f406d34..af8bb4f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,7 +12,7 @@ requests = "^2.28.1"
fontawesomefree = "^6.2.1"
mistune = "^3.0.2"
django-ckeditor = "^6.0.0"
-
+django-impersonate = "^1.9.2"
[tool.poetry.dev-dependencies]
[build-system]
diff --git a/wlmac-scavenger/settings.py b/wlmac-scavenger/settings.py
index 8bdf36d..87b2aec 100644
--- a/wlmac-scavenger/settings.py
+++ b/wlmac-scavenger/settings.py
@@ -18,6 +18,7 @@
"django.contrib.staticfiles",
"fontawesomefree",
"core",
+ "impersonate",
]
MIDDLEWARE = [
@@ -29,6 +30,7 @@
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "impersonate.middleware.ImpersonateMiddleware",
]
ROOT_URLCONF = "wlmac-scavenger.urls"
@@ -90,7 +92,7 @@
LANGUAGE_CODE = "en"
-TIME_ZONE = "UTC"
+TIME_ZONE = "America/Toronto"
USE_I18N = True
@@ -111,15 +113,9 @@
scope="me_meta internal",
)
-MAX_TEAM_SIZE: Final[int] = 4 # max # of people per team
-ALWAYS_LAST_QR_PK: Final[
+HINTS_GROUP_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
+] = 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:
diff --git a/wlmac-scavenger/urls.py b/wlmac-scavenger/urls.py
index 4287866..0d87f4c 100644
--- a/wlmac-scavenger/urls.py
+++ b/wlmac-scavenger/urls.py
@@ -5,6 +5,7 @@
urlpatterns = [
path("admin/", admin.site.urls),
+ path("impersonate/", include("impersonate.urls")),
path("", include("core.urls")),
*static(settings.STATIC_URL, document_root=settings.STATIC_ROOT),
]