readded images

This commit is contained in:
nvrl committed 2026-02-16 13:41:28 +01:00
1 parent 209af30a43
commit 9878ce4a91
141 files changed
+88 -90

No files matched your search

@@ -3,8 +3,8 @@ import os
import urllib.request
from django.core.management.base import BaseCommand
from django.core.files import File
from tempfile import NamedTemporaryFile
from django.conf import settings
from django.utils.text import slugify
from api.models import Category, Flavor
class Command(BaseCommand):
@@ -14,54 +14,53 @@ class Command(BaseCommand):
if not url:
return None
try:
# Generate a clean, consistent filename
ext = url.split('.')[-1].split('?')[0]
if len(ext) > 4 or len(ext) < 3: ext = 'png'
filename = f"legacy_{flavor_name.replace(' ', '_').lower()}.{ext}"
safe_name = slugify(flavor_name)
ext = url.split('.')[-1].split('?')[0].lower()
if ext not in ['jpg', 'jpeg', 'png', 'webp', 'gif']:
ext = 'png'
# Use a fixed path to check for existing file
filename = f"legacy_{safe_name}.{ext}"
filepath = os.path.join(settings.MEDIA_ROOT, 'flavors', filename)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req) as response:
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(response.read())
img_temp.flush()
img_temp.seek(0)
with urllib.request.urlopen(req, timeout=10) as response:
content = response.read()
# If file exists, delete it so Django doesn't add a suffix
if os.path.exists(filepath):
os.remove(filepath)
return File(img_temp, name=filename)
if not content:
return None
if os.path.exists(filepath):
os.remove(filepath)
with open(filepath, 'wb') as f:
f.write(content)
return f"flavors/{filename}"
except Exception as e:
self.stdout.write(self.style.WARNING(f"Could not download image for {flavor_name}: {e}"))
self.stdout.write(self.style.WARNING(f"Failed to download image for {flavor_name}: {e}"))
return None
def handle(self, *args, **options):
# 1. Remove old legacy flavors (ones without external_id or marked as legacy)
# To be safe, we only remove those marked as legacy or with no external_id
removed_count, _ = Flavor.objects.filter(is_legacy=True).delete()
# Remove old legacy entries
Flavor.objects.filter(is_legacy=True).delete()
Flavor.objects.filter(external_id__isnull=True).delete()
self.stdout.write(self.style.SUCCESS(f"Removed {removed_count} old legacy flavors."))
# Ensure main categories exist
energy = Category.objects.get_or_create(name='Energy', defaults={'slug': 'energy'})[0]
hydration = Category.objects.get_or_create(name='Hydration', defaults={'slug': 'hydration'})[0]
iced_tea = Category.objects.get_or_create(name='Iced Tea', defaults={'slug': 'iced-tea'})[0]
packs = Category.objects.get_or_create(name='Packs and other', defaults={'slug': 'packs-and-other'})[0]
# Try both common locations for the legacy folder
legacy_dir = os.path.join(settings.BASE_DIR, 'legacy')
if not os.path.exists(legacy_dir):
legacy_dir = os.path.join(settings.BASE_DIR, '..', 'legacy')
if not os.path.exists(legacy_dir):
self.stdout.write(self.style.ERROR(f"Legacy directory not found. Checked: {os.path.join(settings.BASE_DIR, 'legacy')} and {os.path.join(settings.BASE_DIR, '..', 'legacy')}"))
self.stdout.write(self.style.ERROR(f"Legacy directory not found."))
return
json_files = [f for f in os.listdir(legacy_dir) if f.endswith('.json')]
created_count = 0
for json_file in json_files:
@@ -78,7 +77,6 @@ class Command(BaseCommand):
full_desc = f"**Geschmack:** {taste}\n\n{desc}\n\n**Status:** {status_text}"
# Determine category
cat = energy
if "hydration" in name.lower() or "hydration" in desc.lower():
cat = hydration
@@ -98,12 +96,20 @@ class Command(BaseCommand):
)
if image_url:
img_file = self.download_image(image_url, name)
if img_file:
flavor.image.save(img_file.name, img_file, save=False)
# Check if local file exists
file_exists = False
if flavor.image:
abs_path = os.path.join(settings.MEDIA_ROOT, flavor.image.name)
if os.path.exists(abs_path):
file_exists = True
if not file_exists:
rel_path = self.download_image(image_url, name)
if rel_path:
flavor.image = rel_path
flavor.save()
created_count += 1
self.stdout.write(f"Added legacy flavor: {name}")
self.stdout.write(self.style.SUCCESS(f"Finished! Created {created_count} legacy flavors from JSON."))
self.stdout.write(self.style.SUCCESS(f"Finished! Created {created_count} legacy flavors."))
+52 -60
View File
@@ -5,7 +5,7 @@ import os
from django.core.management.base import BaseCommand
from django.core.files import File
from django.conf import settings
from tempfile import NamedTemporaryFile
from django.utils.text import slugify
from api.models import Category, Flavor
class Command(BaseCommand):
@@ -22,29 +22,32 @@ class Command(BaseCommand):
if not url:
return None
try:
# Generate a clean, consistent filename
ext = url.split('.')[-1].split('?')[0]
if len(ext) > 4 or len(ext) < 3: ext = 'png'
filename = f"{flavor_name.replace(' ', '_').lower()}.{ext}"
safe_name = slugify(flavor_name)
ext = url.split('.')[-1].split('?')[0].lower()
if ext not in ['jpg', 'jpeg', 'png', 'webp', 'gif']:
ext = 'png'
# Use a fixed path to check for existing file
filename = f"{safe_name}.{ext}"
filepath = os.path.join(settings.MEDIA_ROOT, 'flavors', filename)
# Download to a temporary file
os.makedirs(os.path.dirname(filepath), exist_ok=True)
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
with urllib.request.urlopen(req) as response:
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(response.read())
img_temp.flush()
img_temp.seek(0)
with urllib.request.urlopen(req, timeout=10) as response:
content = response.read()
# If file exists, delete it so Django doesn't add a suffix
if os.path.exists(filepath):
os.remove(filepath)
return File(img_temp, name=filename)
if not content:
return None
if os.path.exists(filepath):
os.remove(filepath)
with open(filepath, 'wb') as f:
f.write(content)
return f"flavors/{filename}"
except Exception as e:
self.stdout.write(self.style.WARNING(f"Could not download image for {flavor_name}: {e}"))
self.stdout.write(self.style.WARNING(f"Failed to download image for {flavor_name}: {e}"))
return None
def handle(self, *args, **options):
@@ -62,7 +65,6 @@ class Command(BaseCommand):
products = data.get('products', [])
self.stdout.write(f"Found {len(products)} products.")
# Ensure categories exist
cat_map = {
'energy': Category.objects.get_or_create(name='Energy', defaults={'slug': 'energy'})[0],
'hydration': Category.objects.get_or_create(name='Hydration', defaults={'slug': 'hydration'})[0],
@@ -78,12 +80,9 @@ class Command(BaseCommand):
for p in products:
title = p.get('title')
tags = p.get('tags', [])
product_type = p.get('product_type', '').lower()
if isinstance(tags, str):
tags = [t.strip() for t in tags.split(',')]
# Determine category
category = None
tags_lower = [t.lower() for t in tags]
title_lower = title.lower()
@@ -94,6 +93,7 @@ class Command(BaseCommand):
if 'shaker' in title_lower or 'merch' in tags_lower:
is_pack = True
category = None
if is_pack:
category = cat_map['packs']
elif 'holy energy' in tags_lower or 'energy' in tags_lower:
@@ -119,51 +119,43 @@ class Command(BaseCommand):
handle = p.get('handle')
shop_url = f"https://weareholy.com/products/{handle}" if handle else None
# Create or update
# Find or create
flavor = Flavor.objects.filter(external_id=p['id']).first()
if not flavor:
flavor = Flavor.objects.filter(name=title, is_legacy=False).first()
if flavor:
flavor.external_id = p['id']
flavor.name = title
flavor.category = category
flavor.description = description
flavor.shop_url = shop_url
flavor.is_available = is_available
flavor.is_legacy = False # API flavors are not legacy
# Cache image if URL changed or local image missing
if image_url and (flavor.image_url != image_url or not flavor.image):
img_file = self.download_image(image_url, title)
if img_file:
flavor.image.save(img_file.name, img_file, save=False)
flavor.image_url = image_url
flavor.save()
updated_count += 1
else:
new_flavor = Flavor(
external_id=p['id'],
name=title,
category=category,
description=description,
image_url=image_url,
shop_url=shop_url,
is_available=is_available,
is_legacy=False
)
if image_url:
img_file = self.download_image(image_url, title)
if img_file:
new_flavor.image.save(img_file.name, img_file, save=False)
new_flavor.save()
if not flavor:
flavor = Flavor(external_id=p['id'], name=title, is_legacy=False)
created_count += 1
else:
updated_count += 1
flavor.category = category
flavor.description = description
flavor.shop_url = shop_url
flavor.is_available = is_available
flavor.external_id = p['id'] # Ensure ID is set
if image_url:
# Check if local file exists
file_exists = False
if flavor.image:
abs_path = os.path.join(settings.MEDIA_ROOT, flavor.image.name)
if os.path.exists(abs_path):
file_exists = True
# Download if missing or URL changed
if not file_exists or flavor.image_url != image_url:
rel_path = self.download_image(image_url, title)
if rel_path:
flavor.image = rel_path
self.stdout.write(f"Downloaded image for: {title}")
else:
self.stdout.write(self.style.WARNING(f"Failed to ensure image for: {title}"))
flavor.image_url = image_url
flavor.save()
synced_external_ids.append(p['id'])
# Mark flavors NOT in the sync as unavailable (only for those that have an external_id)
# These are discontinued API flavors.
discontinued_count = Flavor.objects.filter(external_id__isnull=False).exclude(external_id__in=synced_external_ids).update(is_available=False)
self.stdout.write(self.style.SUCCESS(f"Finished! Created: {created_count}, Updated: {updated_count}, Marked Unavailable: {discontinued_count}"))
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Loaded 100 of 141 files, more files were not shown because too many files have changed in this diff. Show more