Files
octopus/myproject/products/forms.py
Andrey Smakotin a12f8f990d Fix: Remove readonly attribute from parameter name input field
The parameter name field had readonly='readonly' which prevented users from entering values.

This fix allows users to:
- Enter parameter name directly in the form field
- Modify parameter names during editing
- Type any parameter name they need

The readonly attribute was from a mistaken assumption that values would be pre-filled by JavaScript.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 20:57:13 +03:00

876 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django import forms
from django.forms import inlineformset_factory
from .models import (
Product, ProductKit, ProductCategory, ProductTag, ProductPhoto, KitItem,
ProductKitPhoto, ProductCategoryPhoto, ProductVariantGroup, ProductVariantGroupItem,
ConfigurableKitProduct, ConfigurableKitOption, ConfigurableKitProductAttribute
)
class ProductForm(forms.ModelForm):
"""
Форма для создания и редактирования товара.
Поле photos НЕ включено в форму - файлы обрабатываются отдельно в view.
"""
categories = forms.ModelMultipleChoiceField(
queryset=ProductCategory.objects.filter(is_active=True),
widget=forms.CheckboxSelectMultiple,
required=False,
label="Категории"
)
tags = forms.ModelMultipleChoiceField(
queryset=ProductTag.objects.filter(is_active=True),
widget=forms.CheckboxSelectMultiple,
required=False,
label="Теги"
)
class Meta:
model = Product
fields = [
'name', 'sku', 'description', 'short_description', 'categories',
'tags', 'unit', 'cost_price', 'price', 'sale_price', 'status'
]
labels = {
'name': 'Название',
'sku': 'Артикул',
'description': 'Описание',
'short_description': 'Краткое описание',
'categories': 'Категории',
'tags': 'Теги',
'unit': 'Единица измерения',
'cost_price': 'Себестоимость',
'price': 'Основная цена',
'sale_price': 'Цена со скидкой',
'status': 'Статус'
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make fields more user-friendly
self.fields['name'].widget.attrs.update({
'class': 'form-control form-control-lg fw-semibold',
'placeholder': 'Введите название товара'
})
self.fields['sku'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'Артикул (необязательно, будет сгенерирован автоматически)'
})
self.fields['description'].widget.attrs.update({
'class': 'form-control',
'rows': 3
})
self.fields['short_description'].widget.attrs.update({
'class': 'form-control',
'rows': 2,
'placeholder': 'Краткое описание для превью и площадок'
})
self.fields['cost_price'].widget.attrs.update({'class': 'form-control'})
self.fields['price'].widget.attrs.update({'class': 'form-control'})
self.fields['sale_price'].widget.attrs.update({'class': 'form-control'})
self.fields['unit'].widget.attrs.update({'class': 'form-control'})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
def clean(self):
"""Валидация уникальности имени для активных товаров"""
cleaned_data = super().clean()
name = cleaned_data.get('name')
if name:
# Проверяем уникальность имени среди активных товаров
# Исключаем текущий товар при редактировании (self.instance.pk)
existing = Product.objects.filter(
name=name,
status='active'
)
if self.instance.pk:
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
f'Товар с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
return cleaned_data
class ProductKitForm(forms.ModelForm):
"""
Форма для создания и редактирования комплекта.
Цена комплекта вычисляется автоматически из цен компонентов.
"""
categories = forms.ModelMultipleChoiceField(
queryset=ProductCategory.objects.filter(is_active=True),
widget=forms.CheckboxSelectMultiple,
required=False,
label="Категории"
)
tags = forms.ModelMultipleChoiceField(
queryset=ProductTag.objects.filter(is_active=True),
widget=forms.CheckboxSelectMultiple,
required=False,
label="Теги"
)
class Meta:
model = ProductKit
fields = [
'name', 'sku', 'description', 'short_description', 'categories',
'tags', 'sale_price', 'price_adjustment_type', 'price_adjustment_value', 'status'
]
labels = {
'name': 'Название',
'sku': 'Артикул',
'description': 'Описание',
'short_description': 'Краткое описание',
'categories': 'Категории',
'tags': 'Теги',
'sale_price': 'Цена со скидкой',
'price_adjustment_type': 'Как изменить итоговую цену',
'price_adjustment_value': 'Значение корректировки',
'status': 'Статус'
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make fields more user-friendly
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': 3
})
self.fields['short_description'].widget.attrs.update({
'class': 'form-control',
'rows': 2,
'placeholder': 'Краткое описание для превью и площадок'
})
self.fields['sale_price'].widget.attrs.update({'class': 'form-control'})
self.fields['price_adjustment_type'].widget.attrs.update({'class': 'form-control'})
self.fields['price_adjustment_value'].widget.attrs.update({
'class': 'form-control',
'step': '0.01',
'placeholder': '0'
})
self.fields['status'].widget.attrs.update({'class': 'form-control'})
def clean(self):
"""
Валидация формы комплекта.
Проверяет:
1. Уникальность имени для активных комплектов
2. Что если выбран тип корректировки, указано значение
"""
cleaned_data = super().clean()
# Проверяем уникальность имени среди активных комплектов
name = cleaned_data.get('name')
if name:
existing = ProductKit.objects.filter(
name=name,
status='active',
is_temporary=False
)
if self.instance.pk:
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
f'Комплект с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
adjustment_type = cleaned_data.get('price_adjustment_type')
adjustment_value = cleaned_data.get('price_adjustment_value')
# Если выбран тип корректировки (не 'none'), значение обязательно
if adjustment_type and adjustment_type != 'none':
if not adjustment_value or adjustment_value == 0:
raise forms.ValidationError(
'Укажите значение корректировки цены (> 0)'
)
return cleaned_data
class KitItemForm(forms.ModelForm):
"""
Форма для одного компонента комплекта.
Валидирует, что указан либо product, либо variant_group (но не оба).
Если обе поля пусты - это пустая форма, которая будет удалена.
"""
class Meta:
model = KitItem
fields = ['product', 'variant_group', 'quantity']
labels = {
'product': 'Конкретный товар',
'variant_group': 'Группа вариантов',
'quantity': 'Количество'
}
widgets = {
'product': forms.Select(attrs={'class': 'form-control'}),
'variant_group': forms.Select(attrs={'class': 'form-control'}),
'quantity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001', 'min': '0'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Устанавливаем значение по умолчанию для quantity = 1
if not self.instance.pk: # Только для новых форм (создание, не редактирование)
self.fields['quantity'].initial = 1
def clean(self):
cleaned_data = super().clean()
product = cleaned_data.get('product')
variant_group = cleaned_data.get('variant_group')
quantity = cleaned_data.get('quantity')
# Если оба поля пусты - это пустая форма (не валидируем, она будет удалена)
if not product and not variant_group:
# Для пустых форм обнуляем количество
cleaned_data['quantity'] = None
return cleaned_data
# Валидация: должен быть указан либо product, либо variant_group (но не оба)
if product and variant_group:
raise forms.ValidationError(
"Нельзя указывать одновременно товар и группу вариантов. Выберите что-то одно."
)
# Валидация: если выбран товар/группа, количество обязательно и должно быть > 0
if (product or variant_group):
if not quantity or quantity <= 0:
raise forms.ValidationError('Необходимо указать количество больше 0')
return cleaned_data
# Кастомный базовый формсет с валидацией на дубликаты
class BaseKitItemFormSet(forms.BaseInlineFormSet):
def clean(self):
"""Проверка на дубликаты товаров в комплекте"""
if any(self.errors):
# Не проверяем дубликаты если есть другие ошибки
return
products = []
variant_groups = []
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
continue
product = form.cleaned_data.get('product')
variant_group = form.cleaned_data.get('variant_group')
# Проверка дубликатов товаров
if product:
if product in products:
raise forms.ValidationError(
f'Товар "{product.name}" добавлен в комплект более одного раза. '
f'Каждый товар может быть добавлен только один раз.'
)
products.append(product)
# Проверка дубликатов групп вариантов
if variant_group:
if variant_group in variant_groups:
raise forms.ValidationError(
f'Группа вариантов "{variant_group.name}" добавлена более одного раза. '
f'Каждая группа может быть добавлена только один раз.'
)
variant_groups.append(variant_group)
# Формсет для создания комплектов (с пустой формой для удобства)
KitItemFormSetCreate = inlineformset_factory(
ProductKit,
KitItem,
form=KitItemForm,
formset=BaseKitItemFormSet,
fields=['product', 'variant_group', 'quantity'],
extra=1, # Показать 1 пустую форму для первого компонента
can_delete=True, # Разрешить удаление компонентов
min_num=0, # Минимум 0 компонентов (можно создать пустой комплект)
validate_min=False, # Не требовать минимум компонентов
can_delete_extra=True, # Разрешить удалять дополнительные формы
)
# Формсет для редактирования комплектов (без пустых форм, только существующие компоненты)
KitItemFormSetUpdate = inlineformset_factory(
ProductKit,
KitItem,
form=KitItemForm,
formset=BaseKitItemFormSet,
fields=['product', 'variant_group', 'quantity'],
extra=0, # НЕ показывать пустые формы при редактировании
can_delete=True, # Разрешить удаление компонентов
min_num=0, # Минимум 0 компонентов
validate_min=False, # Не требовать минимум компонентов
can_delete_extra=True, # Разрешить удалять дополнительные формы
)
# Для обратной совместимости (если где-то еще используется KitItemFormSet)
KitItemFormSet = KitItemFormSetCreate
class ProductCategoryForm(forms.ModelForm):
"""
Форма для создания и редактирования категории товаров.
Поле photos НЕ включено в форму - файлы обрабатываются отдельно в view.
"""
parent = forms.ModelChoiceField(
queryset=ProductCategory.objects.filter(is_active=True),
required=False,
empty_label="Нет (корневая категория)",
label="Родительская категория",
widget=forms.Select(attrs={'class': 'form-control'})
)
class Meta:
model = ProductCategory
fields = ['name', 'sku', 'slug', 'parent', 'is_active']
labels = {
'name': 'Название',
'sku': 'Артикул',
'slug': 'URL-идентификатор',
'parent': 'Родительская категория',
'is_active': 'Активна'
}
help_texts = {
'sku': 'Оставьте пустым для автоматической генерации (CAT-XXXX)',
'slug': 'Оставьте пустым для автоматической генерации из названия',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make fields more user-friendly
self.fields['name'].widget.attrs.update({
'class': 'form-control form-control-lg fw-semibold',
'placeholder': 'Введите название категории'
})
self.fields['sku'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'CAT-XXXX (автоматически)'
})
self.fields['slug'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'url-identifier (автоматически)'
})
self.fields['slug'].required = False # Делаем поле необязательным
self.fields['is_active'].widget.attrs.update({'class': 'form-check-input'})
# Исключаем текущую категорию и её потомков из списка родительских
# (чтобы не создать циклическую зависимость)
if self.instance and self.instance.pk:
# Получаем все потомки текущей категории
descendants = self._get_descendants(self.instance)
# Исключаем текущую категорию и все её потомки
exclude_ids = [self.instance.pk] + [cat.pk for cat in descendants]
self.fields['parent'].queryset = ProductCategory.objects.filter(
is_active=True
).exclude(pk__in=exclude_ids)
def clean(self):
"""Валидация уникальности имени для активных категорий"""
cleaned_data = super().clean()
name = cleaned_data.get('name')
if name:
# Проверяем уникальность имени среди активных категорий
existing = ProductCategory.objects.filter(
name=name,
is_deleted=False
)
if self.instance.pk:
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
f'Категория с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
return cleaned_data
def clean_slug(self):
"""Преобразуем пустую строку в None для автогенерации slug"""
slug = self.cleaned_data.get('slug')
if slug == '' or slug is None:
return None
return slug
def _get_descendants(self, category):
"""Рекурсивно получает всех потомков категории"""
descendants = []
children = category.children.all()
for child in children:
descendants.append(child)
descendants.extend(self._get_descendants(child))
return descendants
# ==================== VARIANT GROUP FORMS ====================
class ProductVariantGroupForm(forms.ModelForm):
"""
Форма для создания и редактирования группы вариантов товара.
"""
class Meta:
model = ProductVariantGroup
fields = ['name', 'description']
labels = {
'name': 'Название группы',
'description': 'Описание (для какого букета или ситуации создана)'
}
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control form-control-lg fw-semibold',
'placeholder': 'Например: Роза красная Freedom'
}),
'description': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Описание группы и её использования'
}),
}
class ProductVariantGroupItemForm(forms.ModelForm):
"""
Форма для добавления товара в группу с приоритетом.
"""
class Meta:
model = ProductVariantGroupItem
fields = ['product', 'priority']
labels = {
'product': 'Товар',
'priority': 'Приоритет'
}
widgets = {
'product': forms.Select(attrs={'class': 'form-control form-control-sm'}),
'priority': forms.NumberInput(attrs={
'class': 'form-control form-control-sm',
'min': '1',
'placeholder': '1, 2, 3...'
}),
}
# Базовый формсет с валидацией на дубликаты
class BaseProductVariantGroupItemFormSet(forms.BaseInlineFormSet):
def clean(self):
"""Проверка на дубликаты товаров в группе"""
if any(self.errors):
return
products = []
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
continue
product = form.cleaned_data.get('product')
# Проверка дубликатов товаров
if product:
if product in products:
raise forms.ValidationError(
f'Товар "{product.name}" уже добавлен в группу. '
f'Каждый товар может быть добавлен только один раз.'
)
products.append(product)
# Формсет для создания групп (с пустой формой для добавления товара)
ProductVariantGroupItemFormSetCreate = inlineformset_factory(
ProductVariantGroup,
ProductVariantGroupItem,
form=ProductVariantGroupItemForm,
formset=BaseProductVariantGroupItemFormSet,
fields=['product', 'priority'],
extra=1, # Показать 1 пустую форму для первого товара
can_delete=True, # Разрешить удаление товаров
min_num=0, # Минимум 0 товаров (можно создать пустую группу)
validate_min=False, # Не требовать минимум товаров
can_delete_extra=True, # Разрешить удалять дополнительные формы
)
# Формсет для редактирования групп (без пустых форм, только существующие товары)
ProductVariantGroupItemFormSetUpdate = inlineformset_factory(
ProductVariantGroup,
ProductVariantGroupItem,
form=ProductVariantGroupItemForm,
formset=BaseProductVariantGroupItemFormSet,
fields=['product', 'priority'],
extra=0, # НЕ показывать пустые формы при редактировании
can_delete=True, # Разрешить удаление товаров
min_num=0, # Минимум 0 товаров
validate_min=False, # Не требовать минимум товаров
can_delete_extra=True, # Разрешить удалять дополнительные формы
)
class ProductTagForm(forms.ModelForm):
"""
Форма для создания и редактирования тегов товаров.
"""
class Meta:
model = ProductTag
fields = ['name', 'slug', 'is_active']
labels = {
'name': 'Название',
'slug': 'URL-идентификатор',
'is_active': 'Активен'
}
help_texts = {
'slug': 'Оставьте пустым для автоматической генерации из названия',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({
'class': 'form-control form-control-lg fw-semibold',
'placeholder': 'Введите название тега'
})
self.fields['slug'].widget.attrs.update({
'class': 'form-control',
'placeholder': 'url-identifier (автоматически)'
})
self.fields['slug'].required = False
self.fields['is_active'].widget.attrs.update({'class': 'form-check-input'})
def clean(self):
"""Валидация уникальности имени для активных тегов"""
cleaned_data = super().clean()
name = cleaned_data.get('name')
is_active = cleaned_data.get('is_active', True)
if name and is_active:
# Проверяем уникальность имени среди активных тегов
existing = ProductTag.objects.filter(
name=name,
is_active=True
)
if self.instance.pk:
existing = existing.exclude(pk=self.instance.pk)
if existing.exists():
self.add_error('name',
f'Тег с названием "{name}" уже существует. '
f'Пожалуйста, используйте другое название.'
)
return cleaned_data
def clean_slug(self):
"""Разрешаем пустой slug - он сгенерируется в модели"""
slug = self.cleaned_data.get('slug')
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):
"""
Форма для добавления варианта (комплекта) к вариативному товару.
Атрибуты варианта выбираются динамически на основе parent_attributes.
"""
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
# Убрали 'attributes' - он будет заполняться через ConfigurableKitOptionAttribute
fields = ['kit', 'is_default']
labels = {
'kit': 'Комплект',
'is_default': 'По умолчанию'
}
widgets = {
'is_default': forms.CheckboxInput(attrs={
'class': 'form-check-input is-default-switch',
'role': 'switch'
})
}
def __init__(self, *args, **kwargs):
"""
Динамически генерируем поля для выбора атрибутов на основе parent_attributes.
"""
super().__init__(*args, **kwargs)
# Получаем instance (ConfigurableKitOption)
if self.instance and self.instance.parent_id:
parent = self.instance.parent
# Получаем все уникальные названия атрибутов родителя
attributes = parent.parent_attributes.all().order_by('position', 'name').distinct('name')
# Создаем поле для каждого названия атрибута
for attr in attributes:
attr_name = attr.name
field_name = f'attribute_{attr_name}'
# Получаем все возможные значения для этого атрибута
options = parent.parent_attributes.filter(name=attr_name).values_list('id', 'option')
# Создаем ChoiceField для выбора значения
self.fields[field_name] = forms.ModelChoiceField(
queryset=parent.parent_attributes.filter(name=attr_name),
required=True,
label=attr_name,
widget=forms.Select(attrs={'class': 'form-select', 'data-attribute-name': attr_name}),
to_field_name='id'
)
# Если редактируем существующий вариант - устанавливаем текущее значение
if self.instance.pk:
current_attr = self.instance.attributes_set.filter(attribute__name=attr_name).first()
if current_attr:
self.fields[field_name].initial = current_attr.attribute
class BaseConfigurableKitOptionFormSet(forms.BaseInlineFormSet):
def clean(self):
"""Проверка на дубликаты комплектов и что все атрибуты заполнены"""
if any(self.errors):
return
kits = []
default_count = 0
for idx, form in enumerate(self.forms):
if self.can_delete and self._should_delete_form(form):
continue
# Пропускаем пустые формы (extra формы при создании)
if not form.cleaned_data or not form.cleaned_data.get('kit'):
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
# Проверяем что все атрибуты заполнены для варианта с kit
parent = self.instance
if parent and parent.pk:
# Получаем все уникальные названия атрибутов родителя
attribute_names = (
parent.parent_attributes
.all()
.order_by('position', 'name')
.distinct('name')
.values_list('name', flat=True)
)
# Проверяем что каждый атрибут выбран
missing_attributes = []
for attr_name in attribute_names:
field_name = f'attribute_{attr_name}'
if field_name not in form.cleaned_data or not form.cleaned_data[field_name]:
missing_attributes.append(attr_name)
if missing_attributes:
attrs_str = ', '.join(f'"{attr}"' for attr in missing_attributes)
raise forms.ValidationError(
f'Вариант {idx + 1}: необходимо заполнить атрибут(ы) {attrs_str}.'
)
# Проверяем, что не более одного "is_default"
if default_count > 1:
raise forms.ValidationError(
'Можно установить только один вариант как "по умолчанию".'
)
# Формсет для создания вариативного товара
ConfigurableKitOptionFormSetCreate = inlineformset_factory(
ConfigurableKitProduct,
ConfigurableKitOption,
form=ConfigurableKitOptionForm,
formset=BaseConfigurableKitOptionFormSet,
fields=['kit', 'is_default'], # Убрали 'attributes' - заполняется через ConfigurableKitOptionAttribute
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', 'is_default'], # Убрали 'attributes' - заполняется через ConfigurableKitOptionAttribute
extra=0, # НЕ показывать пустые формы
can_delete=True,
min_num=0,
validate_min=False,
can_delete_extra=True,
)
# === Формы для атрибутов родительского вариативного товара ===
class ConfigurableKitProductAttributeForm(forms.ModelForm):
"""
Форма для добавления атрибута родительского товара в карточном интерфейсе.
На фронтенде: одна карточка параметра (имя + позиция + видимость)
+ множество инлайн значений через JavaScript
Пример структуры:
- name: "Длина"
- position: 0
- visible: True
- values: [50, 60, 70] (будут созданы как отдельные ConfigurableKitProductAttribute)
"""
class Meta:
model = ConfigurableKitProductAttribute
fields = ['name', 'position', 'visible']
labels = {
'name': 'Название параметра',
'position': 'Порядок',
'visible': 'Видимый на витрине'
}
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control param-name-input',
'placeholder': 'Например: Длина, Цвет, Размер'
}),
'position': forms.NumberInput(attrs={
'class': 'form-control param-position-input',
'min': '0',
'value': '0'
}),
'visible': forms.CheckboxInput(attrs={
'class': 'form-check-input param-visible-input'
})
}
class BaseConfigurableKitProductAttributeFormSet(forms.BaseInlineFormSet):
def clean(self):
"""Проверка на дубликаты параметров и что у каждого параметра есть значения"""
if any(self.errors):
return
parameter_names = []
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
continue
# Пропускаем пустые формы
if not form.cleaned_data.get('name'):
continue
name = form.cleaned_data.get('name').strip()
# Проверка дубликатов параметров (в карточном интерфейсе каждый параметр должен быть один раз)
if name in parameter_names:
raise forms.ValidationError(
f'Параметр "{name}" добавлен более одного раза. '
f'Каждый параметр должен быть добавлен только один раз.'
)
parameter_names.append(name)
# Формсет для создания атрибутов родительского товара (карточный интерфейс)
ConfigurableKitProductAttributeFormSetCreate = inlineformset_factory(
ConfigurableKitProduct,
ConfigurableKitProductAttribute,
form=ConfigurableKitProductAttributeForm,
formset=BaseConfigurableKitProductAttributeFormSet,
# Убрали 'option' - значения будут добавляться через JavaScript в карточку
fields=['name', '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,
# Убрали 'option' - значения будут добавляться через JavaScript в карточку
fields=['name', 'position', 'visible'],
extra=0,
can_delete=True,
min_num=0,
validate_min=False,
can_delete_extra=True,
)