Добавлена модель атрибутов для вариативных товаров (ConfigurableKitProductAttribute)
- Создана модель ConfigurableKitProductAttribute с полями name, option, position, visible - Добавлены формы и formsets для управления атрибутами родительского товара - Обновлены CRUD представления для работы с атрибутами (создание/редактирование) - Добавлен блок атрибутов в шаблоны создания/редактирования - Обновлена страница детального просмотра с отображением атрибутов товара - Добавлен JavaScript для динамического добавления форм атрибутов - Реализована валидация дубликатов атрибутов в formset - Атрибуты сохраняются в transaction.atomic() вместе с вариантами Теперь можно определять схему атрибутов для экспорта на WooCommerce без использования JSON или ID, только name и option.
This commit is contained in:
@@ -32,7 +32,7 @@ from .variants import ProductVariantGroup, ProductVariantGroupItem
|
||||
from .products import Product
|
||||
|
||||
# Комплекты
|
||||
from .kits import ProductKit, KitItem, KitItemPriority
|
||||
from .kits import ProductKit, KitItem, KitItemPriority, ConfigurableKitProduct, ConfigurableKitOption, ConfigurableKitProductAttribute
|
||||
|
||||
# Фотографии
|
||||
from .photos import BasePhoto, ProductPhoto, ProductKitPhoto, ProductCategoryPhoto, PhotoProcessingStatus
|
||||
@@ -63,6 +63,9 @@ __all__ = [
|
||||
'ProductKit',
|
||||
'KitItem',
|
||||
'KitItemPriority',
|
||||
'ConfigurableKitProduct',
|
||||
'ConfigurableKitOption',
|
||||
'ConfigurableKitProductAttribute',
|
||||
|
||||
# Photos
|
||||
'BasePhoto',
|
||||
|
||||
@@ -378,3 +378,113 @@ class KitItemPriority(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.product.name} (приоритет {self.priority})"
|
||||
|
||||
|
||||
class ConfigurableKitProduct(BaseProductEntity):
|
||||
"""
|
||||
Вариативный товар, объединяющий несколько наших ProductKit
|
||||
как варианты для внешних площадок (WooCommerce и подобные).
|
||||
"""
|
||||
class Meta:
|
||||
verbose_name = "Вариативный товар (из комплектов)"
|
||||
verbose_name_plural = "Вариативные товары (из комплектов)"
|
||||
# Уникальность активного имени наследуется из BaseProductEntity
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""
|
||||
Физическое удаление вариативного товара из БД.
|
||||
При удалении удаляются только связи (ConfigurableKitOption),
|
||||
но сами ProductKit остаются нетронутыми благодаря CASCADE на уровне связей.
|
||||
"""
|
||||
# Используем super() для вызова стандартного Django delete, минуя BaseProductEntity.delete()
|
||||
super(BaseProductEntity, self).delete(*args, **kwargs)
|
||||
|
||||
|
||||
class ConfigurableKitProductAttribute(models.Model):
|
||||
"""
|
||||
Атрибут родительского вариативного товара.
|
||||
Определяет схему атрибутов для экспорта на WooCommerce и подобные площадки.
|
||||
Например: name="Цвет", option="Красный" или name="Размер", option="M".
|
||||
"""
|
||||
parent = models.ForeignKey(
|
||||
ConfigurableKitProduct,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='parent_attributes',
|
||||
verbose_name="Родительский товар"
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=150,
|
||||
verbose_name="Название атрибута",
|
||||
help_text="Например: Цвет, Размер, Длина"
|
||||
)
|
||||
option = models.CharField(
|
||||
max_length=150,
|
||||
verbose_name="Значение опции",
|
||||
help_text="Например: Красный, M, 60см"
|
||||
)
|
||||
position = models.PositiveIntegerField(
|
||||
default=0,
|
||||
verbose_name="Порядок отображения",
|
||||
help_text="Меньше = выше в списке"
|
||||
)
|
||||
visible = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name="Видимый на витрине",
|
||||
help_text="Показывать ли атрибут на странице товара"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Атрибут вариативного товара"
|
||||
verbose_name_plural = "Атрибуты вариативных товаров"
|
||||
ordering = ['parent', 'position', 'name', 'option']
|
||||
unique_together = [['parent', 'name', 'option']]
|
||||
indexes = [
|
||||
models.Index(fields=['parent', 'name']),
|
||||
models.Index(fields=['parent', 'position']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.parent.name} - {self.name}: {self.option}"
|
||||
|
||||
|
||||
class ConfigurableKitOption(models.Model):
|
||||
"""
|
||||
Отдельный вариант внутри ConfigurableKitProduct, указывающий на конкретный ProductKit.
|
||||
Атрибуты варианта хранятся простым текстом (можно расширить до JSON позже).
|
||||
"""
|
||||
parent = models.ForeignKey(
|
||||
ConfigurableKitProduct,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='options',
|
||||
verbose_name="Родитель (вариативный товар)"
|
||||
)
|
||||
kit = models.ForeignKey(
|
||||
ProductKit,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='as_configurable_option_in',
|
||||
verbose_name="Комплект (вариант)"
|
||||
)
|
||||
attributes = models.TextField(
|
||||
blank=True,
|
||||
verbose_name="Атрибуты варианта (для внешних площадок)"
|
||||
)
|
||||
is_default = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name="Вариант по умолчанию"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Вариант комплекта"
|
||||
verbose_name_plural = "Варианты комплектов"
|
||||
unique_together = [['parent', 'kit']]
|
||||
indexes = [
|
||||
models.Index(fields=['parent']),
|
||||
models.Index(fields=['kit']),
|
||||
models.Index(fields=['parent', 'is_default']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.parent.name} → {self.kit.name}"
|
||||
|
||||
Reference in New Issue
Block a user