added superuser support in apps.lyric.admin & new manage.py cmd ensure_superuser; .tests.integrated.test_admin & .test_management_commands green
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Disco DeDisco
2026-02-22 20:42:33 -05:00
parent 87ef197823
commit 44c335b089
10 changed files with 125 additions and 24 deletions

View File

@@ -133,11 +133,12 @@
DJANGO_DEBUG_FALSE: "1" DJANGO_DEBUG_FALSE: "1"
DJANGO_SECRET_KEY: "{{ secret_key.content | b64decode }}" DJANGO_SECRET_KEY: "{{ secret_key.content | b64decode }}"
DJANGO_ALLOWED_HOST: "{{ django_allowed_host }}" 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" DATABASE_URL: "postgresql://gamearray:{{ postgres_password }}@gamearray_postgres/gamearray"
MAILGUN_API_KEY: "{{ mailgun_api_key }}" MAILGUN_API_KEY: "{{ mailgun_api_key }}"
CELERY_BROKER_URL: "redis://gamearray_redis:6379/0" CELERY_BROKER_URL: "redis://gamearray_redis:6379/0"
REDIS_URL: "redis://gamearray_redis:6379/1" REDIS_URL: "redis://gamearray_redis:6379/1"
networks: networks:
- name: gamearray_net - name: gamearray_net
ports: ports:
@@ -180,6 +181,11 @@
container: gamearray container: gamearray
command: python manage.py migrate command: python manage.py migrate
- name: Ensure superuser exists
community.docker.docker_container_exec:
container: gamearray
command: python manage.py ensure_superuser
handlers: handlers:
- name: Restart nginx - name: Restart nginx
ansible.builtin.service: ansible.builtin.service:

View File

@@ -30,6 +30,9 @@ docker run -d --name gamearray_celery \
echo "==> Running migrations..." echo "==> Running migrations..."
docker exec gamearray python ./manage.py migrate docker exec gamearray python ./manage.py migrate
echo "==> Ensuring superuser exists..."
docker exec gamearray python manage.py ensure_superuser
echo "==> Copying static files..." echo "==> Copying static files..."
sudo docker cp gamearray:/src/static/. /var/www/gamearray/static/ sudo docker cp gamearray:/src/static/. /var/www/gamearray/static/

View File

@@ -1,6 +1,8 @@
DJANGO_DEBUG_FALSE=1 DJANGO_DEBUG_FALSE=1
DJANGO_SECRET_KEY={{ secret_key.content | b64decode }} DJANGO_SECRET_KEY={{ secret_key.content | b64decode }}
DJANGO_ALLOWED_HOST={{ django_allowed_host }} 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 DATABASE_URL=postgresql://gamearray:{{ postgres_password }}@gamearray_postgres/gamearray
MAILGUN_API_KEY={{ mailgun_api_key }} MAILGUN_API_KEY={{ mailgun_api_key }}
CELERY_BROKER_URL=redis://gamearray_redis:6379/0 CELERY_BROKER_URL=redis://gamearray_redis:6379/0

View File

@@ -1,23 +1,28 @@
$ANSIBLE_VAULT;1.1;AES256 $ANSIBLE_VAULT;1.1;AES256
33616230376431343735626631623932393166343538653732383533323436326335343463646664 65383061626464353936363564313761663834646361326362613934363565623234636337313363
6565373531623465613661613533376231373837326438300a393665613839646231633737313938 3933313962643261353830333463336166393030313936370a616234626135633432613366633363
64633035336663313163333634623732323537326363646132313136376131636666636538323066 61633265363937326231623365646336333737306634646335376135633031643564666164336230
3037373930303537320a313062646166353862633836373466316261363939633433663039323866 3435353764383936620a396165386538666433356166383661323037333861373632376432313332
62333739303662343836306538393734343830366336323265393138343438363533353166383031 66666236373462363236663335623734633364653539323331396361613738636166323134386466
32313461313137643039376237346633316466646136353038633861333031663164656233366634 66656431663261633036333537373336643866623236643139656662333831366435373837656262
38303363383130376264373861393863623330623733643135643461383132613339376633353031 36333734376363373462643239623437623735373935633732343639313666663436616630363933
32313863323039646534633733383661333361313832333830383066633130396239626661643264 61396530336461393064323161666537646135383462383532363932326132363331633438313138
65636335303339613432326533343337366261356632313639623634386633383836333733663536 61623431326537313637626239653038353263313731303262653537316134383264616661623962
39383361353530646166643531333535356636326535383534326237666638326137616162646261 32333564366362383431336432303964663835363365636434303332613161363930333065336637
65316466323335653932636338653565383038313531383638393839313736643739363037353230 33343466343062306434663765613837343635386630326439303739616166396134393939626434
35653632353531656435396663316537333133653632366437613339303033333536643937353166 62336634303963653230626630636363343730623734626336363039623231633532653330646366
64363037653733303332643931343362303261643432366531326262383465313965633064356338 66613432633834393133386666623466326131386633303264333766306135623337353433306632
31336333373665373035656533633864316139303934623030383934393434356334643962666163 66323733373232383862646661313966366465333463366361366337656537623562613964666631
33343739366336613263333764306365333566363536616662383733616237396563346132336633 65373566316432383134666434393338626138363632633766636561383263333636623530326664
38663239613339376335386233386330396634323033343332366130616162666339393861306336 63333265366132376437396431393535323931383637323833303839336635633735333565333530
35383566383831356530633130313732356331616164646132626665646235396635386237313538 65343263373630633063383931646163323237643436366566363932646566323539373136646433
38656631336261646530303761643334303937613036363766303637376262373466316431323731 37623638333834373537316164633166633738333363656431356163623332396631353864333333
38666462313639353131303134646434646135366136343361353932326165626666306361393431 33306666646532626636376239326438373737383432663539333736363866663938396136383035
62646238323265346263386363373462313766616333326366366461346436383064336535376339 32343534613862653538346430313338326435356230636535343464666262626663376635363835
31356566356336386262393831616631666233633930393263623563386265343237323133313832 65363862663461353464313533313333323863313539643533343431643130383663656161616131
3430363635363332303963316530663765613666306233376463 33323639333564383830346163386362386238323936393832623961646565613961356263356365
65376431666130356564666236383764316136326366666661326538653133343165326431393564
36303065366263316232663230343137333231346538633036613066643365616331336135376461
35613265623134663633303238366363336137383436663836353863623533396236666433303738
38356361653633323065303035376664326238633066623731623436333332373363636634323433
393631303539373234386465663630316335

View File

@@ -1,6 +1,11 @@
from django.contrib import admin from django.contrib import admin
from .models import Token, User 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) admin.site.register(Token)

View File

View File

@@ -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!")

View File

@@ -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")

View File

@@ -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)