mirror of
https://github.com/narrrl/holy-flavor-rater.git
synced 2026-11-03 16:41:27 +01:00
migrated to redis
This commit is contained in:
1 parent
33e4b0376e
commit
9a3f6b5db8
9 files changed
+112
-45
No files matched your search
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.4 on 2026-04-24 16:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0030_flavor_image_urls'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='name',
|
||||
field=models.CharField(choices=[('sync_flavors', 'Sync Holy Flavors'), ('cleanup_duplicates', 'Cleanup Duplicate Flavors'), ('backup_db', 'Database Backup'), ('seed_legacy', 'Seed Legacy Data'), ('seed_banners', 'Seed Banner Configurations')], max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -56,6 +56,7 @@ class Job(models.Model):
|
||||
("cleanup_duplicates", "Cleanup Duplicate Flavors"),
|
||||
("backup_db", "Database Backup"),
|
||||
("seed_legacy", "Seed Legacy Data"),
|
||||
("seed_banners", "Seed Banner Configurations"),
|
||||
]
|
||||
STATUS_CHOICES = [
|
||||
("pending", "Pending"),
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from api.models import Banner, Job, SystemConfig
|
||||
|
||||
_INTERVAL_PERIOD_TO_TIMEDELTA = {
|
||||
"days": lambda every: timedelta(days=every),
|
||||
"hours": lambda every: timedelta(hours=every),
|
||||
"minutes": lambda every: timedelta(minutes=every),
|
||||
"seconds": lambda every: timedelta(seconds=every),
|
||||
}
|
||||
|
||||
|
||||
class SystemConfigSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
@@ -17,6 +26,21 @@ class SystemConfigSerializer(serializers.ModelSerializer):
|
||||
|
||||
class JobSerializer(serializers.ModelSerializer):
|
||||
name_display = serializers.CharField(source="get_name_display", read_only=True)
|
||||
next_run = serializers.SerializerMethodField()
|
||||
|
||||
def get_next_run(self, obj):
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
|
||||
task = PeriodicTask.objects.filter(name=f"job:{obj.name}", enabled=True).first()
|
||||
if not task or not task.interval:
|
||||
return None
|
||||
base = task.last_run_at or obj.last_run
|
||||
if not base:
|
||||
return None
|
||||
delta_fn = _INTERVAL_PERIOD_TO_TIMEDELTA.get(task.interval.period)
|
||||
if not delta_fn:
|
||||
return None
|
||||
return base + delta_fn(task.interval.every)
|
||||
|
||||
class Meta:
|
||||
model = Job
|
||||
|
||||
+19
-8
@@ -8,7 +8,6 @@ status/output/timestamps so the admin UI keeps working.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from io import StringIO
|
||||
from typing import Any
|
||||
|
||||
@@ -22,7 +21,12 @@ logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
def _run_command_job(job_name: str, command: str, *args: str) -> str:
|
||||
"""Run a management command and mirror status/output into the Job row."""
|
||||
"""Run a management command and mirror status/output into the Job row.
|
||||
|
||||
Scheduling (next_run) is owned by django_celery_beat.PeriodicTask; the Job
|
||||
serializer derives next_run from it. This function only writes execution
|
||||
state: status, last_run, last_output, error_message.
|
||||
"""
|
||||
from api.models import Job
|
||||
|
||||
job, _ = Job.objects.get_or_create(name=job_name)
|
||||
@@ -43,16 +47,18 @@ def _run_command_job(job_name: str, command: str, *args: str) -> str:
|
||||
raise
|
||||
finally:
|
||||
job.last_output = out.getvalue()
|
||||
if job.interval_hours > 0:
|
||||
job.next_run = timezone.now() + timedelta(hours=job.interval_hours)
|
||||
else:
|
||||
job.next_run = None
|
||||
job.save()
|
||||
job.save(update_fields=["status", "last_output", "error_message"])
|
||||
|
||||
return job.last_output
|
||||
|
||||
|
||||
@shared_task(name="api.sync_flavors")
|
||||
@shared_task(
|
||||
name="api.sync_flavors",
|
||||
autoretry_for=(Exception,),
|
||||
retry_backoff=60,
|
||||
retry_backoff_max=600,
|
||||
retry_kwargs={"max_retries": 2},
|
||||
)
|
||||
def sync_flavors_task() -> str:
|
||||
return _run_command_job("sync_flavors", "sync_flavors")
|
||||
|
||||
@@ -72,6 +78,11 @@ def seed_legacy_task() -> str:
|
||||
return _run_command_job("seed_legacy", "seed_legacy_flavors")
|
||||
|
||||
|
||||
@shared_task(name="api.seed_banners")
|
||||
def seed_banners_task() -> str:
|
||||
return _run_command_job("seed_banners", "seed_banners")
|
||||
|
||||
|
||||
@shared_task(
|
||||
name="api.send_email",
|
||||
autoretry_for=(Exception,),
|
||||
|
||||
@@ -20,6 +20,7 @@ JOB_TASK_MAP = {
|
||||
"cleanup_duplicates": "api.cleanup_duplicates",
|
||||
"backup_db": "api.backup_db",
|
||||
"seed_legacy": "api.seed_legacy",
|
||||
"seed_banners": "api.seed_banners",
|
||||
}
|
||||
|
||||
|
||||
@@ -130,35 +131,17 @@ class AdminViewSet(viewsets.ViewSet):
|
||||
def update_job_schedule(self, request: Request, pk=None) -> Response:
|
||||
try:
|
||||
job = Job.objects.get(pk=pk)
|
||||
interval = request.data.get("interval_hours")
|
||||
next_run = request.data.get("next_run")
|
||||
|
||||
if interval is not None:
|
||||
job.interval_hours = int(interval)
|
||||
|
||||
if next_run is not None:
|
||||
from django.utils import timezone
|
||||
from django.utils.dateparse import parse_datetime
|
||||
|
||||
parsed_dt = parse_datetime(next_run)
|
||||
if parsed_dt:
|
||||
if timezone.is_naive(parsed_dt):
|
||||
job.next_run = timezone.make_aware(
|
||||
parsed_dt, timezone.get_current_timezone()
|
||||
)
|
||||
else:
|
||||
job.next_run = parsed_dt
|
||||
elif interval is not None and job.interval_hours > 0 and not job.next_run:
|
||||
from django.utils import timezone
|
||||
|
||||
job.next_run = timezone.now()
|
||||
|
||||
job.save()
|
||||
_sync_periodic_task(job)
|
||||
return Response(JobSerializer(job).data)
|
||||
except Job.DoesNotExist:
|
||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
interval = request.data.get("interval_hours")
|
||||
if interval is not None:
|
||||
job.interval_hours = int(interval)
|
||||
job.save(update_fields=["interval_hours"])
|
||||
|
||||
_sync_periodic_task(job)
|
||||
return Response(JobSerializer(job).data)
|
||||
|
||||
@action(detail=False, methods=["post"])
|
||||
def send_test_email(self, request: Request) -> Response:
|
||||
try:
|
||||
|
||||
@@ -18,15 +18,10 @@ fi
|
||||
echo "Applying database migrations..."
|
||||
python manage.py migrate --noinput
|
||||
|
||||
echo "Creating cache table..."
|
||||
python manage.py createcachetable
|
||||
|
||||
echo "Collecting static files..."
|
||||
python manage.py collectstatic --no-input
|
||||
|
||||
mkdir -p media/flavors
|
||||
mkdir -p django_cache
|
||||
chmod -R 777 django_cache
|
||||
|
||||
# One-shot seed jobs. These should run on image first boot, not on every
|
||||
# restart. A marker file on the bind-mounted volume gates them.
|
||||
@@ -40,8 +35,6 @@ if [ ! -f "$SEED_MARKER" ]; then
|
||||
touch "$SEED_MARKER"
|
||||
else
|
||||
echo "Seed marker present — skipping seed commands. Trigger them from /admin if needed."
|
||||
# Banners are cheap + config-driven; always re-sync them.
|
||||
python manage.py seed_banners || echo "seed_banners failed (non-fatal)"
|
||||
fi
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
|
||||
@@ -87,10 +87,12 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
DJANGO_CACHE_URL = os.environ.get("DJANGO_CACHE_URL", "redis://redis:6379/1")
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
||||
"LOCATION": "django_ratelimit_cache",
|
||||
"BACKEND": "django.core.cache.backends.redis.RedisCache",
|
||||
"LOCATION": DJANGO_CACHE_URL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,3 +191,7 @@ CELERY_TASK_TRACK_STARTED = True
|
||||
CELERY_TASK_TIME_LIMIT = 30 * 60
|
||||
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
||||
CELERY_TASK_ALWAYS_EAGER = os.environ.get("CELERY_TASK_ALWAYS_EAGER", "false").lower() == "true"
|
||||
CELERY_TASK_ACKS_LATE = True
|
||||
CELERY_TASK_REJECT_ON_WORKER_LOST = True
|
||||
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
|
||||
CELERY_RESULT_EXPIRES = 3600
|
||||
Reference in new issue
Block a user