From 44c335b08992f4035b67fe3da4ff21bda6e38e10 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Sun, 22 Feb 2026 20:42:33 -0500 Subject: [PATCH] added superuser support in apps.lyric.admin & new manage.py cmd ensure_superuser; .tests.integrated.test_admin & .test_management_commands green --- infra/deploy-playbook.yaml | 8 ++- infra/deploy.sh.j2 | 3 ++ infra/gamearray.env.j2 | 2 + infra/group_vars/staging/vault.yaml | 49 ++++++++++--------- src/apps/lyric/admin.py | 7 ++- src/apps/lyric/management/__init__.py | 0 .../lyric/management/commands/__init__.py | 0 .../management/commands/ensure_superuser.py | 21 ++++++++ src/apps/lyric/tests/integrated/test_admin.py | 25 ++++++++++ .../integrated/test_management_commands.py | 34 +++++++++++++ 10 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 src/apps/lyric/management/__init__.py create mode 100644 src/apps/lyric/management/commands/__init__.py create mode 100644 src/apps/lyric/management/commands/ensure_superuser.py create mode 100644 src/apps/lyric/tests/integrated/test_admin.py create mode 100644 src/apps/lyric/tests/integrated/test_management_commands.py diff --git a/infra/deploy-playbook.yaml b/infra/deploy-playbook.yaml index 2b20273..69e682f 100644 --- a/infra/deploy-playbook.yaml +++ b/infra/deploy-playbook.yaml @@ -133,11 +133,12 @@ DJANGO_DEBUG_FALSE: "1" DJANGO_SECRET_KEY: "{{ secret_key.content | b64decode }}" DJANGO_ALLOWED_HOST: "{{ django_allowed_host }}" + DJANGO_SUPERUSER_EMAIL: "{{ django_superuser_email }}" + DJANGO_SUPERUSER_PASSWORD: "{{ django_superuser_password }}" DATABASE_URL: "postgresql://gamearray:{{ postgres_password }}@gamearray_postgres/gamearray" MAILGUN_API_KEY: "{{ mailgun_api_key }}" CELERY_BROKER_URL: "redis://gamearray_redis:6379/0" REDIS_URL: "redis://gamearray_redis:6379/1" - networks: - name: gamearray_net ports: @@ -180,6 +181,11 @@ container: gamearray command: python manage.py migrate + - name: Ensure superuser exists + community.docker.docker_container_exec: + container: gamearray + command: python manage.py ensure_superuser + handlers: - name: Restart nginx ansible.builtin.service: diff --git a/infra/deploy.sh.j2 b/infra/deploy.sh.j2 index 8345766..57b8b08 100644 --- a/infra/deploy.sh.j2 +++ b/infra/deploy.sh.j2 @@ -30,6 +30,9 @@ docker run -d --name gamearray_celery \ echo "==> Running migrations..." docker exec gamearray python ./manage.py migrate +echo "==> Ensuring superuser exists..." +docker exec gamearray python manage.py ensure_superuser + echo "==> Copying static files..." sudo docker cp gamearray:/src/static/. /var/www/gamearray/static/ diff --git a/infra/gamearray.env.j2 b/infra/gamearray.env.j2 index 5b43c4b..0b990ef 100644 --- a/infra/gamearray.env.j2 +++ b/infra/gamearray.env.j2 @@ -1,6 +1,8 @@ DJANGO_DEBUG_FALSE=1 DJANGO_SECRET_KEY={{ secret_key.content | b64decode }} DJANGO_ALLOWED_HOST={{ django_allowed_host }} +DJANGO_SUPERUSER_EMAIL={{ django_superuser_email }} +DJANGO_SUPERUSER_PASSWORD={{ django_superuser_password }} DATABASE_URL=postgresql://gamearray:{{ postgres_password }}@gamearray_postgres/gamearray MAILGUN_API_KEY={{ mailgun_api_key }} CELERY_BROKER_URL=redis://gamearray_redis:6379/0 diff --git a/infra/group_vars/staging/vault.yaml b/infra/group_vars/staging/vault.yaml index c7177aa..ee7f134 100644 --- a/infra/group_vars/staging/vault.yaml +++ b/infra/group_vars/staging/vault.yaml @@ -1,23 +1,28 @@ $ANSIBLE_VAULT;1.1;AES256 -33616230376431343735626631623932393166343538653732383533323436326335343463646664 -6565373531623465613661613533376231373837326438300a393665613839646231633737313938 -64633035336663313163333634623732323537326363646132313136376131636666636538323066 -3037373930303537320a313062646166353862633836373466316261363939633433663039323866 -62333739303662343836306538393734343830366336323265393138343438363533353166383031 -32313461313137643039376237346633316466646136353038633861333031663164656233366634 -38303363383130376264373861393863623330623733643135643461383132613339376633353031 -32313863323039646534633733383661333361313832333830383066633130396239626661643264 -65636335303339613432326533343337366261356632313639623634386633383836333733663536 -39383361353530646166643531333535356636326535383534326237666638326137616162646261 -65316466323335653932636338653565383038313531383638393839313736643739363037353230 -35653632353531656435396663316537333133653632366437613339303033333536643937353166 -64363037653733303332643931343362303261643432366531326262383465313965633064356338 -31336333373665373035656533633864316139303934623030383934393434356334643962666163 -33343739366336613263333764306365333566363536616662383733616237396563346132336633 -38663239613339376335386233386330396634323033343332366130616162666339393861306336 -35383566383831356530633130313732356331616164646132626665646235396635386237313538 -38656631336261646530303761643334303937613036363766303637376262373466316431323731 -38666462313639353131303134646434646135366136343361353932326165626666306361393431 -62646238323265346263386363373462313766616333326366366461346436383064336535376339 -31356566356336386262393831616631666233633930393263623563386265343237323133313832 -3430363635363332303963316530663765613666306233376463 +65383061626464353936363564313761663834646361326362613934363565623234636337313363 +3933313962643261353830333463336166393030313936370a616234626135633432613366633363 +61633265363937326231623365646336333737306634646335376135633031643564666164336230 +3435353764383936620a396165386538666433356166383661323037333861373632376432313332 +66666236373462363236663335623734633364653539323331396361613738636166323134386466 +66656431663261633036333537373336643866623236643139656662333831366435373837656262 +36333734376363373462643239623437623735373935633732343639313666663436616630363933 +61396530336461393064323161666537646135383462383532363932326132363331633438313138 +61623431326537313637626239653038353263313731303262653537316134383264616661623962 +32333564366362383431336432303964663835363365636434303332613161363930333065336637 +33343466343062306434663765613837343635386630326439303739616166396134393939626434 +62336634303963653230626630636363343730623734626336363039623231633532653330646366 +66613432633834393133386666623466326131386633303264333766306135623337353433306632 +66323733373232383862646661313966366465333463366361366337656537623562613964666631 +65373566316432383134666434393338626138363632633766636561383263333636623530326664 +63333265366132376437396431393535323931383637323833303839336635633735333565333530 +65343263373630633063383931646163323237643436366566363932646566323539373136646433 +37623638333834373537316164633166633738333363656431356163623332396631353864333333 +33306666646532626636376239326438373737383432663539333736363866663938396136383035 +32343534613862653538346430313338326435356230636535343464666262626663376635363835 +65363862663461353464313533313333323863313539643533343431643130383663656161616131 +33323639333564383830346163386362386238323936393832623961646565613961356263356365 +65376431666130356564666236383764316136326366666661326538653133343165326431393564 +36303065366263316232663230343137333231346538633036613066643365616331336135376461 +35613265623134663633303238366363336137383436663836353863623533396236666433303738 +38356361653633323065303035376664326238633066623731623436333332373363636634323433 +393631303539373234386465663630316335 diff --git a/src/apps/lyric/admin.py b/src/apps/lyric/admin.py index 4ae762f..898834d 100644 --- a/src/apps/lyric/admin.py +++ b/src/apps/lyric/admin.py @@ -1,6 +1,11 @@ from django.contrib import admin + from .models import Token, User -admin.site.register(User) +class UserAdmin(admin.ModelAdmin): + list_display = ["email"] + search_fields = ["email"] + +admin.site.register(User, UserAdmin) admin.site.register(Token) diff --git a/src/apps/lyric/management/__init__.py b/src/apps/lyric/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/lyric/management/commands/__init__.py b/src/apps/lyric/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/lyric/management/commands/ensure_superuser.py b/src/apps/lyric/management/commands/ensure_superuser.py new file mode 100644 index 0000000..bc997fb --- /dev/null +++ b/src/apps/lyric/management/commands/ensure_superuser.py @@ -0,0 +1,21 @@ +import os + +from django.core.management.base import BaseCommand + +from apps.lyric.models import User + + +class Command(BaseCommand): + help = "Create a superuser if none exists" + + def handle(self, *args, **options): + if User.objects.filter(is_superuser=True).exists(): + self.stdout.write("Superuser already exists!") + return + email = os.environ.get('DJANGO_SUPERUSER_EMAIL') + password = os.environ.get('DJANGO_SUPERUSER_PASSWORD') + if not email or not password: + self.stdout.write("Superuser credentials not set!—skipping") + return + User.objects.create_superuser(email=email, password=password) + self.stdout.write("Superuser created!") \ No newline at end of file diff --git a/src/apps/lyric/tests/integrated/test_admin.py b/src/apps/lyric/tests/integrated/test_admin.py new file mode 100644 index 0000000..e0778fd --- /dev/null +++ b/src/apps/lyric/tests/integrated/test_admin.py @@ -0,0 +1,25 @@ +from django.test import TestCase + +from apps.lyric.models import User + + +class UserAdminTest(TestCase): + def setUp(self): + self.superuser = User.objects.create_superuser( + email="admin@example.com", password="secret" + ) + self.client.force_login(self.superuser) + + def test_user_changelist_loads(self): + response = self.client.get("/admin/lyric/user/") + self.assertEqual(response.status_code, 200) + + def test_user_changelist_displays_email(self): + response = self.client.get("/admin/lyric/user/") + self.assertContains(response, "admin@example.com") + + def test_user_changelist_search_by_email(self): + User.objects.create_superuser(email="other@example.com", password="x") + response = self.client.get("/admin/lyric/user/?q=admin") + self.assertContains(response, "admin@example.com") + self.assertNotContains(response, "other@example.com") diff --git a/src/apps/lyric/tests/integrated/test_management_commands.py b/src/apps/lyric/tests/integrated/test_management_commands.py new file mode 100644 index 0000000..3f71e8e --- /dev/null +++ b/src/apps/lyric/tests/integrated/test_management_commands.py @@ -0,0 +1,34 @@ +import os + +from django.core.management import call_command +from django.test import TestCase +from unittest.mock import patch + +# from apps.lyric.management.commands.ensure_superuser import EnsureSuperuserCommand +from apps.lyric.models import User + + +FAKE_ENV = { + 'DJANGO_SUPERUSER_EMAIL': 'admin@example.com', + 'DJANGO_SUPERUSER_PASSWORD': 'secret', +} + + +class EnsureSuperuserCommandTest(TestCase): + def test_creates_superuser_if_none_exists(self): + with patch.dict('os.environ', FAKE_ENV): + call_command('ensure_superuser') + self.assertEqual(User.objects.filter(is_superuser=True).count(), 1) + + def test_does_not_create_duplicate_if_superuser_exists(self): + User.objects.create_superuser(email="admin@example.com", password="secret") + with patch.dict('os.environ', FAKE_ENV): + call_command('ensure_superuser') + self.assertEqual(User.objects.filter(is_superuser=True).count(), 1) + + def test_skips_creation_if_credentials_not_set(self): + with patch.dict("os.environ", {}): + os.environ.pop("DJANGO_SUPERUSER_EMAIL", None) + os.environ.pop("DJANGO_SUPERUSER_PASSWORD", None) + call_command("ensure_superuser") + self.assertEqual(User.objects.filter(is_superuser=True).count(), 0)