feat: Замена is_active на status для архивирования товаров
Реализована трёхуровневая система статусов товаров и комплектов: - active (Активный) - товар доступен для продажи - archived (Архивный) - скрыт, можно восстановить в следующем сезоне - discontinued (Снят) - морально устарел, готов к удалению Изменения: 1. Модели (BaseProductEntity, Product, ProductKit): - Заменено поле is_deleted (Boolean) на status (CharField) - Добавлены архивные метаданные (archived_at, archived_by) - Обновлены методы: archive(), restore(), discontinue(), delete() - Уникальное ограничение изменено на conditional (status='active') 2. Менеджеры (ActiveManager, SoftDeleteQuerySet): - Полиморфная поддержка обеих систем (status и is_active) - Использует hasattr() для совместимости с наследниками - Методы: archive(), restore(), discontinue(), archived_only(), active_only() 3. Формы (ProductForm, ProductKitForm): - Включены поле status в формы - Валидация уникальности по status='active' - CSS классы для статус-селектора 4. Admin панель: - DeletedFilter переименован в StatusFilter с тремя опциями - get_status_display() с цветным отображением статуса - Actions: restore_items, hard_delete_selected, delete_selected - Readonly поля для архивирования 5. Представления: - ProductListView: фильтр status вместо is_active - CombinedProductListView: поддержка фильтра status для товаров и комплектов - API views обновлены для работы со статусом 6. Шаблоны: - product_form.html: form.status вместо form.is_active - productkit_create.html: form.status вместо form.is_active - productkit_edit.html: form.status вместо form.is_active 7. Миграции: - Удалены все старые миграции (чистый перезапуск по требованию пользователя) - Создана новая миграция 0001_initial с полной структурой status-системы - Удален старый код преобразования is_deleted -> status Проведённые проверки: - Django system check passed ✓ - Полиморфные менеджеры работают с обеими системами - Уникальные ограничения корректно работают с условиями - История заказов сохраняется даже после архивирования товара (django-simple-history) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-14 20:45
|
||||
# Generated by Django 5.0.10 on 2025-11-15 11:57
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@@ -15,6 +15,17 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KitItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.DecimalField(blank=True, decimal_places=3, max_digits=10, null=True, verbose_name='Количество')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Компонент комплекта',
|
||||
'verbose_name_plural': 'Компоненты комплектов',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductVariantGroup',
|
||||
fields=[
|
||||
@@ -30,6 +41,18 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductVariantGroupItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('priority', models.PositiveIntegerField(default=0, help_text='Меньше = выше приоритет (1 - наивысший приоритет в этой группе)')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Товар в группе вариантов',
|
||||
'verbose_name_plural': 'Товары в группах вариантов',
|
||||
'ordering': ['priority', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SKUCounter',
|
||||
fields=[
|
||||
@@ -42,6 +65,74 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Счетчики артикулов',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PhotoProcessingStatus',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('photo_id', models.IntegerField(help_text='ID объекта ProductPhoto/ProductKitPhoto/ProductCategoryPhoto', verbose_name='ID фото')),
|
||||
('photo_model', models.CharField(help_text='Полный путь модели (e.g., products.ProductPhoto)', max_length=100, verbose_name='Модель фото')),
|
||||
('status', models.CharField(choices=[('pending', 'В очереди'), ('processing', 'Обрабатывается'), ('completed', 'Завершено'), ('failed', 'Ошибка')], db_index=True, default='pending', max_length=20, verbose_name='Статус обработки')),
|
||||
('task_id', models.CharField(blank=True, db_index=True, help_text='Уникальный ID задачи для отслеживания', max_length=255, verbose_name='ID задачи Celery')),
|
||||
('error_message', models.TextField(blank=True, help_text='Детальное описание ошибки при обработке', verbose_name='Сообщение об ошибке')),
|
||||
('result_data', models.JSONField(blank=True, default=dict, help_text='JSON с информацией о качестве, путях и метаданных', verbose_name='Результаты обработки')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('started_at', models.DateTimeField(blank=True, null=True, verbose_name='Время начала обработки')),
|
||||
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Время завершения обработки')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Статус обработки фото',
|
||||
'verbose_name_plural': 'Статусы обработки фото',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['photo_id', 'photo_model'], name='products_ph_photo_i_e42a67_idx'), models.Index(fields=['task_id'], name='products_ph_task_id_748118_idx'), models.Index(fields=['status'], name='products_ph_status_1182b4_idx'), models.Index(fields=['status', 'created_at'], name='products_ph_status_41d415_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('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-идентификатор')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('short_description', models.TextField(blank=True, help_text='Используется для карточек товаров, превью и площадок', null=True, verbose_name='Краткое описание')),
|
||||
('status', models.CharField(choices=[('active', 'Активный'), ('archived', 'Архивный'), ('discontinued', 'Снят')], db_index=True, default='active', max_length=20, verbose_name='Статус')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('archived_at', models.DateTimeField(blank=True, null=True, verbose_name='Время архивирования')),
|
||||
('variant_suffix', models.CharField(blank=True, help_text='Суффикс для артикула (например: 50, 60, S, M). Автоматически извлекается из названия.', max_length=20, null=True, verbose_name='Суффикс варианта')),
|
||||
('unit', models.CharField(choices=[('шт', 'Штука'), ('м', 'Метр'), ('г', 'Грамм'), ('л', 'Литр'), ('кг', 'Килограмм')], default='шт', max_length=10, verbose_name='Единица измерения')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, help_text='Автоматически вычисляется из партий (средневзвешенная стоимость по FIFO)', max_digits=10, verbose_name='Себестоимость')),
|
||||
('price', models.DecimalField(decimal_places=2, help_text='Цена продажи товара (бывшее поле sale_price)', max_digits=10, verbose_name='Основная цена')),
|
||||
('sale_price', models.DecimalField(blank=True, decimal_places=2, help_text='Если задана, товар продается по этой цене (дешевле основной)', max_digits=10, null=True, verbose_name='Цена со скидкой')),
|
||||
('in_stock', models.BooleanField(db_index=True, default=False, help_text='Автоматически обновляется при изменении остатков на складе', verbose_name='В наличии')),
|
||||
('search_keywords', models.TextField(blank=True, help_text='Автоматически генерируется из названия, артикула, описания и категории. Можно дополнить вручную.', verbose_name='Ключевые слова для поиска')),
|
||||
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Архивировано пользователем')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Товар',
|
||||
'verbose_name_plural': 'Товары',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KitItemPriority',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('priority', models.PositiveIntegerField(default=0, help_text='Меньше = выше приоритет (0 - наивысший)')),
|
||||
('kit_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='priorities', to='products.kititem', verbose_name='Позиция в букете')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product', verbose_name='Товар')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Приоритет варианта',
|
||||
'verbose_name_plural': 'Приоритеты вариантов',
|
||||
'ordering': ['priority', 'id'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kititem',
|
||||
name='product',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kit_items_direct', to='products.product', verbose_name='Конкретный товар'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductCategory',
|
||||
fields=[
|
||||
@@ -62,34 +153,10 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Категории товаров',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('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-идентификатор')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('short_description', models.TextField(blank=True, help_text='Используется для карточек товаров, превью и площадок', null=True, verbose_name='Краткое описание')),
|
||||
('is_active', models.BooleanField(default=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='Время удаления')),
|
||||
('variant_suffix', models.CharField(blank=True, help_text='Суффикс для артикула (например: 50, 60, S, M). Автоматически извлекается из названия.', max_length=20, null=True, verbose_name='Суффикс варианта')),
|
||||
('unit', models.CharField(choices=[('шт', 'Штука'), ('м', 'Метр'), ('г', 'Грамм'), ('л', 'Литр'), ('кг', 'Килограмм')], default='шт', max_length=10, verbose_name='Единица измерения')),
|
||||
('cost_price', models.DecimalField(decimal_places=2, help_text='Автоматически вычисляется из партий (средневзвешенная стоимость по FIFO)', max_digits=10, verbose_name='Себестоимость')),
|
||||
('price', models.DecimalField(decimal_places=2, help_text='Цена продажи товара (бывшее поле sale_price)', max_digits=10, verbose_name='Основная цена')),
|
||||
('sale_price', models.DecimalField(blank=True, decimal_places=2, help_text='Если задана, товар продается по этой цене (дешевле основной)', max_digits=10, null=True, verbose_name='Цена со скидкой')),
|
||||
('in_stock', models.BooleanField(db_index=True, default=False, help_text='Автоматически обновляется при изменении остатков на складе', verbose_name='В наличии')),
|
||||
('search_keywords', models.TextField(blank=True, help_text='Автоматически генерируется из названия, артикула, описания и категории. Можно дополнить вручную.', verbose_name='Ключевые слова для поиска')),
|
||||
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Удален пользователем')),
|
||||
('categories', models.ManyToManyField(blank=True, related_name='products', to='products.productcategory', verbose_name='Категории')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Товар',
|
||||
'verbose_name_plural': 'Товары',
|
||||
},
|
||||
migrations.AddField(
|
||||
model_name='product',
|
||||
name='categories',
|
||||
field=models.ManyToManyField(blank=True, related_name='products', to='products.productcategory', verbose_name='Категории'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductCategoryPhoto',
|
||||
@@ -117,19 +184,18 @@ class Migration(migrations.Migration):
|
||||
('slug', models.SlugField(blank=True, max_length=200, unique=True, verbose_name='URL-идентификатор')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('short_description', models.TextField(blank=True, help_text='Используется для карточек товаров, превью и площадок', null=True, verbose_name='Краткое описание')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
|
||||
('status', models.CharField(choices=[('active', 'Активный'), ('archived', 'Архивный'), ('discontinued', 'Снят')], db_index=True, default='active', max_length=20, 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='Время удаления')),
|
||||
('archived_at', models.DateTimeField(blank=True, null=True, verbose_name='Время архивирования')),
|
||||
('base_price', models.DecimalField(decimal_places=2, default=0, help_text='Сумма actual_price всех компонентов. Пересчитывается автоматически.', max_digits=10, verbose_name='Базовая цена')),
|
||||
('price', models.DecimalField(decimal_places=2, default=0, help_text='Базовая цена с учетом корректировок. Вычисляется автоматически.', max_digits=10, verbose_name='Итоговая цена')),
|
||||
('sale_price', models.DecimalField(blank=True, decimal_places=2, help_text='Если задана, комплект продается по этой цене', max_digits=10, null=True, verbose_name='Цена со скидкой')),
|
||||
('price_adjustment_type', models.CharField(choices=[('none', 'Без изменения'), ('increase_percent', 'Увеличить на %'), ('increase_amount', 'Увеличить на сумму'), ('decrease_percent', 'Уменьшить на %'), ('decrease_amount', 'Уменьшить на сумму')], default='none', max_length=20, verbose_name='Тип корректировки цены')),
|
||||
('price_adjustment_value', models.DecimalField(decimal_places=2, default=0, help_text='Процент (%) или сумма (руб) в зависимости от типа корректировки', max_digits=10, verbose_name='Значение корректировки')),
|
||||
('is_temporary', models.BooleanField(default=False, help_text='Временные комплекты не показываются в каталоге и создаются для конкретного заказа', verbose_name='Временный комплект')),
|
||||
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Архивировано пользователем')),
|
||||
('categories', models.ManyToManyField(blank=True, related_name='kits', to='products.productcategory', verbose_name='Категории')),
|
||||
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_%(class)s_set', to=settings.AUTH_USER_MODEL, verbose_name='Удален пользователем')),
|
||||
('order', models.ForeignKey(blank=True, help_text='Заказ, для которого создан временный комплект', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='temporary_kits', to='orders.order', verbose_name='Заказ')),
|
||||
],
|
||||
options={
|
||||
@@ -137,6 +203,11 @@ class Migration(migrations.Migration):
|
||||
'verbose_name_plural': 'Комплекты',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kititem',
|
||||
name='kit',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kit_items', to='products.productkit', verbose_name='Комплект'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductKitPhoto',
|
||||
fields=[
|
||||
@@ -175,7 +246,7 @@ class Migration(migrations.Migration):
|
||||
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='Название')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название')),
|
||||
('slug', models.SlugField(max_length=100, unique=True, verbose_name='URL-идентификатор')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True, verbose_name='Активен')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Дата создания')),
|
||||
@@ -187,6 +258,10 @@ class Migration(migrations.Migration):
|
||||
'indexes': [models.Index(fields=['is_active'], name='products_pr_is_acti_7f288f_idx')],
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='producttag',
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('is_active', True)), fields=('name',), name='unique_active_tag_name'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='productkit',
|
||||
name='tags',
|
||||
@@ -202,48 +277,24 @@ class Migration(migrations.Migration):
|
||||
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(blank=True, decimal_places=3, max_digits=10, null=True, 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='Комплект')),
|
||||
('variant_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kit_items', to='products.productvariantgroup', verbose_name='Группа вариантов')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Компонент комплекта',
|
||||
'verbose_name_plural': 'Компоненты комплектов',
|
||||
},
|
||||
migrations.AddField(
|
||||
model_name='kititem',
|
||||
name='variant_group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='kit_items', to='products.productvariantgroup', verbose_name='Группа вариантов'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProductVariantGroupItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('priority', models.PositiveIntegerField(default=0, help_text='Меньше = выше приоритет (1 - наивысший приоритет в этой группе)')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variant_group_items', to='products.product', verbose_name='Товар')),
|
||||
('variant_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='products.productvariantgroup', verbose_name='Группа вариантов')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Товар в группе вариантов',
|
||||
'verbose_name_plural': 'Товары в группах вариантов',
|
||||
'ordering': ['priority', 'id'],
|
||||
},
|
||||
migrations.AddField(
|
||||
model_name='productvariantgroupitem',
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variant_group_items', to='products.product', verbose_name='Товар'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KitItemPriority',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('priority', models.PositiveIntegerField(default=0, help_text='Меньше = выше приоритет (0 - наивысший)')),
|
||||
('kit_item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='priorities', to='products.kititem', verbose_name='Позиция в букете')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='products.product', verbose_name='Товар')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Приоритет варианта',
|
||||
'verbose_name_plural': 'Приоритеты вариантов',
|
||||
'ordering': ['priority', 'id'],
|
||||
'unique_together': {('kit_item', 'product')},
|
||||
},
|
||||
migrations.AddField(
|
||||
model_name='productvariantgroupitem',
|
||||
name='variant_group',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='products.productvariantgroup', verbose_name='Группа вариантов'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='kititempriority',
|
||||
unique_together={('kit_item', 'product')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productcategory',
|
||||
@@ -257,6 +308,10 @@ class Migration(migrations.Migration):
|
||||
model_name='productcategory',
|
||||
index=models.Index(fields=['is_deleted', 'created_at'], name='products_pr_is_dele_b8cdf3_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='productcategory',
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('is_deleted', False)), fields=('name',), name='unique_active_category_name'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='productcategoryphoto',
|
||||
index=models.Index(fields=['quality_level'], name='products_pr_quality_ab44c2_idx'),
|
||||
@@ -301,6 +356,10 @@ class Migration(migrations.Migration):
|
||||
model_name='productkit',
|
||||
index=models.Index(fields=['order'], name='products_pr_order_i_2b5675_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='productkit',
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('is_temporary', False), ('status', 'active')), fields=('name',), name='unique_active_kit_name'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='product',
|
||||
index=models.Index(fields=['in_stock'], name='products_pr_in_stoc_4fee1a_idx'),
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-15 07:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('products', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PhotoProcessingStatus',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('photo_id', models.IntegerField(help_text='ID объекта ProductPhoto/ProductKitPhoto/ProductCategoryPhoto', verbose_name='ID фото')),
|
||||
('photo_model', models.CharField(help_text='Полный путь модели (e.g., products.ProductPhoto)', max_length=100, verbose_name='Модель фото')),
|
||||
('status', models.CharField(choices=[('pending', 'В очереди'), ('processing', 'Обрабатывается'), ('completed', 'Завершено'), ('failed', 'Ошибка')], db_index=True, default='pending', max_length=20, verbose_name='Статус обработки')),
|
||||
('task_id', models.CharField(blank=True, db_index=True, help_text='Уникальный ID задачи для отслеживания', max_length=255, verbose_name='ID задачи Celery')),
|
||||
('error_message', models.TextField(blank=True, help_text='Детальное описание ошибки при обработке', verbose_name='Сообщение об ошибке')),
|
||||
('result_data', models.JSONField(blank=True, default=dict, help_text='JSON с информацией о качестве, путях и метаданных', verbose_name='Результаты обработки')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('started_at', models.DateTimeField(blank=True, null=True, verbose_name='Время начала обработки')),
|
||||
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Время завершения обработки')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Статус обработки фото',
|
||||
'verbose_name_plural': 'Статусы обработки фото',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['photo_id', 'photo_model'], name='products_ph_photo_i_e42a67_idx'), models.Index(fields=['task_id'], name='products_ph_task_id_748118_idx'), models.Index(fields=['status'], name='products_ph_status_1182b4_idx'), models.Index(fields=['status', 'created_at'], name='products_ph_status_41d415_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,37 +0,0 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user