Добавлена валидация уникальности 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>
This commit is contained in:
2025-11-11 17:36:11 +03:00
parent 0973121b39
commit 9394abfa3f
5 changed files with 118 additions and 53 deletions

View File

@@ -380,67 +380,54 @@ def api_create_customer(request):
try:
data = json.loads(request.body)
name = data.get('name', '')
phone = data.get('phone')
email = data.get('email', '')
# Нормализуем данные
name = data.get('name', '').strip() if data.get('name') else ''
phone = data.get('phone', '').strip() if data.get('phone') else ''
email = data.get('email', '').strip() if data.get('email') else ''
# Нормализуем строки (может быть None из JavaScript)
name = name.strip() if name else ''
phone = phone.strip() if phone else ''
email = email.strip() if email else ''
# Подготавливаем данные для формы
form_data = {
'name': name,
'phone': phone if phone else None,
'email': email if email else None,
}
# Используем форму для валидации и создания
form = CustomerForm(data=form_data)
if form.is_valid():
# Сохраняем клиента через форму (автоматически вызывает все валидации)
customer = form.save()
phone_display = str(customer.phone) if customer.phone else ''
return JsonResponse({
'success': True,
'id': customer.pk,
'name': customer.name,
'phone': phone_display,
'email': customer.email if customer.email else '',
}, status=201)
else:
# Собираем ошибки валидации
errors = []
for field, field_errors in form.errors.items():
for error in field_errors:
errors.append(error)
# Возвращаем первую ошибку
error_message = errors[0] if errors else 'Ошибка валидации данных'
# Валидация: имя обязательно
if not name:
return JsonResponse({
'success': False,
'error': 'Имя клиента обязательно'
'error': error_message
}, status=400)
# Нормализуем телефон если он указан
if phone:
phone = normalize_query_phone(phone)
# Проверяем, не существует ли уже клиент с таким телефоном
if phone and Customer.objects.filter(phone=phone).exists():
return JsonResponse({
'success': False,
'error': 'Клиент с таким номером телефона уже существует'
}, status=400)
# Проверяем, не существует ли уже клиент с таким email
if email and Customer.objects.filter(email=email).exists():
return JsonResponse({
'success': False,
'error': 'Клиент с таким email уже существует'
}, status=400)
# Создаем нового клиента
customer = Customer.objects.create(
name=name,
phone=phone if phone else None,
email=email if email else None
)
phone_display = str(customer.phone) if customer.phone else ''
return JsonResponse({
'success': True,
'id': customer.pk,
'name': customer.name,
'phone': phone_display,
'email': customer.email,
}, status=201)
except json.JSONDecodeError:
return JsonResponse({
'success': False,
'error': 'Некорректный JSON'
}, status=400)
except ValidationError as e:
return JsonResponse({
'success': False,
'error': str(e)
}, status=400)
except Exception as e:
return JsonResponse({
'success': False,