fix: Исправить сохранение множественных товаров в комплект
ПРОБЛЕМА: При создании комплекта с несколькими товарами сохранялся только первый товар. ПРИЧИНЫ И РЕШЕНИЯ: 1. Неправильный префикс в JavaScript (productkit_create.html) - Динамически добавляемые формы создавались с префиксом kititem_set- - Django ожидает префикс kititem- - ИСПРАВЛЕНО: изменены все name атрибуты с kititem_set- на kititem- 2. NULL constraint для quantity (models.py) - Поле KitItem.quantity было NOT NULL - Пустые формы пытались сохраняться с NULL - ИСПРАВЛЕНО: добавлены null=True, blank=True к полю quantity 3. Неправильная валидация пустых форм (forms.py) - Не было логики для обработки пустых компонентов - ИСПРАВЛЕНО: пустые формы получают quantity=None, заполненные требуют quantity>0 4. Неправильный порядок сохранения (productkit_views.py) - Формсет не имел правильного prefixсе - ИСПРАВЛЕНО: явно установлен prefix='kititem' везде (get_context_data, form_valid) ✅ РЕЗУЛЬТАТ: Теперь можно создавать комплекты с неограниченным количеством товаров 🧪 ТЕСТИРОВАНО: - Комплект 0 товаров ✓ - Комплект 1 товар ✓ - Комплект 3 товара ✓ 🤖 Generated with Claude Code
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 13:03
|
||||
# Generated by Django 5.2.7 on 2025-10-23 20:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -9,21 +10,10 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProductTag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True, verbose_name='Название')),
|
||||
('slug', models.SlugField(max_length=100, unique=True, verbose_name='URL-идентификатор')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Тег товара',
|
||||
'verbose_name_plural': 'Теги товаров',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductVariantGroup',
|
||||
fields=[
|
||||
@@ -59,6 +49,11 @@ class Migration(migrations.Migration):
|
||||
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, unique=True, verbose_name='Артикул')),
|
||||
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активна')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Дата обновления')),
|
||||
('is_deleted', models.BooleanField(db_index=True, default=False, verbose_name='Удалена')),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Время удаления')),
|
||||
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_categories', to=settings.AUTH_USER_MODEL, verbose_name='Удалена пользователем')),
|
||||
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='products.productcategory', verbose_name='Родительская категория')),
|
||||
],
|
||||
options={
|
||||
@@ -72,6 +67,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('sku', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Артикул')),
|
||||
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
|
||||
('variant_suffix', models.CharField(blank=True, help_text='Суффикс для артикула (например: 50, 60, S, M). Автоматически извлекается из названия.', max_length=20, null=True, verbose_name='Суффикс варианта')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('unit', models.CharField(choices=[('шт', 'Штука'), ('м', 'Метр'), ('г', 'Грамм'), ('л', 'Литр'), ('кг', 'Килограмм')], default='шт', max_length=10, verbose_name='Единица измерения')),
|
||||
@@ -81,9 +77,10 @@ class Migration(migrations.Migration):
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('search_keywords', models.TextField(blank=True, help_text='Автоматически генерируется из названия, артикула, описания и категории. Можно дополнить вручную.', verbose_name='Ключевые слова для поиска')),
|
||||
('is_deleted', models.BooleanField(db_index=True, default=False, verbose_name='Удален')),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Время удаления')),
|
||||
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_products', to=settings.AUTH_USER_MODEL, verbose_name='Удален пользователем')),
|
||||
('categories', models.ManyToManyField(blank=True, related_name='products', to='products.productcategory', verbose_name='Категории')),
|
||||
('tags', models.ManyToManyField(blank=True, related_name='products', to='products.producttag', verbose_name='Теги')),
|
||||
('variant_groups', models.ManyToManyField(blank=True, related_name='products', to='products.productvariantgroup', verbose_name='Группы вариантов')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Товар',
|
||||
@@ -120,8 +117,10 @@ class Migration(migrations.Migration):
|
||||
('markup_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Фиксированная наценка')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('is_deleted', models.BooleanField(db_index=True, default=False, verbose_name='Удален')),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Время удаления')),
|
||||
('categories', models.ManyToManyField(blank=True, related_name='kits', to='products.productcategory', verbose_name='Категории')),
|
||||
('tags', models.ManyToManyField(blank=True, related_name='kits', to='products.producttag', verbose_name='Теги')),
|
||||
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_kits', to=settings.AUTH_USER_MODEL, verbose_name='Удален пользователем')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Комплект',
|
||||
@@ -158,11 +157,43 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['order', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductTag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True, verbose_name='Название')),
|
||||
('slug', models.SlugField(max_length=100, unique=True, verbose_name='URL-идентификатор')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, null=True, verbose_name='Дата обновления')),
|
||||
('is_deleted', models.BooleanField(db_index=True, default=False, verbose_name='Удален')),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Время удаления')),
|
||||
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_tags', to=settings.AUTH_USER_MODEL, verbose_name='Удален пользователем')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Тег товара',
|
||||
'verbose_name_plural': 'Теги товаров',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='productkit',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, related_name='kits', to='products.producttag', verbose_name='Теги'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, related_name='products', to='products.producttag', verbose_name='Теги'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='variant_groups',
|
||||
field=models.ManyToManyField(blank=True, related_name='products', to='products.productvariantgroup', verbose_name='Группы вариантов'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KitItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('quantity', models.DecimalField(blank=True, decimal_places=3, max_digits=10, null=True, verbose_name='Количество')),
|
||||
('notes', models.CharField(blank=True, max_length=200, verbose_name='Примечание')),
|
||||
('product', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kit_items_direct', to='products.product', verbose_name='Конкретный товар')),
|
||||
('kit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kit_items', to='products.productkit', verbose_name='Комплект')),
|
||||
@@ -192,6 +223,22 @@ class Migration(migrations.Migration):
|
||||
model_name='productcategory',
|
||||
index=models.Index(fields=['is_active'], name='products_pr_is_acti_226e74_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productcategory',
|
||||
index=models.Index(fields=['is_deleted'], name='products_pr_is_dele_2a96d1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productcategory',
|
||||
index=models.Index(fields=['is_deleted', 'created_at'], name='products_pr_is_dele_b8cdf3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='producttag',
|
||||
index=models.Index(fields=['is_deleted'], name='products_pr_is_dele_ea9be0_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='producttag',
|
||||
index=models.Index(fields=['is_deleted', 'created_at'], name='products_pr_is_dele_bc2d9c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productkit',
|
||||
index=models.Index(fields=['is_active'], name='products_pr_is_acti_214d4f_idx'),
|
||||
@@ -200,8 +247,24 @@ class Migration(migrations.Migration):
|
||||
model_name='productkit',
|
||||
index=models.Index(fields=['slug'], name='products_pr_slug_b5e185_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productkit',
|
||||
index=models.Index(fields=['is_deleted'], name='products_pr_is_dele_e83a83_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productkit',
|
||||
index=models.Index(fields=['is_deleted', 'created_at'], name='products_pr_is_dele_1e5bec_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='product',
|
||||
index=models.Index(fields=['is_active'], name='products_pr_is_acti_ca4d9a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='product',
|
||||
index=models.Index(fields=['is_deleted'], name='products_pr_is_dele_3bba04_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='product',
|
||||
index=models.Index(fields=['is_deleted', 'created_at'], name='products_pr_is_dele_f30efb_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user