diff --git a/README.md b/README.md index 38b87d5..6968811 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,25 @@ python manage.py ensure_superuser python manage.py runserver ``` +### Persistent local preview without Docker + +The local preview uses `instance/preview.sqlite3`, which survives server restarts and is +ignored by Git. Put the preview admin credentials in `instance/preview_admin.env` +(also ignored by Git): + +```text +DJANGO_SUPERUSER_USERNAME=admin +DJANGO_SUPERUSER_EMAIL=admin@localhost +DJANGO_SUPERUSER_PASSWORD=your-local-password +DJANGO_SUPERUSER_SYNC_PASSWORD=False +``` + +Then start the preview with `./run_preview.sh`. It applies migrations and ensures the +admin account exists before starting the server. Existing passwords are preserved; +set `DJANGO_SUPERUSER_SYNC_PASSWORD=True` for one start only if you intentionally +need to reset the account password. This preview database is separate from the +PostgreSQL database used by Docker and production. + ## Management commands ```bash diff --git a/apps/core/management/commands/ensure_superuser.py b/apps/core/management/commands/ensure_superuser.py index cc26725..11329f3 100644 --- a/apps/core/management/commands/ensure_superuser.py +++ b/apps/core/management/commands/ensure_superuser.py @@ -31,9 +31,10 @@ class Command(BaseCommand): user.email = email user.is_staff = True user.is_superuser = True + user.is_active = True user.set_password(password) user.save( - update_fields=["email", "is_staff", "is_superuser", "password"] + update_fields=["email", "is_staff", "is_superuser", "is_active", "password"] ) self.stdout.write( self.style.SUCCESS( @@ -41,9 +42,16 @@ class Command(BaseCommand): ) ) return + fields_to_update = [] + for field in ("is_staff", "is_superuser", "is_active"): + if not getattr(user, field): + setattr(user, field, True) + fields_to_update.append(field) + if fields_to_update: + user.save(update_fields=fields_to_update) self.stdout.write( self.style.SUCCESS( - f"Admin '{username}' already exists; its password was preserved." + f"Admin '{username}' is ready; its password was preserved." ) ) return diff --git a/apps/core/tests/test_bootstrap_commands.py b/apps/core/tests/test_bootstrap_commands.py index adebb40..ee3244f 100644 --- a/apps/core/tests/test_bootstrap_commands.py +++ b/apps/core/tests/test_bootstrap_commands.py @@ -76,6 +76,24 @@ class EnsureSuperuserCommandTests(TestCase): user.refresh_from_db() self.assertTrue(user.check_password("changed-in-admin")) + def test_existing_user_regains_admin_access_without_password_reset(self): + user = get_user_model().objects.create_user( + username="admin", email="admin@com2care.com", password="existing-password" + ) + user.is_active = False + user.save(update_fields=["is_active"]) + environment = { + "DJANGO_SUPERUSER_USERNAME": "admin", + "DJANGO_SUPERUSER_PASSWORD": "different-password", + "DJANGO_SUPERUSER_SYNC_PASSWORD": "False", + } + with patch.dict(os.environ, environment, clear=False): + call_command("ensure_superuser", verbosity=0) + + user.refresh_from_db() + self.assertTrue(user.is_active and user.is_staff and user.is_superuser) + self.assertTrue(user.check_password("existing-password")) + def test_server_operator_can_explicitly_rotate_password(self): user = get_user_model().objects.create_superuser( username="admin", diff --git a/config/settings/preview.py b/config/settings/preview.py new file mode 100644 index 0000000..692b01e --- /dev/null +++ b/config/settings/preview.py @@ -0,0 +1,10 @@ +"""Persistent local preview database, separate from the production PostgreSQL database.""" + +from .development import * # noqa: F401,F403 + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "instance" / "preview.sqlite3", + } +} diff --git a/run_preview.sh b/run_preview.sh new file mode 100755 index 0000000..670d1c0 --- /dev/null +++ b/run_preview.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +cd "$(dirname "$0")" +mkdir -p instance + +if [ -f instance/preview_admin.env ]; then + set -a + . ./instance/preview_admin.env + set +a +fi + +export DJANGO_SETTINGS_MODULE=config.settings.preview +PYTHON=${PYTHON:-venv/bin/python} + +"$PYTHON" manage.py migrate --noinput +"$PYTHON" manage.py ensure_superuser +exec "$PYTHON" manage.py runserver 127.0.0.1:8000