Files
octopus/myproject/customers/forms.py
Andrey Smakotin 9394abfa3f Добавлена валидация уникальности email и phone для клиентов
Изменения:
- Добавлено ограничение unique=True для поля email в модели Customer
- Email теперь поддерживает NULL значения (несколько клиентов могут быть без email)
- Добавлены методы clean_email() и clean_phone() в CustomerForm для валидации
- Переписан API endpoint api_create_customer для использования формы вместо прямого создания
- Создано две миграции: сначала разрешение NULL, затем добавление unique constraint
- В текущей БД преобразованы пустые строки email в NULL (4 записи)

Это исправляет:
- Возможность создания дубликатов клиентов с одинаковыми email/phone
- Работает как в веб-форме, так и в модальном окне создания заказа
- Устранена уязвимость race condition через использование DB constraints

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 17:36:11 +03:00

80 lines
3.2 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.core.exceptions import ValidationError
from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.widgets import PhoneNumberPrefixWidget
from .models import Customer
class CustomerForm(forms.ModelForm):
phone = PhoneNumberField(
region='BY',
required=False,
help_text='Формат: +375XXXXXXXXX или 80XXXXXXXXX',
widget=forms.TextInput(attrs={'placeholder': '+375XXXXXXXXX'})
)
class Meta:
model = Customer
fields = ['name', 'email', 'phone', 'loyalty_tier', 'notes']
widgets = {
'notes': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Ensure phone displays in E.164 format
if self.instance and self.instance.phone:
self.initial['phone'] = str(self.instance.phone)
for field_name, field in self.fields.items():
if field_name == 'notes':
# Textarea already has rows=3 from widget, just add class
field.widget.attrs.update({'class': 'form-control'})
elif field_name == 'loyalty_tier':
# Select fields need form-select class
field.widget.attrs.update({'class': 'form-select'})
elif field_name == 'phone':
# Phone field gets form-control class
field.widget.attrs.update({'class': 'form-control'})
else:
# Regular input fields get form-control class
field.widget.attrs.update({'class': 'form-control'})
def clean_email(self):
"""Проверяет уникальность email при создании/редактировании"""
email = self.cleaned_data.get('email')
# Если email пустой, это нормально (blank=True)
if not email:
return email
# Проверяем уникальность
queryset = Customer.objects.filter(email=email)
# При редактировании исключаем текущий экземпляр
if self.instance and self.instance.pk:
queryset = queryset.exclude(pk=self.instance.pk)
if queryset.exists():
raise ValidationError('Клиент с таким email уже существует.')
return email
def clean_phone(self):
"""Проверяет уникальность телефона при создании/редактировании"""
phone = self.cleaned_data.get('phone')
# Если телефон пустой, это нормально (blank=True)
if not phone:
return phone
# Проверяем уникальность
queryset = Customer.objects.filter(phone=phone)
# При редактировании исключаем текущий экземпляр
if self.instance and self.instance.pk:
queryset = queryset.exclude(pk=self.instance.pk)
if queryset.exists():
raise ValidationError('Клиент с таким номером телефона уже существует.')
return phone