Laurent 8 months ago
commit de79174f4d
  1. 20
      shop/admin.py
  2. 36
      shop/management/commands/create_initial_shop_data.py
  3. 22
      shop/migrations/0023_alter_color_options_color_ordering.py
  4. 4
      shop/models.py
  5. 46
      shop/static/shop/css/shop.css
  6. BIN
      shop/static/shop/images/products/PC001/blanc/PS_KP912-B_WHITE.png.avif
  7. BIN
      shop/static/shop/images/products/PC001/blanc/PS_KP912-S_WHITE.png.avif
  8. BIN
      shop/static/shop/images/products/PC001/blanc/PS_KP912_WHITE.png.avif
  9. BIN
      shop/static/shop/images/products/PC001/bleu-sport/PS_KP912-B_NAVY.png.avif
  10. BIN
      shop/static/shop/images/products/PC001/bleu-sport/PS_KP912-S_NAVY.png.avif
  11. BIN
      shop/static/shop/images/products/PC001/bleu-sport/PS_KP912_NAVY.png.avif
  12. BIN
      shop/static/shop/images/products/PC001/noir/PS_KP912-B_BLACK.png.avif
  13. BIN
      shop/static/shop/images/products/PC001/noir/PS_KP912-S_BLACK.png.avif
  14. 0
      shop/static/shop/images/products/PC001/noir/noir_hat.png.avif
  15. BIN
      shop/static/shop/images/products/PC002/blanc/PS_K473-B_WHITE.png.avif
  16. BIN
      shop/static/shop/images/products/PC002/blanc/PS_K473-S_WHITE.png.avif
  17. 0
      shop/static/shop/images/products/PC002/blanc/PS_K473_WHITE.png.avif
  18. BIN
      shop/static/shop/images/products/PC002/bleu-sport/PS_K473-B_NAVY.png.avif
  19. BIN
      shop/static/shop/images/products/PC002/bleu-sport/PS_K473-S_NAVY.png.avif
  20. BIN
      shop/static/shop/images/products/PC002/bleu-sport/PS_K473_NAVY.png.avif
  21. BIN
      shop/static/shop/images/products/PC002/fuchsia/PS_K473-B_FUCHSIA.png.avif
  22. BIN
      shop/static/shop/images/products/PC002/fuchsia/PS_K473-S_FUCHSIA.png.avif
  23. BIN
      shop/static/shop/images/products/PC002/fuchsia/PS_K473_FUCHSIA.png.avif
  24. BIN
      shop/static/shop/images/products/PC002/noir/PS_K473-B_BLACK.png.avif
  25. BIN
      shop/static/shop/images/products/PC002/noir/PS_K473-S_BLACK.png.avif
  26. BIN
      shop/static/shop/images/products/PC002/noir/PS_K473_BLACK.png.avif
  27. BIN
      shop/static/shop/images/products/PC003/blanc/PS_K476-B_WHITE.png.avif
  28. BIN
      shop/static/shop/images/products/PC003/blanc/PS_K476-S_WHITE.png.avif
  29. 0
      shop/static/shop/images/products/PC003/blanc/PS_K476_WHITE.png.avif
  30. BIN
      shop/static/shop/images/products/PC003/bleu-sport/PS_K476-B_NAVY.png.avif
  31. BIN
      shop/static/shop/images/products/PC003/bleu-sport/PS_K476-S_NAVY.png.avif
  32. BIN
      shop/static/shop/images/products/PC003/bleu-sport/PS_K476_NAVY.png.avif
  33. BIN
      shop/static/shop/images/products/PC003/fuchsia/PS_K476-B_FUCHSIA.png.avif
  34. BIN
      shop/static/shop/images/products/PC003/fuchsia/PS_K476-S_FUCHSIA.png.avif
  35. BIN
      shop/static/shop/images/products/PC003/fuchsia/PS_K476_FUCHSIA.png.avif
  36. BIN
      shop/static/shop/images/products/PC003/noir/PS_K476-B_BLACK.png.avif
  37. BIN
      shop/static/shop/images/products/PC003/noir/PS_K476-S_BLACK.png.avif
  38. BIN
      shop/static/shop/images/products/PC003/noir/PS_K476_BLACK.png.avif
  39. BIN
      shop/static/shop/images/products/PC004/blanc-bleu-sport/PS_PA4031-B_WHITE-SPORTYNAVY.png.avif
  40. BIN
      shop/static/shop/images/products/PC004/blanc-bleu-sport/PS_PA4031-S_WHITE-SPORTYNAVY.png.avif
  41. 0
      shop/static/shop/images/products/PC004/blanc-bleu-sport/PS_PA4031_WHITE-SPORTYNAVY.png.avif
  42. BIN
      shop/static/shop/images/products/PC004/noir-corail/PS_PA4031-B_BLACK-CORAL.png.avif
  43. BIN
      shop/static/shop/images/products/PC004/noir-corail/PS_PA4031-S_BLACK-CORAL.png.avif
  44. BIN
      shop/static/shop/images/products/PC004/noir-corail/PS_PA4031_BLACK-CORAL.png.avif
  45. BIN
      shop/static/shop/images/products/PC004/noir-gris-fonce-chine/PS_PA4031-B_BLACK-MARLDARKGREY.png.avif
  46. BIN
      shop/static/shop/images/products/PC004/noir-gris-fonce-chine/PS_PA4031-S_BLACK-MARLDARKGREY.png.avif
  47. BIN
      shop/static/shop/images/products/PC004/noir-gris-fonce-chine/PS_PA4031_BLACK-MARLDARKGREY.png.avif
  48. BIN
      shop/static/shop/images/products/PC005/blanc-bleu-sport/PS_PA1031-B_WHITE-SPORTYNAVY.png.avif
  49. BIN
      shop/static/shop/images/products/PC005/blanc-bleu-sport/PS_PA1031-S_WHITE-SPORTYNAVY.png.avif
  50. 0
      shop/static/shop/images/products/PC005/blanc-bleu-sport/PS_PA1031_WHITE-SPORTYNAVY.png.avif
  51. BIN
      shop/static/shop/images/products/PC005/bleu-sport-blanc/PS_PA1031-B_SPORTYNAVY-WHITE.png.avif
  52. BIN
      shop/static/shop/images/products/PC005/bleu-sport-blanc/PS_PA1031-S_SPORTYNAVY-WHITE.png.avif
  53. BIN
      shop/static/shop/images/products/PC005/bleu-sport-blanc/PS_PA1031_SPORTYNAVY-WHITE.png.avif
  54. BIN
      shop/static/shop/images/products/PC005/corail-noir/PS_PA1031-B_CORAL-BLACK.png.avif
  55. BIN
      shop/static/shop/images/products/PC005/corail-noir/PS_PA1031-S_CORAL-BLACK.png.avif
  56. BIN
      shop/static/shop/images/products/PC005/corail-noir/PS_PA1031_CORAL-BLACK.png.avif
  57. BIN
      shop/static/shop/images/products/PC005/noir-gris-fonce-chine/PS_PA1031-B_BLACK-MARLDARKGREY.png.avif
  58. BIN
      shop/static/shop/images/products/PC005/noir-gris-fonce-chine/PS_PA1031-S_BLACK-MARLDARKGREY.png.avif
  59. BIN
      shop/static/shop/images/products/PC005/noir-gris-fonce-chine/PS_PA1031_BLACK-MARLDARKGREY.png.avif
  60. BIN
      shop/static/shop/images/products/PC006/blanc-gris-clair/PS_PA4030-B_WHITE-FINEGREY.png.avif
  61. BIN
      shop/static/shop/images/products/PC006/blanc-gris-clair/PS_PA4030-S_WHITE-FINEGREY.png.avif
  62. BIN
      shop/static/shop/images/products/PC006/blanc-gris-clair/PS_PA4030_WHITE-FINEGREY.png.avif
  63. BIN
      shop/static/shop/images/products/PC006/bleu-sport-blanc/PS_PA4030-B_SPORTYNAVY-WHITE.png.avif
  64. BIN
      shop/static/shop/images/products/PC006/bleu-sport-blanc/PS_PA4030-S_SPORTYNAVY-WHITE.png.avif
  65. 0
      shop/static/shop/images/products/PC006/bleu-sport-blanc/PS_PA4030_SPORTYNAVY-WHITE.png.avif
  66. BIN
      shop/static/shop/images/products/PC006/bleu-sport-bleu-sport-chine/PS_PA4030-B_SPORTYNAVY-MARLSPORTYNAVY.png.avif
  67. BIN
      shop/static/shop/images/products/PC006/bleu-sport-bleu-sport-chine/PS_PA4030-S_SPORTYNAVY-MARLSPORTYNAVY.png.avif
  68. BIN
      shop/static/shop/images/products/PC006/bleu-sport-bleu-sport-chine/PS_PA4030_SPORTYNAVY-MARLSPORTYNAVY.png.avif
  69. BIN
      shop/static/shop/images/products/PC006/noir-gris-fonce-chine/PS_PA4030-B_BLACK-MARLDARKGREY.png.avif
  70. BIN
      shop/static/shop/images/products/PC006/noir-gris-fonce-chine/PS_PA4030-S_BLACK-MARLDARKGREY.png.avif
  71. BIN
      shop/static/shop/images/products/PC006/noir-gris-fonce-chine/PS_PA4030_BLACK-MARLDARKGREY.png.avif
  72. BIN
      shop/static/shop/images/products/PC006/noir/PS_PA4030-B_BLACK.png.avif
  73. BIN
      shop/static/shop/images/products/PC006/noir/PS_PA4030-S_BLACK.png.avif
  74. BIN
      shop/static/shop/images/products/PC006/noir/PS_PA4030_BLACK.png.avif
  75. BIN
      shop/static/shop/images/products/PC007/blanc-bleu-sport/PS_PA1030-B_WHITE-SPORTYNAVY.png.avif
  76. BIN
      shop/static/shop/images/products/PC007/blanc-bleu-sport/PS_PA1030-S_WHITE-SPORTYNAVY.png.avif
  77. 0
      shop/static/shop/images/products/PC007/blanc-bleu-sport/PS_PA1030_WHITE-SPORTYNAVY.png.avif
  78. BIN
      shop/static/shop/images/products/PC007/blanc-gris-clair/PS_PA1030-B_WHITE-FINEGREY.png.avif
  79. BIN
      shop/static/shop/images/products/PC007/blanc-gris-clair/PS_PA1030-S_WHITE-FINEGREY.png.avif
  80. BIN
      shop/static/shop/images/products/PC007/blanc-gris-clair/PS_PA1030_WHITE-FINEGREY.png.avif
  81. BIN
      shop/static/shop/images/products/PC007/gris-fonce-chine-noir/PS_PA1030-B_MARLDARKGREY-BLACK.png.avif
  82. BIN
      shop/static/shop/images/products/PC007/gris-fonce-chine-noir/PS_PA1030-S_MARLDARKGREY-BLACK.png.avif
  83. BIN
      shop/static/shop/images/products/PC007/gris-fonce-chine-noir/PS_PA1030_MARLDARKGREY-BLACK.png.avif
  84. BIN
      shop/static/shop/images/products/PC007/noir/PS_PA1030-B_BLACK.png.avif
  85. BIN
      shop/static/shop/images/products/PC007/noir/PS_PA1030-S_BLACK.png.avif
  86. BIN
      shop/static/shop/images/products/PC007/noir/PS_PA1030_BLACK.png.avif
  87. BIN
      shop/static/shop/images/products/hat.jpg
  88. BIN
      shop/static/shop/images/products/tshirt_h.png
  89. 70
      shop/templates/shop/product_item.html
  90. 119
      shop/templatetags/shop_extras.py

