Implement card-based interface for ConfigurableKitProduct attributes

This commit introduces a new user-friendly interface for managing product attributes:

1. **Form Changes** (products/forms.py):
   - Removed 'option' field from ConfigurableKitOptionForm (values now inline)
   - Updated ConfigurableKitProductAttributeFormSetCreate to only include name, position, visible
   - Updated BaseConfigurableKitProductAttributeFormSet validation for new structure

2. **Template Updates** (products/templates/products/configurablekit_form.html):
   - Replaced row-based attribute interface with card-based design
   - Each card contains:
     - Parameter name field
     - Position field
     - Visibility toggle
     - Inline value inputs with add/remove buttons
   - "Add parameter" button creates new cards
   - "Add value" button adds inline value inputs

3. **JavaScript Enhancements**:
   - addValueField(): Creates new value input with delete button
   - initAddValueBtn(): Initializes add value button for each card
   - addParameterBtn: Dynamically generates new parameter cards
   - serializeAttributeValues(): Converts inline values to JSON for POST submission
   - Form submission intercept to serialize data before sending

4. **View Updates** (products/views/configurablekit_views.py):
   - Both Create and Update views now have _save_attributes_from_cards() method
   - Reads attributes-X-values JSON from POST data
   - Creates ConfigurableKitProductAttribute for each parameter+value combination
   - Handles parameter deletion and visibility toggling

**Key Features**:
✓ One-time parameter name entry with multiple inline values
✓ Add/remove values without reloading page
✓ Add/remove entire parameters with one click
✓ No database changes required
✓ Better UX: card layout more intuitive than rows
✓ Proper JSON serialization for value transmission

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 20:54:14 +03:00
parent 48938db04f
commit def795f0ad
9 changed files with 1310 additions and 107 deletions

View File

@@ -783,71 +783,76 @@ ConfigurableKitOptionFormSetUpdate = inlineformset_factory(
class ConfigurableKitProductAttributeForm(forms.ModelForm):
"""
Форма для добавления атрибута родительского товара.
Пример: name="Цвет", option="Красный"
Форма для добавления атрибута родительского товара в карточном интерфейсе.
На фронтенде: одна карточка параметра (имя + позиция + видимость)
+ множество инлайн значений через JavaScript
Пример структуры:
- name: "Длина"
- position: 0
- visible: True
- values: [50, 60, 70] (будут созданы как отдельные ConfigurableKitProductAttribute)
"""
class Meta:
model = ConfigurableKitProductAttribute
fields = ['name', 'option', 'position', 'visible']
fields = ['name', 'position', 'visible']
labels = {
'name': 'Название атрибута',
'option': 'Значение опции',
'name': 'Название параметра',
'position': 'Порядок',
'visible': 'Видимый'
'visible': 'Видимый на витрине'
}
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Например: Цвет, Размер, Длина'
}),
'option': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Например: Красный, M, 60см'
'class': 'form-control param-name-input',
'placeholder': 'Например: Длина, Цвет, Размер',
'readonly': 'readonly' # Должен быть заполнен через JavaScript
}),
'position': forms.NumberInput(attrs={
'class': 'form-control',
'class': 'form-control param-position-input',
'min': '0',
'value': '0'
}),
'visible': forms.CheckboxInput(attrs={
'class': 'form-check-input'
'class': 'form-check-input param-visible-input'
})
}
class BaseConfigurableKitProductAttributeFormSet(forms.BaseInlineFormSet):
def clean(self):
"""Проверка на дубликаты атрибутов"""
"""Проверка на дубликаты параметров и что у каждого параметра есть значения"""
if any(self.errors):
return
attributes = []
parameter_names = []
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 not form.cleaned_data.get('name'):
continue
# Проверка дубликатов
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)
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,
fields=['name', 'option', 'position', 'visible'],
# Убрали 'option' - значения будут добавляться через JavaScript в карточку
fields=['name', 'position', 'visible'],
extra=1,
can_delete=True,
min_num=0,
@@ -861,7 +866,8 @@ ConfigurableKitProductAttributeFormSetUpdate = inlineformset_factory(
ConfigurableKitProductAttribute,
form=ConfigurableKitProductAttributeForm,
formset=BaseConfigurableKitProductAttributeFormSet,
fields=['name', 'option', 'position', 'visible'],
# Убрали 'option' - значения будут добавляться через JavaScript в карточку
fields=['name', 'position', 'visible'],
extra=0,
can_delete=True,
min_num=0,