Добавлена модель атрибутов для вариативных товаров (ConfigurableKitProductAttribute)

- Создана модель ConfigurableKitProductAttribute с полями name, option, position, visible
- Добавлены формы и formsets для управления атрибутами родительского товара
- Обновлены CRUD представления для работы с атрибутами (создание/редактирование)
- Добавлен блок атрибутов в шаблоны создания/редактирования
- Обновлена страница детального просмотра с отображением атрибутов товара
- Добавлен JavaScript для динамического добавления форм атрибутов
- Реализована валидация дубликатов атрибутов в formset
- Атрибуты сохраняются в transaction.atomic() вместе с вариантами

Теперь можно определять схему атрибутов для экспорта на WooCommerce без использования JSON или ID, только name и option.
This commit is contained in:
2025-11-18 09:24:49 +03:00
parent bdea6b5398
commit c4260f6b1c
15 changed files with 2017 additions and 2 deletions

View File

@@ -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}"