from __future__ import annotations from dataclasses import dataclass from django.core.management.base import BaseCommand from apps.blog.models import Post from apps.events.models import Event from apps.gallery.models import Gallery from apps.users.models import User from core.media import safe_process_public_image @dataclass(frozen=True) class BackfillTarget: label: str queryset: object field_name: str family: str class Command(BaseCommand): help = "Generate missing public image derivatives for existing media." def add_arguments(self, parser): parser.add_argument( "--force", action="store_true", help="Regenerate derivatives even when sidecar files already exist.", ) def handle(self, *args, **options): force = options["force"] processed = 0 skipped = 0 failed = 0 targets = ( BackfillTarget("events", Event.objects.exclude(featured_image="").exclude(featured_image__isnull=True), "featured_image", "event_featured"), BackfillTarget("gallery", Gallery.objects.exclude(image="").exclude(image__isnull=True), "image", "gallery"), BackfillTarget("blog", Post.objects.exclude(featured_image="").exclude(featured_image__isnull=True), "featured_image", "blog_featured"), BackfillTarget("users", User.objects.exclude(profile_picture="").exclude(profile_picture__isnull=True), "profile_picture", "profile_picture"), ) for target in targets: self.stdout.write(self.style.NOTICE(f"Backfilling {target.label}...")) for instance in target.queryset.iterator(): file_field = getattr(instance, target.field_name) result = safe_process_public_image(file_field, target.family, force=force) if result is None: failed += 1 self.stdout.write(self.style.WARNING(f"failed: {target.label}#{instance.pk}")) continue if hasattr(instance, "file_size") and hasattr(instance, "width") and hasattr(instance, "height"): type(instance).objects.filter(pk=instance.pk).update( file_size=result.file_size, width=result.width, height=result.height, ) if result.processed: processed += 1 else: skipped += 1 self.stdout.write( self.style.SUCCESS( f"Media derivative backfill finished. processed={processed} skipped={skipped} failed={failed}" ) )