Добавлена автогенерация и валидация уникальности артикулов для всех типов товаров
- Добавлен миксин 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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user