readded images
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."))
|
||||
@@ -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}"))
|
||||
|
After Width: | Height: | Size: 7.6 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 186 KiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 616 KiB |
|
After Width: | Height: | Size: 474 KiB |
|
After Width: | Height: | Size: 390 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 531 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 307 KiB |
|
After Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 630 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 665 KiB |
|
After Width: | Height: | Size: 807 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 705 KiB |
|
After Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 760 KiB |
|
After Width: | Height: | Size: 581 KiB |
|
After Width: | Height: | Size: 5.3 MiB |
|
After Width: | Height: | Size: 581 KiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 491 KiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 827 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 907 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 449 KiB |
|
After Width: | Height: | Size: 7.1 MiB |
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 517 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 5.7 MiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 258 KiB |
|
After Width: | Height: | Size: 373 KiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 217 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 437 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 711 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 819 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 394 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 217 KiB |