from django import forms from django.forms import inlineformset_factory from .models import Product, ProductKit, ProductCategory, ProductTag, ProductPhoto, KitItem, ProductKitPhoto, ProductCategoryPhoto 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.all(), widget=forms.CheckboxSelectMultiple, required=False, label="Теги" ) class Meta: model = Product fields = [ 'name', 'sku', 'description', 'categories', 'tags', 'unit', 'cost_price', 'sale_price', 'is_active' ] labels = { 'name': 'Название', 'sku': 'Артикул', 'description': 'Описание', 'categories': 'Категории', 'tags': 'Теги', 'unit': 'Единица измерения', 'cost_price': 'Себестоимость', 'sale_price': 'Цена продажи', 'is_active': 'Активен' } 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['cost_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['is_active'].widget.attrs.update({'class': 'form-check-input'}) 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.all(), widget=forms.CheckboxSelectMultiple, required=False, label="Теги" ) class Meta: model = ProductKit fields = [ 'name', 'description', 'categories', 'tags', 'pricing_method', 'fixed_price', 'markup_percent', 'markup_amount', 'is_active' ] labels = { 'name': 'Название', 'description': 'Описание', 'categories': 'Категории', 'tags': 'Теги', 'pricing_method': 'Метод ценообразования', 'fixed_price': 'Фиксированная цена', 'markup_percent': 'Процент наценки', 'markup_amount': 'Фиксированная наценка', 'is_active': 'Активен' } 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['description'].widget.attrs.update({ 'class': 'form-control', 'rows': 3 }) self.fields['pricing_method'].widget.attrs.update({'class': 'form-control'}) self.fields['fixed_price'].widget.attrs.update({'class': 'form-control'}) self.fields['markup_percent'].widget.attrs.update({'class': 'form-control'}) self.fields['markup_amount'].widget.attrs.update({'class': 'form-control'}) self.fields['is_active'].widget.attrs.update({'class': 'form-check-input'}) class KitItemForm(forms.ModelForm): """ Форма для одного компонента комплекта. Валидирует, что указан либо product, либо variant_group (но не оба). Если обе поля пусты - это пустая форма, которая будет удалена. """ class Meta: model = KitItem fields = ['product', 'variant_group', 'quantity', 'notes'] labels = { 'product': 'Конкретный товар', 'variant_group': 'Группа вариантов', 'quantity': 'Количество', 'notes': 'Примечание' } 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'}), 'notes': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Опциональное примечание'}), } def clean(self): cleaned_data = super().clean() product = cleaned_data.get('product') variant_group = cleaned_data.get('variant_group') # Если оба поля пусты - это пустая форма (не валидируем, она будет удалена) if not product and not variant_group: return cleaned_data # Валидация: должен быть указан либо product, либо variant_group (но не оба) if product and variant_group: raise forms.ValidationError( "Нельзя указывать одновременно товар и группу вариантов. Выберите что-то одно." ) return cleaned_data # Формсет для создания комплектов (с пустой формой для удобства) KitItemFormSetCreate = inlineformset_factory( ProductKit, KitItem, form=KitItemForm, fields=['id', 'product', 'variant_group', 'quantity', 'notes'], extra=1, # Показать 1 пустую форму для первого компонента can_delete=True, # Разрешить удаление компонентов min_num=0, # Минимум 0 компонентов (можно создать пустой комплект) validate_min=False, # Не требовать минимум компонентов can_delete_extra=True, # Разрешить удалять дополнительные формы ) # Формсет для редактирования комплектов (без пустых форм, только существующие компоненты) KitItemFormSetUpdate = inlineformset_factory( ProductKit, KitItem, form=KitItemForm, fields=['id', 'product', 'variant_group', 'quantity', 'notes'], 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_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