Laurent 8 months ago
commit de79174f4d
  1. 20
      shop/admin.py
  2. 44
      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. 82
      shop/templates/shop/product_item.html
  90. 119
      shop/templatetags/shop_extras.py

@ -1,5 +1,6 @@
from django.contrib import admin from django.contrib import admin
from .models import Product, Color, Size, Order, OrderItem, GuestUser from .models import Product, Color, Size, Order, OrderItem, GuestUser
from django.utils.html import format_html
@admin.register(Product) @admin.register(Product)
class ProductAdmin(admin.ModelAdmin): class ProductAdmin(admin.ModelAdmin):
@ -7,7 +8,24 @@ class ProductAdmin(admin.ModelAdmin):
@admin.register(Color) @admin.register(Color)
class ColorAdmin(admin.ModelAdmin): 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) @admin.register(Size)
class SizeAdmin(admin.ModelAdmin): class SizeAdmin(admin.ModelAdmin):

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

@ -12,6 +12,7 @@
object-fit: contain; /* This will maintain the aspect ratio of the image */ object-fit: contain; /* This will maintain the aspect ratio of the image */
background-color: white; background-color: white;
border-radius: 12px; border-radius: 12px;
display: none; /* Hide all images by default */
} }
.no-image { .no-image {
@ -480,3 +481,48 @@ v .cart-table {
position: relative; position: relative;
overflow: hidden; 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="small-12 medium-6 large-3 my-block">
<div class="bubble"> <div class="bubble">
{% if product.image %} {% if product.image %}
<img id="product-image-{{ product.id }}" <div class="slider-container" id="slider-{{ product.id }}">
src="{{ product.image|color_image_url:product.colors.all.0.name }}" {% color_images_url product.image product.colors.all.0.name product.sku as images %}
alt="{{ product.title }}" <div class="slides">
class="product-image"> {% for image_url in images %}
<img src="{{ image_url }}"
alt="{{ product.title }}"
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 %} {% else %}
<div class="no-image">No Image Available</div> <div class="no-image">No Image Available</div>
{% endif %} {% endif %}
@ -28,17 +39,19 @@
<input type="hidden" name="color" id="color-{{ product.id }}" value="{{ product.colors.all.0.id }}" required> <input type="hidden" name="color" id="color-{{ product.id }}" value="{{ product.colors.all.0.id }}" required>
<div class="color-samples"> <div class="color-samples">
{% for color in product.colors.all %} {% 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 %}" <div class="color-sample {% if forloop.first %}selected{% endif %}"
{% if color.secondary_hex_color %} {% if color.secondary_hex_color %}
style="background-image: linear-gradient(to right, {{ color.colorHex }} 50%, {{ color.secondary_hex_color }} 50%);" style="background-image: linear-gradient(to right, {{ color.colorHex }} 50%, {{ color.secondary_hex_color }} 50%);"
{% else %} {% else %}
style="background-color: {{ color.colorHex }};" style="background-color: {{ color.colorHex }};"
{% endif %} {% endif %}
title="{{ color.name }}" title="{{ color.name }}"
data-color-id="{{ color.id }}" data-color-id="{{ color.id }}"
data-color-name="{{ color.name }}" data-color-name="{{ color.name }}"
data-color-image="{{ product.image|color_image_url:color.name }}" data-color-images="{{ color_images|join:',' }}"
onclick="selectColor('{{ product.id }}', '{{ color.id }}', '{{ color.name }}', this)"></div> onclick="selectColor('{{ product.id }}', '{{ color.id }}', '{{ color.name }}', this)">
</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
@ -129,14 +142,30 @@ function selectColor(productId, colorId, colorName, element) {
// Add selected class to clicked color // Add selected class to clicked color
element.classList.add('selected'); element.classList.add('selected');
// Update product image based on selected color // Update product images based on selected color
const productImage = document.getElementById(`product-image-${productId}`); const slider = document.getElementById(`slider-${productId}`);
if (slider) {
const colorImages = element.getAttribute('data-color-images').split(',');
const slidesContainer = slider.querySelector('.slides');
if (productImage) { // Clear existing slides
const colorImage = element.getAttribute('data-color-image'); slidesContainer.innerHTML = '';
if (colorImage) {
productImage.src = colorImage; // 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); }, 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> </script>

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

Loading…
Cancel
Save