diff --git a/README.md b/README.md index 313af8e..4476d36 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,18 @@ API docs: http://localhost:8000/api/docs/ ``` +Admin panel: + +```text +http://localhost:8000/admin/ +``` + +Create an admin user: + +```powershell +docker compose exec backend python manage.py createsuperuser +``` + ## Architecture ```text diff --git a/backend/README.md b/backend/README.md index 67ff2cc..c276b36 100644 --- a/backend/README.md +++ b/backend/README.md @@ -22,6 +22,20 @@ GET /api/jobs/stats/ POST /api/jobs/{id}/retry/ ``` +## Admin + +The project uses `django-unfold` for the Django admin UI. + +```text +http://localhost:8000/admin/ +``` + +Create a user: + +```powershell +docker compose exec backend python manage.py createsuperuser +``` + ## Worker ```powershell diff --git a/backend/config/settings.py b/backend/config/settings.py index 560e969..ba61b7c 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -12,6 +12,7 @@ DEBUG = os.getenv("DEBUG", "true").lower() in {"1", "true", "yes"} ALLOWED_HOSTS = [host.strip() for host in os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1,0.0.0.0").split(",")] INSTALLED_APPS = [ + "unfold", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -113,6 +114,12 @@ SPECTACULAR_SETTINGS = { "SERVE_INCLUDE_SCHEMA": False, } +UNFOLD = { + "SITE_TITLE": "Minimal Job Queue Admin", + "SITE_HEADER": "Minimal Job Queue", + "SITE_SUBHEADER": "Jobs, attempts, leases, and events", +} + JOB_WORKER_THREADS = int(os.getenv("JOB_WORKER_THREADS", "4")) JOB_WORKER_POLL_INTERVAL_MS = int(os.getenv("JOB_WORKER_POLL_INTERVAL_MS", "500")) JOB_WORKER_LEASE_SECONDS = int(os.getenv("JOB_WORKER_LEASE_SECONDS", "30")) diff --git a/backend/jobs/admin.py b/backend/jobs/admin.py index 373f54b..5365852 100644 --- a/backend/jobs/admin.py +++ b/backend/jobs/admin.py @@ -1,18 +1,34 @@ from django.contrib import admin +from unfold.admin import ModelAdmin from jobs.models import Job, JobEvent @admin.register(Job) -class JobAdmin(admin.ModelAdmin): +class JobAdmin(ModelAdmin): list_display = ("id", "type", "status", "priority", "attempts", "max_attempts", "locked_by", "available_at") list_filter = ("status", "type") search_fields = ("id", "type", "idempotency_key", "locked_by") - readonly_fields = ("created_at", "updated_at", "finished_at") + readonly_fields = ( + "id", + "attempts", + "locked_by", + "locked_until", + "last_error", + "result", + "created_at", + "updated_at", + "finished_at", + ) + ordering = ("-created_at",) + list_per_page = 50 @admin.register(JobEvent) -class JobEventAdmin(admin.ModelAdmin): +class JobEventAdmin(ModelAdmin): list_display = ("id", "job", "type", "attempt", "worker_id", "created_at") list_filter = ("type",) search_fields = ("job__id", "worker_id", "message") + readonly_fields = ("job", "type", "attempt", "worker_id", "message", "data", "created_at") + ordering = ("-id",) + list_per_page = 100 diff --git a/backend/requirements.txt b/backend/requirements.txt index 0bf3ddd..a1c8be0 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,6 +4,7 @@ django-cors-headers>=4.7,<5.0 drf-spectacular>=0.28,<0.29 python-dotenv>=1.1,<2.0 psycopg[binary]>=3.2,<4.0 +django-unfold>=0.76,<1.0 pytest>=8.0,<9.0 pytest-django>=4.9,<5.0 ruff>=0.15,<0.16