@ -1,5 +1,6 @@
from django.contrib import admin
from .models import Product, Color, Size, Order, OrderItem, GuestUser
from django.utils.html import format_html
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
@ -7,7 +8,24 @@ class ProductAdmin(admin.ModelAdmin):
@admin.register(Color)
class ColorAdmin(admin.ModelAdmin):
list_display = ("name",)
list_display = ("color_preview", "name", "ordering", "colorHex", "secondary_hex_color")
list_editable = ("ordering",)
ordering = ["ordering"]
search_fields = ["name"]
list_per_page = 20
def color_preview(self, obj):
if obj.secondary_hex_color:
return format_html(
'<div style="background-image: linear-gradient(to right, {} 50%, {} 50%); '
'width: 60px; height: 30px; border-radius: 15px; border: 1px solid #ddd;"></div>',
obj.colorHex, obj.secondary_hex_color
)
return format_html(
'<div style="background-color: {}; width: 60px; height: 30px; '
'border-radius: 15px; border: 1px solid #ddd;"></div>',
obj.colorHex
)
@admin.register(Size)
class SizeAdmin(admin.ModelAdmin):

@ -9,16 +9,18 @@ class Command(BaseCommand):
# Create colors
self.stdout.write('Creating colors...')
colors = [
{'name': 'Noir', 'hex': '#333333', 'secondary_hex': None},
{'name': 'Noir / Gris Foncé Chiné', 'hex': '#000000', 'secondary_hex': '#4D4D4D'},
{'name': 'Bleu Sport', 'hex': '#112B44', 'secondary_hex': None},
{'name': 'Bleu Sport / Bleu Sport Chiné', 'hex': '#112B44', 'secondary_hex': '#16395A'},
{'name': 'Bleu Sport / Blanc', 'hex': '#112B44', 'secondary_hex': '#FFFFFF'},
{'name': 'Blanc / Gris Clair', 'hex': '#FFFFFF', 'secondary_hex': '#D3D3D3'},
{'name': 'Fuchsia', 'hex': '#C1366B', 'secondary_hex': None},
{'name': 'Corail / Noir', 'hex': '#FF7F50', 'secondary_hex': '#000000'},
{'name': 'Blanc / Bleu Sport', 'hex': '#FFFFFF', 'secondary_hex': '#112B44'},
{'name': 'Blanc', 'hex': '#FFFFFF', 'secondary_hex': None},
{'name': 'Blanc', 'hex': '#FFFFFF', 'secondary_hex': None, 'ordering': 10},
{'name': 'Blanc / Bleu Sport', 'hex': '#FFFFFF', 'secondary_hex': '#112B44', 'ordering': 11},
{'name': 'Blanc / Gris Clair', 'hex': '#FFFFFF', 'secondary_hex': '#D3D3D3', 'ordering': 12},
{'name': 'Bleu Sport', 'hex': '#112B44', 'secondary_hex': None, 'ordering': 20},
{'name': 'Bleu Sport / Blanc', 'hex': '#112B44', 'secondary_hex': '#FFFFFF', 'ordering': 21},
{'name': 'Bleu Sport / Bleu Sport Chiné', 'hex': '#112B44', 'secondary_hex': '#16395A', 'ordering': 22},
{'name': 'Fuchsia', 'hex': '#C1366B', 'secondary_hex': None, 'ordering': 30},
{'name': 'Corail / Noir', 'hex': '#FF7F50', 'secondary_hex': '#000000', 'ordering': 40},
{'name': 'Gris Foncé Chiné / Noir', 'hex': '#4D4D4D', 'secondary_hex': '#000000', 'ordering': 50},
{'name': 'Noir', 'hex': '#333333', 'secondary_hex': None, 'ordering': 60},
{'name': 'Noir / Corail', 'hex': '#000000', 'secondary_hex': '#FF7F50', 'ordering': 61},
{'name': 'Noir / Gris Foncé Chiné', 'hex': '#000000', 'secondary_hex': '#4D4D4D', 'ordering': 62},
]
color_objects = {}
@ -27,21 +29,19 @@ class Command(BaseCommand):
name=color_data['name'],
defaults={
'colorHex': color_data['hex'],
'secondary_hex_color': color_data['secondary_hex']
'secondary_hex_color': color_data['secondary_hex'],
'ordering': color_data['ordering']
}
)
color_objects[color_data['name']] = color
if created:
self.stdout.write(f'Created color: {color_data["name"]}')
else:
# Update existing colors with secondary color if needed
if color.colorHex != color_data['hex'] or color.secondary_hex_color != color_data['secondary_hex']:
color.colorHex = color_data['hex']
color.secondary_hex_color = color_data['secondary_hex']
color.ordering = color_data['ordering']
color.save()
self.stdout.write(f'Updated color: {color_data["name"]}')
else:
self.stdout.write(f'Color already exists: {color_data["name"]}')
# Create sizes
self.stdout.write('Creating sizes...')
@ -99,7 +99,7 @@ class Command(BaseCommand):
'price': 25.00,
'ordering_value': 20,
'cut': 1, # Women
'colors': ['Blanc / Bleu Sport', 'Noir', 'Noir / Gris Foncé Chiné'],
'colors': ['Blanc / Bleu Sport', 'Noir / Corail', 'Noir / Gris Foncé Chiné'],
'sizes': ['XS', 'S', 'M', 'L', 'XL'],
'image_filename': 'PS_PA4031_WHITE-SPORTYNAVY.png.avif'
},
@ -121,7 +121,7 @@ class Command(BaseCommand):
'price': 25.00,
'ordering_value': 40,
'cut': 2, # Men
'colors': ['Blanc / Bleu Sport', 'Noir', 'Noir / Gris Foncé Chiné'],
'colors': ['Blanc / Gris Clair', 'Bleu Sport / Blanc', 'Bleu Sport / Bleu Sport Chine', 'Noir', 'Noir / Gris Foncé Chiné'],
'sizes': ['S', 'M', 'L', 'XL', 'XXL', '3XL'],
'image_filename': 'tshirt_h.png'
},
@ -132,7 +132,7 @@ class Command(BaseCommand):
'price': 30.00,
'ordering_value': 50,
'cut': 2, # Men
'colors': ['Blanc / Bleu Sport', 'Noir', 'Noir / Gris Foncé Chiné'],
'colors': ['Blanc / Bleu Sport', 'Blanc / Gris Clair', 'Noir', 'Gris Foncé Chiné / Noir'],
'sizes': ['S', 'M', 'L', 'XL', 'XXL', '3XL'],
'image_filename': 'PS_PA1030_WHITE-SPORTYNAVY.png.avif'
},

