feat(admin): add unfold admin panel
This commit is contained in:
12
README.md
12
README.md
@@ -35,6 +35,18 @@ API docs:
|
|||||||
http://localhost:8000/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
|
## Architecture
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
@@ -22,6 +22,20 @@ GET /api/jobs/stats/
|
|||||||
POST /api/jobs/{id}/retry/
|
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
|
## Worker
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|||||||
@@ -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(",")]
|
ALLOWED_HOSTS = [host.strip() for host in os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1,0.0.0.0").split(",")]
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
"unfold",
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
@@ -113,6 +114,12 @@ SPECTACULAR_SETTINGS = {
|
|||||||
"SERVE_INCLUDE_SCHEMA": False,
|
"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_THREADS = int(os.getenv("JOB_WORKER_THREADS", "4"))
|
||||||
JOB_WORKER_POLL_INTERVAL_MS = int(os.getenv("JOB_WORKER_POLL_INTERVAL_MS", "500"))
|
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"))
|
JOB_WORKER_LEASE_SECONDS = int(os.getenv("JOB_WORKER_LEASE_SECONDS", "30"))
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from unfold.admin import ModelAdmin
|
||||||
|
|
||||||
from jobs.models import Job, JobEvent
|
from jobs.models import Job, JobEvent
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Job)
|
@admin.register(Job)
|
||||||
class JobAdmin(admin.ModelAdmin):
|
class JobAdmin(ModelAdmin):
|
||||||
list_display = ("id", "type", "status", "priority", "attempts", "max_attempts", "locked_by", "available_at")
|
list_display = ("id", "type", "status", "priority", "attempts", "max_attempts", "locked_by", "available_at")
|
||||||
list_filter = ("status", "type")
|
list_filter = ("status", "type")
|
||||||
search_fields = ("id", "type", "idempotency_key", "locked_by")
|
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)
|
@admin.register(JobEvent)
|
||||||
class JobEventAdmin(admin.ModelAdmin):
|
class JobEventAdmin(ModelAdmin):
|
||||||
list_display = ("id", "job", "type", "attempt", "worker_id", "created_at")
|
list_display = ("id", "job", "type", "attempt", "worker_id", "created_at")
|
||||||
list_filter = ("type",)
|
list_filter = ("type",)
|
||||||
search_fields = ("job__id", "worker_id", "message")
|
search_fields = ("job__id", "worker_id", "message")
|
||||||
|
readonly_fields = ("job", "type", "attempt", "worker_id", "message", "data", "created_at")
|
||||||
|
ordering = ("-id",)
|
||||||
|
list_per_page = 100
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ django-cors-headers>=4.7,<5.0
|
|||||||
drf-spectacular>=0.28,<0.29
|
drf-spectacular>=0.28,<0.29
|
||||||
python-dotenv>=1.1,<2.0
|
python-dotenv>=1.1,<2.0
|
||||||
psycopg[binary]>=3.2,<4.0
|
psycopg[binary]>=3.2,<4.0
|
||||||
|
django-unfold>=0.76,<1.0
|
||||||
pytest>=8.0,<9.0
|
pytest>=8.0,<9.0
|
||||||
pytest-django>=4.9,<5.0
|
pytest-django>=4.9,<5.0
|
||||||
ruff>=0.15,<0.16
|
ruff>=0.15,<0.16
|
||||||
|
|||||||
Reference in New Issue
Block a user