Рефакторинг системы вариативных товаров и справочник атрибутов

Основные изменения:
- Переименование ConfigurableKitProduct → ConfigurableProduct
- Добавлена поддержка Product как варианта (не только ProductKit)
- Создан справочник атрибутов (ProductAttribute, ProductAttributeValue)
- CRUD для управления атрибутами с inline редактированием значений
- Пересозданы миграции с нуля для всех приложений
- Добавлена ссылка на атрибуты в навигацию

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 01:44:34 +03:00
parent 277a514a82
commit 79ff523adb
36 changed files with 1597 additions and 951 deletions

View File

@@ -0,0 +1,116 @@
"""
Модели для справочника атрибутов товаров.
Используется для создания переиспользуемых атрибутов (Длина стебля, Цвет, Размер и т.д.)
"""
from django.db import models
from django.utils.text import slugify
from unidecode import unidecode
class ProductAttribute(models.Model):
"""
Справочник атрибутов для вариативных товаров.
Примеры: Длина стебля, Цвет, Размер, Упаковка.
"""
name = models.CharField(
max_length=100,
unique=True,
verbose_name="Название",
help_text="Например: Длина стебля, Цвет, Размер"
)
slug = models.SlugField(
max_length=100,
unique=True,
blank=True,
verbose_name="Slug",
help_text="Автоматически генерируется из названия"
)
description = models.TextField(
blank=True,
verbose_name="Описание",
help_text="Опциональное описание атрибута"
)
position = models.PositiveIntegerField(
default=0,
verbose_name="Позиция",
help_text="Порядок отображения в списке"
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания"
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name="Дата обновления"
)
class Meta:
verbose_name = "Атрибут товара"
verbose_name_plural = "Атрибуты товаров"
ordering = ['position', 'name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(unidecode(self.name))
super().save(*args, **kwargs)
@property
def values_count(self):
"""Количество значений у атрибута"""
return self.values.count()
class ProductAttributeValue(models.Model):
"""
Значения атрибутов.
Примеры для атрибута "Длина стебля": 50, 60, 70, 80.
"""
attribute = models.ForeignKey(
ProductAttribute,
on_delete=models.CASCADE,
related_name='values',
verbose_name="Атрибут"
)
value = models.CharField(
max_length=100,
verbose_name="Значение",
help_text="Например: 50, 60, 70 (для длины) или Красный, Белый (для цвета)"
)
slug = models.SlugField(
max_length=100,
blank=True,
verbose_name="Slug"
)
position = models.PositiveIntegerField(
default=0,
verbose_name="Позиция",
help_text="Порядок отображения в списке значений"
)
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name="Дата создания"
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name="Дата обновления"
)
class Meta:
verbose_name = "Значение атрибута"
verbose_name_plural = "Значения атрибутов"
ordering = ['position', 'value']
unique_together = ['attribute', 'value']
indexes = [
models.Index(fields=['attribute', 'position']),
]
def __str__(self):
return f"{self.attribute.name}: {self.value}"
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(unidecode(self.value))
super().save(*args, **kwargs)