@ -0,0 +1,22 @@
# Generated by Django 5.1 on 2025-03-27 11:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('shop', '0022_alter_cartitem_options_alter_orderitem_options'),
]
operations = [
migrations.AlterModelOptions(
name='color',
options={'ordering': ['ordering']},
),
migrations.AddField(
model_name='color',
name='ordering',
field=models.IntegerField(default=0),
),
]

@ -18,6 +18,10 @@ class Color(models.Model):
colorHex = models.CharField(max_length=7, default="#FFFFFF", help_text="Color in hex format (e.g. #FF0000)")
secondary_hex_color = models.CharField(max_length=7, null=True, blank=True,
help_text="Secondary color in hex format for split color display")
ordering = models.IntegerField(default=0)
class Meta:
ordering = ['ordering'] # This will make queries respect the ordering by default
def __str__(self):
return self.name

@ -12,6 +12,7 @@
object-fit: contain; /* This will maintain the aspect ratio of the image */
background-color: white;
border-radius: 12px;
display: none; /* Hide all images by default */
}
.no-image {
@ -480,3 +481,48 @@ v .cart-table {
position: relative;
overflow: hidden;
}
.slider-container {
position: relative;
width: 100%;
max-height: 240px; /* Match your original height */
}
.slides {
position: relative;
width: 100%;
height: 100%;
}
.product-image.active {
display: block;
}
.prev,
.next {
cursor: pointer;
position: absolute;
top: 50%;
transform: translateY(-50%);
background: none;
color: black;
border: none;
font-size: 24px;
padding: 8px;
opacity: 0.6;
transition: opacity 0.3s;
z-index: 2;
}
.prev {
left: 5px;
}
.next {
right: 5px;
}
.prev:hover,
.next:hover {
opacity: 1;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

@ -2,10 +2,21 @@
<div class="small-12 medium-6 large-3 my-block">
<div class="bubble">
{% if product.image %}
<img id="product-image-{{ product.id }}"
src="{{ product.image|color_image_url:product.colors.all.0.name }}"
<div class="slider-container" id="slider-{{ product.id }}">
{% color_images_url product.image product.colors.all.0.name product.sku as images %}
<div class="slides">
{% for image_url in images %}
<img src="{{ image_url }}"
alt="{{ product.title }}"
class="product-image">
class="product-image {% if forloop.first %}active{% endif %}"
id="product-image-{{ product.id }}-{{ forloop.counter0 }}">
{% endfor %}
</div>
{% if images|length > 1 %}
<button class="prev" onclick="changeSlide({{ product.id }}, -1)"></button>
<button class="next" onclick="changeSlide({{ product.id }}, 1)"></button>
{% endif %}
</div>
{% else %}
<div class="no-image">No Image Available</div>
{% endif %}
@ -28,6 +39,7 @@
<input type="hidden" name="color" id="color-{{ product.id }}" value="{{ product.colors.all.0.id }}" required>
<div class="color-samples">
{% for color in product.colors.all %}
{% color_images_url product.image color.name product.sku as color_images %}
<div class="color-sample {% if forloop.first %}selected{% endif %}"
{% if color.secondary_hex_color %}
style="background-image: linear-gradient(to right, {{ color.colorHex }} 50%, {{ color.secondary_hex_color }} 50%);"
@ -37,8 +49,9 @@
title="{{ color.name }}"
data-color-id="{{ color.id }}"
data-color-name="{{ color.name }}"
data-color-image="{{ product.image|color_image_url:color.name }}"
onclick="selectColor('{{ product.id }}', '{{ color.id }}', '{{ color.name }}', this)"></div>
data-color-images="{{ color_images|join:',' }}"
onclick="selectColor('{{ product.id }}', '{{ color.id }}', '{{ color.name }}', this)">
</div>
{% endfor %}
</div>
{% endif %}
@ -129,14 +142,30 @@ function selectColor(productId, colorId, colorName, element) {
// Add selected class to clicked color
element.classList.add('selected');
// Update product image based on selected color
const productImage = document.getElementById(`product-image-${productId}`);
// Update product images based on selected color
const slider = document.getElementById(`slider-${productId}`);
if (slider) {
const colorImages = element.getAttribute('data-color-images').split(',');
const slidesContainer = slider.querySelector('.slides');
if (productImage) {
const colorImage = element.getAttribute('data-color-image');
if (colorImage) {
productImage.src = colorImage;
}
// Clear existing slides
slidesContainer.innerHTML = '';
// Add new slides
colorImages.forEach((imageUrl, index) => {
const img = document.createElement('img');
img.src = imageUrl;
img.alt = colorName;
img.className = `product-image ${index === 0 ? 'active' : ''}`;
img.id = `product-image-${productId}-${index}`;
slidesContainer.appendChild(img);
});
// Update navigation buttons visibility
const navButtons = slider.querySelectorAll('.prev, .next');
navButtons.forEach(button => {
button.style.display = colorImages.length > 1 ? 'block' : 'none';
});
}
}
@ -212,4 +241,21 @@ function addToCartAjax(productId) {
}, 3000);
});
}
function changeSlide(productId, direction) {
const slides = document.querySelectorAll(`#slider-${productId} .product-image`);
let activeIndex = Array.from(slides).findIndex(slide => slide.classList.contains('active'));
// Remove active class from current slide
slides[activeIndex].classList.remove('active');
// Calculate new index
activeIndex = activeIndex + direction;
if (activeIndex >= slides.length) activeIndex = 0;
if (activeIndex < 0) activeIndex = slides.length - 1;
// Add active class to new slide
slides[activeIndex].classList.add('active');
}
</script>

