Добавлена модель атрибутов для вариативных товаров (ConfigurableKitProductAttribute)
- Создана модель ConfigurableKitProductAttribute с полями name, option, position, visible - Добавлены формы и formsets для управления атрибутами родительского товара - Обновлены CRUD представления для работы с атрибутами (создание/редактирование) - Добавлен блок атрибутов в шаблоны создания/редактирования - Обновлена страница детального просмотра с отображением атрибутов товара - Добавлен JavaScript для динамического добавления форм атрибутов - Реализована валидация дубликатов атрибутов в formset - Атрибуты сохраняются в transaction.atomic() вместе с вариантами Теперь можно определять схему атрибутов для экспорта на WooCommerce без использования JSON или ID, только name и option.
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
from django import forms
|
||||
from django.forms import inlineformset_factory
|
||||
from .models import Product, ProductKit, ProductCategory, ProductTag, ProductPhoto, KitItem, ProductKitPhoto, ProductCategoryPhoto, ProductVariantGroup, ProductVariantGroupItem
|
||||
from .models import (
|
||||
Product, ProductKit, ProductCategory, ProductTag, ProductPhoto, KitItem,
|
||||
ProductKitPhoto, ProductCategoryPhoto, ProductVariantGroup, ProductVariantGroupItem,
|
||||
ConfigurableKitProduct, ConfigurableKitOption, ConfigurableKitProductAttribute
|
||||
)
|
||||
|
||||
|
||||
class ProductForm(forms.ModelForm):
|
||||
@@ -577,3 +581,229 @@ class ProductTagForm(forms.ModelForm):
|
||||
if slug == '' or slug is None:
|
||||
return None
|
||||
return slug
|
||||
|
||||
|
||||
# ==================== CONFIGURABLE KIT FORMS ====================
|
||||
|
||||
class ConfigurableKitProductForm(forms.ModelForm):
|
||||
"""
|
||||
Форма для создания и редактирования вариативного товара.
|
||||
"""
|
||||
class Meta:
|
||||
model = ConfigurableKitProduct
|
||||
fields = ['name', 'sku', 'description', 'short_description', 'status']
|
||||
labels = {
|
||||
'name': 'Название',
|
||||
'sku': 'Артикул',
|
||||
'description': 'Полное описание',
|
||||
'short_description': 'Краткое описание',
|
||||
'status': 'Статус'
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['name'].widget.attrs.update({
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Введите название вариативного товара'
|
||||
})
|
||||
self.fields['sku'].widget.attrs.update({
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Артикул (необязательно)'
|
||||
})
|
||||
self.fields['description'].widget.attrs.update({
|
||||
'class': 'form-control',
|
||||
'rows': 5
|
||||
})
|
||||
self.fields['short_description'].widget.attrs.update({
|
||||
'class': 'form-control',
|
||||
'rows': 3,
|
||||
'placeholder': 'Краткое описание для экспорта'
|
||||
})
|
||||
self.fields['status'].widget.attrs.update({'class': 'form-select'})
|
||||
|
||||
|
||||
class ConfigurableKitOptionForm(forms.ModelForm):
|
||||
"""
|
||||
Форма для добавления варианта (комплекта) к вариативному товару.
|
||||
"""
|
||||
kit = forms.ModelChoiceField(
|
||||
queryset=ProductKit.objects.filter(status='active', is_temporary=False).order_by('name'),
|
||||
required=True,
|
||||
label="Комплект",
|
||||
widget=forms.Select(attrs={'class': 'form-select'})
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ConfigurableKitOption
|
||||
fields = ['kit', 'attributes', 'is_default']
|
||||
labels = {
|
||||
'kit': 'Комплект',
|
||||
'attributes': 'Атрибуты варианта',
|
||||
'is_default': 'По умолчанию'
|
||||
}
|
||||
widgets = {
|
||||
'attributes': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Например: Количество:15;Длина:60см'
|
||||
}),
|
||||
'is_default': forms.CheckboxInput(attrs={
|
||||
'class': 'form-check-input is-default-switch',
|
||||
'role': 'switch'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
class BaseConfigurableKitOptionFormSet(forms.BaseInlineFormSet):
|
||||
def clean(self):
|
||||
"""Проверка на дубликаты комплектов в вариативном товаре"""
|
||||
if any(self.errors):
|
||||
return
|
||||
|
||||
kits = []
|
||||
default_count = 0
|
||||
|
||||
for form in self.forms:
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
continue
|
||||
|
||||
kit = form.cleaned_data.get('kit')
|
||||
is_default = form.cleaned_data.get('is_default')
|
||||
|
||||
# Проверка дубликатов комплектов
|
||||
if kit:
|
||||
if kit in kits:
|
||||
raise forms.ValidationError(
|
||||
f'Комплект "{kit.name}" добавлен более одного раза. '
|
||||
f'Каждый комплект может быть добавлен только один раз.'
|
||||
)
|
||||
kits.append(kit)
|
||||
|
||||
# Считаем количество "is_default"
|
||||
if is_default:
|
||||
default_count += 1
|
||||
|
||||
# Проверяем, что не более одного "is_default"
|
||||
if default_count > 1:
|
||||
raise forms.ValidationError(
|
||||
'Можно установить только один вариант как "по умолчанию".'
|
||||
)
|
||||
|
||||
|
||||
# Формсет для создания вариативного товара
|
||||
ConfigurableKitOptionFormSetCreate = inlineformset_factory(
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitOption,
|
||||
form=ConfigurableKitOptionForm,
|
||||
formset=BaseConfigurableKitOptionFormSet,
|
||||
fields=['kit', 'attributes', 'is_default'],
|
||||
extra=1, # Показать 1 пустую форму
|
||||
can_delete=True,
|
||||
min_num=0,
|
||||
validate_min=False,
|
||||
can_delete_extra=True,
|
||||
)
|
||||
|
||||
# Формсет для редактирования вариативного товара
|
||||
ConfigurableKitOptionFormSetUpdate = inlineformset_factory(
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitOption,
|
||||
form=ConfigurableKitOptionForm,
|
||||
formset=BaseConfigurableKitOptionFormSet,
|
||||
fields=['kit', 'attributes', 'is_default'],
|
||||
extra=0, # НЕ показывать пустые формы
|
||||
can_delete=True,
|
||||
min_num=0,
|
||||
validate_min=False,
|
||||
can_delete_extra=True,
|
||||
)
|
||||
|
||||
|
||||
# === Формы для атрибутов родительского вариативного товара ===
|
||||
|
||||
class ConfigurableKitProductAttributeForm(forms.ModelForm):
|
||||
"""
|
||||
Форма для добавления атрибута родительского товара.
|
||||
Пример: name="Цвет", option="Красный"
|
||||
"""
|
||||
class Meta:
|
||||
model = ConfigurableKitProductAttribute
|
||||
fields = ['name', 'option', 'position', 'visible']
|
||||
labels = {
|
||||
'name': 'Название атрибута',
|
||||
'option': 'Значение опции',
|
||||
'position': 'Порядок',
|
||||
'visible': 'Видимый'
|
||||
}
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Например: Цвет, Размер, Длина'
|
||||
}),
|
||||
'option': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Например: Красный, M, 60см'
|
||||
}),
|
||||
'position': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'min': '0',
|
||||
'value': '0'
|
||||
}),
|
||||
'visible': forms.CheckboxInput(attrs={
|
||||
'class': 'form-check-input'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
class BaseConfigurableKitProductAttributeFormSet(forms.BaseInlineFormSet):
|
||||
def clean(self):
|
||||
"""Проверка на дубликаты атрибутов"""
|
||||
if any(self.errors):
|
||||
return
|
||||
|
||||
attributes = []
|
||||
|
||||
for form in self.forms:
|
||||
if self.can_delete and self._should_delete_form(form):
|
||||
continue
|
||||
|
||||
name = form.cleaned_data.get('name')
|
||||
option = form.cleaned_data.get('option')
|
||||
|
||||
# Проверка дубликатов
|
||||
if name and option:
|
||||
attr_tuple = (name.strip(), option.strip())
|
||||
if attr_tuple in attributes:
|
||||
raise forms.ValidationError(
|
||||
f'Атрибут "{name}: {option}" добавлен более одного раза. '
|
||||
f'Каждая комбинация атрибут-значение должна быть уникальной.'
|
||||
)
|
||||
attributes.append(attr_tuple)
|
||||
|
||||
|
||||
# Формсет для создания атрибутов родительского товара
|
||||
ConfigurableKitProductAttributeFormSetCreate = inlineformset_factory(
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitProductAttribute,
|
||||
form=ConfigurableKitProductAttributeForm,
|
||||
formset=BaseConfigurableKitProductAttributeFormSet,
|
||||
fields=['name', 'option', 'position', 'visible'],
|
||||
extra=1,
|
||||
can_delete=True,
|
||||
min_num=0,
|
||||
validate_min=False,
|
||||
can_delete_extra=True,
|
||||
)
|
||||
|
||||
# Формсет для редактирования атрибутов родительского товара
|
||||
ConfigurableKitProductAttributeFormSetUpdate = inlineformset_factory(
|
||||
ConfigurableKitProduct,
|
||||
ConfigurableKitProductAttribute,
|
||||
form=ConfigurableKitProductAttributeForm,
|
||||
formset=BaseConfigurableKitProductAttributeFormSet,
|
||||
fields=['name', 'option', 'position', 'visible'],
|
||||
extra=0,
|
||||
can_delete=True,
|
||||
min_num=0,
|
||||
validate_min=False,
|
||||
can_delete_extra=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user