Добавлена автогенерация и валидация уникальности артикулов для всех типов товаров

- Добавлен миксин SKUUniqueMixin для единообразной валидации артикулов
- Валидация проверяет уникальность SKU среди Product, ProductKit, ProductCategory, ConfigurableProduct
- Реализована автогенерация артикулов для ConfigurableProduct (формат VAR-XXXXXX)
- Добавлен новый тип счетчика 'configurable' в SKUCounter
- Обновлены формы Product, ProductKit, ProductCategory, ConfigurableProduct
- Рефакторинг методов clean() в формах: валидация имени вынесена в clean_name()
- Добавлена функция generate_configurable_sku() в sku_generator.py
- Обновлена функция ensure_sku_unique() для проверки ConfigurableProduct
- Добавлен метод save() в модель ConfigurableProduct для автогенерации SKU
- Обновлен шаблон configurableproduct_form.html с отображением help_text для SKU

Код стал чистым, без дублирования логики валидации.
This commit is contained in:
2025-12-30 10:47:03 +03:00
parent a95bd56b2b
commit 577401447b
6 changed files with 135 additions and 30 deletions

View File

@@ -8,7 +8,51 @@ from .models import (
)
class ProductForm(forms.ModelForm):
class SKUUniqueMixin:
"""
Миксин для валидации уникальности артикула среди всех моделей.
Проверяет уникальность SKU среди: Product, ProductKit, ProductCategory, ConfigurableProduct.
"""
def clean_sku(self):
"""
Проверяет уникальность артикула среди всех моделей с артикулами.
Если SKU пустой - пропускаем (будет сгенерирован автоматически).
"""
sku = self.cleaned_data.get('sku')
# Пустое значение - ок, будет автогенерация
if not sku or sku.strip() == '':
return None
sku = sku.strip()
# Проверяем во всех моделях
models_to_check = [
(Product, 'товар'),
(ProductKit, 'комплект'),
(ProductCategory, 'категория'),
(ConfigurableProduct, 'вариативный товар'),
]
for model, model_name in models_to_check:
queryset = model.objects.filter(sku=sku)
# Исключаем текущий объект при редактировании
if self.instance.pk and isinstance(self.instance, model):
queryset = queryset.exclude(pk=self.instance.pk)
if queryset.exists():
existing = queryset.first()
raise forms.ValidationError(
f'Артикул "{sku}" уже используется в {model_name} "{existing.name}". '
f'Пожалуйста, выберите другой артикул.'
)
return sku
class ProductForm(SKUUniqueMixin, forms.ModelForm):
"""
Форма для создания и редактирования товара.
Поле photos НЕ включено в форму - файлы обрабатываются отдельно в view.
@@ -71,10 +115,9 @@ class ProductForm(forms.ModelForm):
self.fields['unit'].widget.attrs.update({'class': 'form-control'})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
def clean(self):
def clean_name(self):
"""Валидация уникальности имени для активных товаров"""
cleaned_data = super().clean()
name = cleaned_data.get('name')
name = self.cleaned_data.get('name')
if name:
# Проверяем уникальность имени среди активных товаров
@@ -88,15 +131,15 @@ class ProductForm(forms.ModelForm):
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
raise forms.ValidationError(
f'Товар с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
return cleaned_data
return name
class ProductKitForm(forms.ModelForm):
class ProductKitForm(SKUUniqueMixin, forms.ModelForm):
"""
Форма для создания и редактирования комплекта.
Цена комплекта вычисляется автоматически из цен компонентов.
@@ -163,17 +206,10 @@ class ProductKitForm(forms.ModelForm):
})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
def clean(self):
"""
Валидация формы комплекта.
Проверяет:
1. Уникальность имени для активных комплектов
2. Что если выбран тип корректировки, указано значение
"""
cleaned_data = super().clean()
def clean_name(self):
"""Валидация уникальности имени для активных комплектов"""
name = self.cleaned_data.get('name')
# Проверяем уникальность имени среди активных комплектов
name = cleaned_data.get('name')
if name:
existing = ProductKit.objects.filter(
name=name,
@@ -185,11 +221,17 @@ class ProductKitForm(forms.ModelForm):
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
raise forms.ValidationError(
f'Комплект с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
return name
def clean(self):
"""Дополнительная валидация: если выбран тип корректировки, указано значение"""
cleaned_data = super().clean()
adjustment_type = cleaned_data.get('price_adjustment_type')
adjustment_value = cleaned_data.get('price_adjustment_value')
@@ -323,7 +365,7 @@ KitItemFormSetUpdate = inlineformset_factory(
KitItemFormSet = KitItemFormSetCreate
class ProductCategoryForm(forms.ModelForm):
class ProductCategoryForm(SKUUniqueMixin, forms.ModelForm):
"""
Форма для создания и редактирования категории товаров.
Поле photos НЕ включено в форму - файлы обрабатываются отдельно в view.
@@ -380,10 +422,9 @@ class ProductCategoryForm(forms.ModelForm):
is_active=True
).exclude(pk__in=exclude_ids)
def clean(self):
def clean_name(self):
"""Валидация уникальности имени для активных категорий"""
cleaned_data = super().clean()
name = cleaned_data.get('name')
name = self.cleaned_data.get('name')
if name:
# Проверяем уникальность имени среди активных категорий
@@ -396,12 +437,12 @@ class ProductCategoryForm(forms.ModelForm):
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
raise forms.ValidationError(
f'Категория с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
return cleaned_data
return name
def clean_slug(self):
"""Преобразуем пустую строку в None для автогенерации slug"""
@@ -584,7 +625,7 @@ class ProductTagForm(forms.ModelForm):
# ==================== CONFIGURABLE KIT FORMS ====================
class ConfigurableProductForm(forms.ModelForm):
class ConfigurableProductForm(SKUUniqueMixin, forms.ModelForm):
"""
Форма для создания и редактирования вариативного товара.
"""
@@ -598,6 +639,9 @@ class ConfigurableProductForm(forms.ModelForm):
'short_description': 'Краткое описание',
'status': 'Статус'
}
help_texts = {
'sku': 'Оставьте пустым для автогенерации в формате VAR-XXXXXX',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -607,8 +651,9 @@ class ConfigurableProductForm(forms.ModelForm):
})
self.fields['sku'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Артикул (необязательно)'
'placeholder': 'VAR-XXXXXX (автогенерация)'
})
self.fields['sku'].required = False
self.fields['description'].widget.attrs.update({
'class': 'form-control',
'rows': 5