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:
2025-11-15 13:49:52 +03:00
parent 0b41c6815c
commit 079bd23829
9 changed files with 386 additions and 45 deletions

View File

@@ -0,0 +1,37 @@
# Generated by Django 5.0.10 on 2025-11-15 10:37
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0002_initial'),
('products', '0002_photoprocessingstatus'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='producttag',
name='name',
field=models.CharField(max_length=100, verbose_name='Название'),
),
migrations.AddConstraint(
model_name='product',
constraint=models.UniqueConstraint(condition=models.Q(('is_deleted', False)), fields=('name',), name='unique_active_product_name'),
),
migrations.AddConstraint(
model_name='productcategory',
constraint=models.UniqueConstraint(condition=models.Q(('is_deleted', False)), fields=('name',), name='unique_active_category_name'),
),
migrations.AddConstraint(
model_name='productkit',
constraint=models.UniqueConstraint(condition=models.Q(('is_deleted', False), ('is_temporary', False)), fields=('name',), name='unique_active_kit_name'),
),
migrations.AddConstraint(
model_name='producttag',
constraint=models.UniqueConstraint(condition=models.Q(('is_active', True)), fields=('name',), name='unique_active_tag_name'),
),
]