@ -2,69 +2,66 @@ from django import template
import os
register = template.Library()
from django.conf import settings
@register.filter
def color_image_url(product_image, color_name):
@register.simple_tag
def color_images_url(default_image, color_name, sku):
"""
Returns color-specific image URL with any supported extension.
Falls back to the original image if no color variant exists.
Returns color-specific image URLs from the color-specific folder structure.
Structure expected:
static/shop/images/products/
{sku}/
default.jpg (or any supported extension)
{color_name}/
image.jpg (or any supported extension)
"""
if not product_image or not color_name:
return product_image
# Generate color suffix
suffix = generate_color_suffix(color_name)
# Split path
directory, filename = os.path.split(product_image)
base_name, original_ext = os.path.splitext(filename)
# List of supported image extensions to check
# List of supported image extensions
supported_extensions = ['.png.avif', '.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif']
# Check for the color image with original extension first
color_filename = f"{suffix}_{base_name}{original_ext}"
color_image = os.path.join(directory, color_filename)
# Extract the path after /static/
static_prefix = '/static/'
if color_image.startswith(static_prefix):
rel_path = color_image[len(static_prefix):]
else:
rel_path = color_image.lstrip('/')
# Check if file with original extension exists
from django.conf import settings
app_static_path = os.path.join(settings.BASE_DIR, 'shop', 'static', rel_path)
if os.path.exists(app_static_path):
return color_image
# If not found with original extension, try other extensions
for ext in supported_extensions:
if ext == original_ext:
continue # Skip the original extension as we already checked it
color_filename = f"{suffix}_{base_name}{ext}"
color_image = os.path.join(directory, color_filename)
if color_image.startswith(static_prefix):
rel_path = color_image[len(static_prefix):]
else:
rel_path = color_image.lstrip('/')
app_static_path = os.path.join(settings.BASE_DIR, 'shop', 'static', rel_path)
if os.path.exists(app_static_path):
return color_image
# If no color variant is found with any extension, return the original image
return product_image
def generate_color_suffix(color_name):
# Base path for products
base_path = f'/static/shop/images/products/{sku}/'
physical_base_path = os.path.join(settings.BASE_DIR, 'shop', 'static', 'shop', 'images', 'products', sku)
if color_name:
# Generate color folder name (sanitize the color name)
color_folder = generate_color_folder_name(color_name)
# Check color-specific folder
color_path = os.path.join(physical_base_path, color_folder)
if os.path.exists(color_path):
# Get all images from color folder
files = [f for f in os.listdir(color_path)
if any(f.lower().endswith(ext) for ext in supported_extensions)]
if files:
# Sort files by specific prefix rules
files.sort(key=lambda x: (
1 if '-B_' in x else
2 if '-S_' in x else
0
))
return [f'{base_path}{color_folder}/{file}' for file in files]
# If no color-specific image found, look for default image in product folder
if os.path.exists(physical_base_path):
files = [f for f in os.listdir(physical_base_path)
if os.path.isfile(os.path.join(physical_base_path, f)) and
any(f.lower().endswith(ext) for ext in supported_extensions)]
if files:
files.sort(key=lambda x: (
1 if '-B_' in x else
2 if '-S_' in x else
0
))
return [f'{base_path}{file}' for file in files]
# If nothing found, return list with default image
return [default_image]
def generate_color_folder_name(color_name):
"""
Generates a URL-friendly suffix from a color name
Example: "Noir / Gris Foncé Chiné" becomes "noir_gris_fonce_chine"
Generates a folder-friendly name from a color name
Example: "Noir / Gris Foncé Chiné" becomes "noir-gris-fonce-chine"
"""
import unicodedata
import re
@ -73,10 +70,10 @@ def generate_color_suffix(color_name):
value = color_name.lower()
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
# Replace slashes and spaces with underscores
value = re.sub(r'[/\s]+', '_', value)
# Replace slashes and spaces with hyphens
value = re.sub(r'[/\s]+', '-', value)
# Remove any remaining non-alphanumeric characters
value = re.sub(r'[^\w_]', '', value)
# Remove any remaining non-alphanumeric characters except hyphens
value = re.sub(r'[^\w-]', '', value)
return value

Loading…
Cancel
Save