feat: Добавить трёхуровневую защиту от дубликатов имён товаров, категорий, тегов и комплектов
Реализована полная система обеспечения уникальности названий: 1. **Уровень БД (Model Constraints)** - добавлены UniqueConstraint для: - Product: уникальность имени среди активных товаров - ProductCategory: уникальность имени среди активных категорий - ProductTag: уникальность имени только для активных тегов (неактивные могут повторяться) - ProductKit: уникальность имени среди активных, непроизвременных комплектов 2. **Уровень формы (Form Validation)** - добавлены clean() методы для: - ProductForm, ProductKitForm, ProductCategoryForm, ProductTagForm - Валидация до попытки сохранения в БД - Сохранение введённых данных при ошибке валидации 3. **Уровень представления (IntegrityError Handling)** - добавлена обработка в views: - ProductCategoryCreateView, ProductCategoryUpdateView - ProductTagCreateView, ProductTagUpdateView - ProductKitCreateView, ProductKitUpdateView - create_tag_api: защита от race conditions с fallback поиском Три уровня защиты гарантируют: - Профилактика ошибок на уровне формы - Обработка исключительных ситуаций в views - Защита БД от одновременных запросов (race conditions) - Пользователь видит понятное сообщение об ошибке вместо 500 ошибки 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -681,6 +681,7 @@ def create_tag_api(request):
|
||||
|
||||
try:
|
||||
import json
|
||||
from django.db import IntegrityError
|
||||
from ..models import ProductTag
|
||||
|
||||
data = json.loads(request.body)
|
||||
@@ -700,29 +701,59 @@ def create_tag_api(request):
|
||||
}, status=400)
|
||||
|
||||
# Проверка уникальности (регистронезависимо)
|
||||
if ProductTag.objects.filter(name__iexact=name).exists():
|
||||
# Примечание: это проверка перед созданием, но race condition все еще возможна
|
||||
if ProductTag.objects.filter(name__iexact=name, is_active=True).exists():
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': f'Тег "{name}" уже существует'
|
||||
}, status=400)
|
||||
|
||||
# Создание тега (slug будет сгенерирован автоматически в модели)
|
||||
tag = ProductTag.objects.create(
|
||||
name=name,
|
||||
is_active=True
|
||||
)
|
||||
try:
|
||||
# Создание тега (slug будет сгенерирован автоматически в модели)
|
||||
tag = ProductTag.objects.create(
|
||||
name=name,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'tag': {
|
||||
'id': tag.id,
|
||||
'name': tag.name,
|
||||
'slug': tag.slug,
|
||||
'is_active': tag.is_active,
|
||||
'products_count': 0,
|
||||
'kits_count': 0
|
||||
}
|
||||
})
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'tag': {
|
||||
'id': tag.id,
|
||||
'name': tag.name,
|
||||
'slug': tag.slug,
|
||||
'is_active': tag.is_active,
|
||||
'products_count': 0,
|
||||
'kits_count': 0
|
||||
}
|
||||
})
|
||||
except IntegrityError as e:
|
||||
# Защита от race condition: если 2 запроса одновременно попытались создать тег
|
||||
error_msg = str(e).lower()
|
||||
if 'unique_active_tag_name' in error_msg or ('name' in error_msg and 'duplicate' in error_msg):
|
||||
# Тег был создан параллельным запросом, получаем его
|
||||
tag = ProductTag.objects.get(name__iexact=name, is_active=True)
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'tag': {
|
||||
'id': tag.id,
|
||||
'name': tag.name,
|
||||
'slug': tag.slug,
|
||||
'is_active': tag.is_active,
|
||||
'products_count': tag.products.count(),
|
||||
'kits_count': tag.kits.count()
|
||||
}
|
||||
})
|
||||
elif 'slug' in error_msg:
|
||||
# Конфликт slug, это редко должно происходить но обработаем
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': f'Тег с названием "{name}" не может быть создан (конфликт идентификатора). Пожалуйста, попробуйте другое название.'
|
||||
}, status=400)
|
||||
else:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Ошибка при создании тега: нарушение уникальности'
|
||||
}, status=500)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({
|
||||
|
||||
Reference in New Issue
Block a user