Добавлен параметр format для виджетов DateInput и TimeInput, что позволяет корректно отображать сохраненные значения даты и времени в HTML5 полях формы. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
234 lines
9.2 KiB
Python
234 lines
9.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
from django import forms
|
||
from django.forms import inlineformset_factory
|
||
from .models import Order, OrderItem
|
||
from customers.models import Customer, Address
|
||
from shops.models import Shop
|
||
from products.models import Product, ProductKit
|
||
|
||
|
||
class OrderForm(forms.ModelForm):
|
||
"""Форма для создания и редактирования заказа"""
|
||
|
||
class Meta:
|
||
model = Order
|
||
fields = [
|
||
'customer',
|
||
'is_delivery',
|
||
'delivery_address',
|
||
'pickup_shop',
|
||
'delivery_date',
|
||
'delivery_time_start',
|
||
'delivery_time_end',
|
||
'delivery_cost',
|
||
'customer_is_recipient',
|
||
'recipient_name',
|
||
'recipient_phone',
|
||
'status',
|
||
'payment_method',
|
||
'discount_amount',
|
||
'is_anonymous',
|
||
'special_instructions',
|
||
]
|
||
widgets = {
|
||
'delivery_date': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||
'delivery_time_start': forms.TimeInput(attrs={'type': 'time'}, format='%H:%M'),
|
||
'delivery_time_end': forms.TimeInput(attrs={'type': 'time'}, format='%H:%M'),
|
||
'special_instructions': forms.Textarea(attrs={'rows': 3}),
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Добавляем Bootstrap классы ко всем полям
|
||
for field_name, field in self.fields.items():
|
||
if isinstance(field.widget, forms.CheckboxInput):
|
||
field.widget.attrs.update({'class': 'form-check-input'})
|
||
elif isinstance(field.widget, forms.Textarea):
|
||
field.widget.attrs.update({'class': 'form-control', 'rows': 3})
|
||
else:
|
||
field.widget.attrs.update({'class': 'form-control'})
|
||
|
||
# Select2 для выпадающих списков
|
||
self.fields['customer'].widget.attrs.update({
|
||
'class': 'form-select select2',
|
||
'data-placeholder': 'Выберите клиента'
|
||
})
|
||
|
||
self.fields['delivery_address'].widget.attrs.update({
|
||
'class': 'form-select select2',
|
||
'data-placeholder': 'Выберите адрес доставки'
|
||
})
|
||
self.fields['delivery_address'].required = False
|
||
|
||
self.fields['pickup_shop'].widget.attrs.update({
|
||
'class': 'form-select select2',
|
||
'data-placeholder': 'Выберите точку самовывоза'
|
||
})
|
||
self.fields['pickup_shop'].required = False
|
||
|
||
# Опциональные поля даты/времени
|
||
self.fields['delivery_date'].required = False
|
||
self.fields['delivery_time_start'].required = False
|
||
self.fields['delivery_time_end'].required = False
|
||
|
||
# Подсказки
|
||
self.fields['is_delivery'].label = 'С доставкой'
|
||
self.fields['customer_is_recipient'].label = 'Покупатель = получатель'
|
||
|
||
# Поля получателя опциональны
|
||
self.fields['recipient_name'].required = False
|
||
self.fields['recipient_phone'].required = False
|
||
|
||
|
||
class OrderItemForm(forms.ModelForm):
|
||
"""Форма для позиции заказа"""
|
||
|
||
class Meta:
|
||
model = OrderItem
|
||
fields = ['product', 'product_kit', 'quantity', 'price', 'is_custom_price']
|
||
widgets = {
|
||
'quantity': forms.NumberInput(attrs={'min': 1, 'value': 1}),
|
||
# Скрываем поля product и product_kit - они будут заполняться через JS
|
||
'product': forms.HiddenInput(),
|
||
'product_kit': forms.HiddenInput(),
|
||
'is_custom_price': forms.HiddenInput(),
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Bootstrap классы
|
||
for field_name, field in self.fields.items():
|
||
if not isinstance(field.widget, forms.HiddenInput):
|
||
field.widget.attrs.update({'class': 'form-control'})
|
||
|
||
# Поля product и product_kit опциональны
|
||
self.fields['product'].required = False
|
||
self.fields['product_kit'].required = False
|
||
|
||
# Поле цены заполняется автоматически, но можно редактировать вручную
|
||
self.fields['price'].widget.attrs.update({
|
||
'placeholder': 'Цена',
|
||
'step': '0.01'
|
||
})
|
||
self.fields['price'].required = False
|
||
|
||
# Поле is_custom_price устанавливается через JS
|
||
self.fields['is_custom_price'].required = False
|
||
|
||
def clean(self):
|
||
"""Валидация: должен быть выбран либо товар, либо комплект (не оба, не ни один)"""
|
||
cleaned_data = super().clean()
|
||
product = cleaned_data.get('product')
|
||
product_kit = cleaned_data.get('product_kit')
|
||
quantity = cleaned_data.get('quantity')
|
||
|
||
# Пустая форма - это нормально (будет удалена)
|
||
if not product and not product_kit:
|
||
# Обнуляем количество для пустых форм
|
||
cleaned_data['quantity'] = None
|
||
return cleaned_data
|
||
|
||
# Проверка: нельзя выбрать оба одновременно
|
||
if product and product_kit:
|
||
raise forms.ValidationError(
|
||
'Нельзя указывать одновременно товар и комплект. Выберите что-то одно.'
|
||
)
|
||
|
||
# Проверка: если выбрано что-то, количество обязательно
|
||
if (product or product_kit):
|
||
if not quantity or quantity <= 0:
|
||
raise forms.ValidationError('Необходимо указать количество больше 0')
|
||
|
||
return cleaned_data
|
||
|
||
|
||
# Formset для inline добавления товаров в заказ
|
||
OrderItemFormSet = inlineformset_factory(
|
||
Order,
|
||
OrderItem,
|
||
form=OrderItemForm,
|
||
extra=0, # Без пустых форм (будем добавлять через JavaScript)
|
||
can_delete=True,
|
||
min_num=0, # Минимум 0 товаров (валидация на уровне бизнес-логики)
|
||
validate_min=False,
|
||
)
|
||
|
||
|
||
# === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
|
||
|
||
class TemporaryKitForm(forms.ModelForm):
|
||
"""
|
||
Упрощенная форма для создания временного комплекта.
|
||
Используется при оформлении заказа для создания букета "на лету".
|
||
"""
|
||
|
||
class Meta:
|
||
model = ProductKit
|
||
fields = ['name', 'description']
|
||
widgets = {
|
||
'description': forms.Textarea(attrs={'rows': 2, 'placeholder': 'Краткое описание (опционально)'}),
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Bootstrap классы
|
||
for field in self.fields.values():
|
||
if isinstance(field.widget, forms.Textarea):
|
||
field.widget.attrs.update({'class': 'form-control'})
|
||
else:
|
||
field.widget.attrs.update({'class': 'form-control'})
|
||
|
||
# Название обязательно
|
||
self.fields['name'].required = True
|
||
self.fields['name'].widget.attrs.update({
|
||
'placeholder': 'Название временного букета (например: "Букет для Анны")'
|
||
})
|
||
|
||
# Описание опционально
|
||
self.fields['description'].required = False
|
||
|
||
|
||
class TemporaryKitItemForm(forms.Form):
|
||
"""
|
||
Форма для компонента временного комплекта.
|
||
Используется в формсете для добавления товаров в букет.
|
||
"""
|
||
product = forms.IntegerField(required=False, widget=forms.HiddenInput())
|
||
quantity = forms.DecimalField(
|
||
required=False,
|
||
min_value=0.001,
|
||
max_digits=10,
|
||
decimal_places=3,
|
||
widget=forms.NumberInput(attrs={'class': 'form-control', 'min': '0.001', 'step': '1'})
|
||
)
|
||
|
||
def clean(self):
|
||
cleaned_data = super().clean()
|
||
product_id = cleaned_data.get('product')
|
||
quantity = cleaned_data.get('quantity')
|
||
|
||
# Пустая форма - это нормально (будет удалена)
|
||
if not product_id:
|
||
return cleaned_data
|
||
|
||
# Если выбран товар, количество обязательно
|
||
if product_id and (not quantity or quantity <= 0):
|
||
raise forms.ValidationError('Необходимо указать количество больше 0')
|
||
|
||
return cleaned_data
|
||
|
||
|
||
# Formset для компонентов временного комплекта
|
||
from django.forms import formset_factory
|
||
|
||
TemporaryKitItemFormSet = formset_factory(
|
||
TemporaryKitItemForm,
|
||
extra=1, # Одна пустая форма для добавления
|
||
can_delete=True,
|
||
min_num=1, # Минимум 1 компонент в комплекте
|
||
validate_min=True,
|
||
)
|