Cache images instead of serve from Twitch
This commit is contained in:
parent
d434eac74a
commit
b97118cffd
16 changed files with 340 additions and 30 deletions
69
twitch/management/commands/backfill_images.py
Normal file
69
twitch/management/commands/backfill_images.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from twitch.models import DropBenefit, DropCampaign, Game
|
||||
from twitch.utils.images import cache_remote_image
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover - typing only
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from django.db.models import QuerySet
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Backfill local image files for existing rows."""
|
||||
|
||||
help = "Download and cache remote images to MEDIA for Games, Campaigns, and Benefits"
|
||||
|
||||
def add_arguments(self, parser: ArgumentParser) -> None: # type: ignore[override]
|
||||
"""Add CLI arguments for the management command."""
|
||||
parser.add_argument("--limit", type=int, default=0, help="Limit number of objects per model to process (0 = no limit)")
|
||||
|
||||
def handle(self, **options: object) -> None:
|
||||
"""Execute the backfill process using provided options."""
|
||||
limit: int = int(options.get("limit", 0)) # type: ignore[arg-type]
|
||||
|
||||
def maybe_limit(qs: QuerySet) -> QuerySet:
|
||||
"""Apply slicing if --limit is provided.
|
||||
|
||||
Returns:
|
||||
Queryset possibly sliced by the limit.
|
||||
"""
|
||||
return qs[:limit] if limit > 0 else qs
|
||||
|
||||
processed = 0
|
||||
|
||||
for game in maybe_limit(Game.objects.filter(box_art_file__isnull=True).exclude(box_art="")):
|
||||
rel = cache_remote_image(game.box_art, "games/box_art")
|
||||
if rel:
|
||||
game.box_art_file.name = rel
|
||||
game.save(update_fields=["box_art_file"]) # type: ignore[list-item]
|
||||
processed += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Processed game box art: {game.id}"))
|
||||
|
||||
for campaign in maybe_limit(DropCampaign.objects.filter(image_file__isnull=True).exclude(image_url="")):
|
||||
rel = cache_remote_image(campaign.image_url, "campaigns/images")
|
||||
if rel:
|
||||
campaign.image_file.name = rel
|
||||
campaign.save(update_fields=["image_file"]) # type: ignore[list-item]
|
||||
processed += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Processed campaign image: {campaign.id}"))
|
||||
|
||||
for benefit in maybe_limit(DropBenefit.objects.filter(image_file__isnull=True).exclude(image_asset_url="")):
|
||||
rel = cache_remote_image(benefit.image_asset_url, "benefits/images")
|
||||
if rel:
|
||||
benefit.image_file.name = rel
|
||||
benefit.save(update_fields=["image_file"]) # type: ignore[list-item]
|
||||
processed += 1
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Processed benefit image: {benefit.id}"))
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Backfill complete. Updated {processed} images."))
|
||||
|
|
@ -13,6 +13,7 @@ from django.db import transaction
|
|||
from django.utils import timezone
|
||||
|
||||
from twitch.models import Channel, DropBenefit, DropBenefitEdge, DropCampaign, Game, Organization, TimeBasedDrop
|
||||
from twitch.utils.images import cache_remote_image
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime
|
||||
|
|
@ -472,6 +473,13 @@ class Command(BaseCommand):
|
|||
defaults=benefit_defaults,
|
||||
)
|
||||
|
||||
# Cache benefit image if available and not already cached
|
||||
if (not benefit.image_file) and benefit.image_asset_url:
|
||||
rel_path: str | None = cache_remote_image(benefit.image_asset_url, "benefits/images")
|
||||
if rel_path:
|
||||
benefit.image_file.name = rel_path
|
||||
benefit.save(update_fields=["image_file"])
|
||||
|
||||
DropBenefitEdge.objects.update_or_create(
|
||||
drop=time_based_drop,
|
||||
benefit=benefit,
|
||||
|
|
@ -590,6 +598,13 @@ class Command(BaseCommand):
|
|||
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Created new drop campaign: {drop_campaign.name} (ID: {drop_campaign.id})"))
|
||||
|
||||
# Cache campaign image if available and not already cached
|
||||
if (not drop_campaign.image_file) and drop_campaign.image_url:
|
||||
rel_path: str | None = cache_remote_image(drop_campaign.image_url, "campaigns/images")
|
||||
if rel_path:
|
||||
drop_campaign.image_file.name = rel_path
|
||||
drop_campaign.save(update_fields=["image_file"]) # type: ignore[list-item]
|
||||
return drop_campaign
|
||||
|
||||
def owner_update_or_create(self, campaign_data: dict[str, Any]) -> Organization | None:
|
||||
|
|
@ -648,4 +663,11 @@ class Command(BaseCommand):
|
|||
)
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(f"Created new game: {game.display_name} (ID: {game.id})"))
|
||||
|
||||
# Cache game box art if available and not already cached
|
||||
if (not game.box_art_file) and game.box_art:
|
||||
rel_path: str | None = cache_remote_image(game.box_art, "games/box_art")
|
||||
if rel_path:
|
||||
game.box_art_file.name = rel_path
|
||||
game.save(update_fields=["box_art_file"])
|
||||
return game
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue