Обновления миграций в products и удаление старых миграций

This commit is contained in:
2026-01-08 22:12:54 +03:00
parent 8590b5907c
commit 7f91244d63
4 changed files with 59 additions and 169 deletions

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.0.10 on 2026-01-03 23:23
# Generated by Django 5.0.10 on 2026-01-08 15:58
import django.core.validators
import django.db.models.deletion
@@ -307,6 +307,7 @@ class Migration(migrations.Migration):
name='ProductCategoryPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_main', models.BooleanField(db_index=True, default=False, help_text='Главное фото отображается в карточках, каталоге и превью. Может быть только одно.', verbose_name='Главное фото')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('image', models.ImageField(upload_to=products.models.photos.get_category_photo_upload_path, verbose_name='Оригинальное фото')),
@@ -317,7 +318,35 @@ class Migration(migrations.Migration):
options={
'verbose_name': 'Фото категории',
'verbose_name_plural': 'Фото категорий',
'ordering': ['order', '-created_at'],
'ordering': ['-is_main', 'order', '-created_at'],
},
),
migrations.CreateModel(
name='ProductImportJob',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_id', models.CharField(max_length=255, unique=True, verbose_name='ID задачи Celery')),
('file_name', models.CharField(max_length=255, verbose_name='Имя файла')),
('file_path', models.CharField(help_text='Временный путь для обработки', max_length=500, verbose_name='Путь к файлу')),
('update_existing', models.BooleanField(default=False, verbose_name='Обновлять существующие')),
('status', models.CharField(choices=[('pending', 'Ожидает'), ('processing', 'Обрабатывается'), ('completed', 'Завершён'), ('failed', 'Ошибка')], db_index=True, default='pending', max_length=20, verbose_name='Статус')),
('total_rows', models.IntegerField(default=0, verbose_name='Всего строк')),
('processed_rows', models.IntegerField(default=0, verbose_name='Обработано строк')),
('created_count', models.IntegerField(default=0, verbose_name='Создано товаров')),
('updated_count', models.IntegerField(default=0, verbose_name='Обновлено товаров')),
('skipped_count', models.IntegerField(default=0, verbose_name='Пропущено')),
('errors_count', models.IntegerField(default=0, verbose_name='Ошибок')),
('errors_json', models.JSONField(blank=True, default=list, verbose_name='Детали ошибок')),
('error_message', models.TextField(blank=True, verbose_name='Сообщение об ошибке')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Завершено')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_import_jobs', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
],
options={
'verbose_name': 'Задача импорта товаров',
'verbose_name_plural': 'Задачи импорта товаров',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
@@ -368,6 +397,7 @@ class Migration(migrations.Migration):
name='ProductKitPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_main', models.BooleanField(db_index=True, default=False, help_text='Главное фото отображается в карточках, каталоге и превью. Может быть только одно.', verbose_name='Главное фото')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('image', models.ImageField(upload_to=products.models.photos.get_kit_photo_upload_path, verbose_name='Оригинальное фото')),
@@ -378,13 +408,14 @@ class Migration(migrations.Migration):
options={
'verbose_name': 'Фото комплекта',
'verbose_name_plural': 'Фото комплектов',
'ordering': ['order', '-created_at'],
'ordering': ['-is_main', 'order', '-created_at'],
},
),
migrations.CreateModel(
name='ProductPhoto',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_main', models.BooleanField(db_index=True, default=False, help_text='Главное фото отображается в карточках, каталоге и превью. Может быть только одно.', verbose_name='Главное фото')),
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('image', models.ImageField(upload_to=products.models.photos.get_product_photo_upload_path, verbose_name='Оригинальное фото')),
@@ -395,7 +426,7 @@ class Migration(migrations.Migration):
options={
'verbose_name': 'Фото товара',
'verbose_name_plural': 'Фото товаров',
'ordering': ['order', '-created_at'],
'ordering': ['-is_main', 'order', '-created_at'],
},
),
migrations.CreateModel(
@@ -539,6 +570,22 @@ class Migration(migrations.Migration):
model_name='productcategoryphoto',
index=models.Index(fields=['quality_warning', 'category'], name='products_pr_quality_fc505a_idx'),
),
migrations.AddConstraint(
model_name='productcategoryphoto',
constraint=models.UniqueConstraint(condition=models.Q(('is_main', True)), fields=('category',), name='unique_main_photo_per_category'),
),
migrations.AddIndex(
model_name='productimportjob',
index=models.Index(fields=['task_id'], name='products_pr_task_id_d8dc9f_idx'),
),
migrations.AddIndex(
model_name='productimportjob',
index=models.Index(fields=['status', '-created_at'], name='products_pr_status_326f2c_idx'),
),
migrations.AddIndex(
model_name='productimportjob',
index=models.Index(fields=['user', '-created_at'], name='products_pr_user_id_e32ca9_idx'),
),
migrations.AddIndex(
model_name='configurableproductoption',
index=models.Index(fields=['parent'], name='products_co_parent__36761a_idx'),
@@ -591,6 +638,10 @@ class Migration(migrations.Migration):
model_name='productkitphoto',
index=models.Index(fields=['quality_warning', 'kit'], name='products_pr_quality_867664_idx'),
),
migrations.AddConstraint(
model_name='productkitphoto',
constraint=models.UniqueConstraint(condition=models.Q(('is_main', True)), fields=('kit',), name='unique_main_photo_per_kit'),
),
migrations.AddIndex(
model_name='productphoto',
index=models.Index(fields=['quality_level'], name='products_pr_quality_d8f85c_idx'),
@@ -603,6 +654,10 @@ class Migration(migrations.Migration):
model_name='productphoto',
index=models.Index(fields=['quality_warning', 'product'], name='products_pr_quality_6e8b51_idx'),
),
migrations.AddConstraint(
model_name='productphoto',
constraint=models.UniqueConstraint(condition=models.Q(('is_main', True)), fields=('product',), name='unique_main_photo_per_product'),
),
migrations.AddIndex(
model_name='productkit',
index=models.Index(fields=['is_temporary'], name='products_pr_is_temp_e407a2_idx'),

View File

@@ -1,45 +0,0 @@
# Generated by Django 5.0.10 on 2026-01-06 03:40
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='ProductImportJob',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_id', models.CharField(max_length=255, unique=True, verbose_name='ID задачи Celery')),
('file_name', models.CharField(max_length=255, verbose_name='Имя файла')),
('file_path', models.CharField(help_text='Временный путь для обработки', max_length=500, verbose_name='Путь к файлу')),
('update_existing', models.BooleanField(default=False, verbose_name='Обновлять существующие')),
('status', models.CharField(choices=[('pending', 'Ожидает'), ('processing', 'Обрабатывается'), ('completed', 'Завершён'), ('failed', 'Ошибка')], db_index=True, default='pending', max_length=20, verbose_name='Статус')),
('total_rows', models.IntegerField(default=0, verbose_name='Всего строк')),
('processed_rows', models.IntegerField(default=0, verbose_name='Обработано строк')),
('created_count', models.IntegerField(default=0, verbose_name='Создано товаров')),
('updated_count', models.IntegerField(default=0, verbose_name='Обновлено товаров')),
('skipped_count', models.IntegerField(default=0, verbose_name='Пропущено')),
('errors_count', models.IntegerField(default=0, verbose_name='Ошибок')),
('errors_json', models.JSONField(blank=True, default=list, verbose_name='Детали ошибок')),
('error_message', models.TextField(blank=True, verbose_name='Сообщение об ошибке')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='Завершено')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_import_jobs', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
],
options={
'verbose_name': 'Задача импорта товаров',
'verbose_name_plural': 'Задачи импорта товаров',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['task_id'], name='products_pr_task_id_d8dc9f_idx'), models.Index(fields=['status', '-created_at'], name='products_pr_status_326f2c_idx'), models.Index(fields=['user', '-created_at'], name='products_pr_user_id_e32ca9_idx')],
},
),
]

View File

@@ -1,52 +0,0 @@
# Generated by Django 5.0.10 on 2026-01-06 05:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('products', '0002_productimportjob'),
]
operations = [
migrations.AlterModelOptions(
name='productcategoryphoto',
options={'ordering': ['-is_main', 'order', '-created_at'], 'verbose_name': 'Фото категории', 'verbose_name_plural': 'Фото категорий'},
),
migrations.AlterModelOptions(
name='productkitphoto',
options={'ordering': ['-is_main', 'order', '-created_at'], 'verbose_name': 'Фото комплекта', 'verbose_name_plural': 'Фото комплектов'},
),
migrations.AlterModelOptions(
name='productphoto',
options={'ordering': ['-is_main', 'order', '-created_at'], 'verbose_name': 'Фото товара', 'verbose_name_plural': 'Фото товаров'},
),
migrations.AddField(
model_name='productcategoryphoto',
name='is_main',
field=models.BooleanField(db_index=True, default=False, help_text='Главное фото отображается в карточках, каталоге и превью. Может быть только одно.', verbose_name='Главное фото'),
),
migrations.AddField(
model_name='productkitphoto',
name='is_main',
field=models.BooleanField(db_index=True, default=False, help_text='Главное фото отображается в карточках, каталоге и превью. Может быть только одно.', verbose_name='Главное фото'),
),
migrations.AddField(
model_name='productphoto',
name='is_main',
field=models.BooleanField(db_index=True, default=False, help_text='Главное фото отображается в карточках, каталоге и превью. Может быть только одно.', verbose_name='Главное фото'),
),
migrations.AddConstraint(
model_name='productcategoryphoto',
constraint=models.UniqueConstraint(condition=models.Q(('is_main', True)), fields=('category',), name='unique_main_photo_per_category'),
),
migrations.AddConstraint(
model_name='productkitphoto',
constraint=models.UniqueConstraint(condition=models.Q(('is_main', True)), fields=('kit',), name='unique_main_photo_per_kit'),
),
migrations.AddConstraint(
model_name='productphoto',
constraint=models.UniqueConstraint(condition=models.Q(('is_main', True)), fields=('product',), name='unique_main_photo_per_product'),
),
]

View File

@@ -1,68 +0,0 @@
# Generated by Django 5.0.10 on 2026-01-06 05:03
from django.db import migrations
def set_main_photo_from_order(apps, schema_editor):
"""
Data migration: устанавливает is_main=True для фото с order=0.
Для каждой сущности (product, kit, category) находит фото с order=0
и устанавливает ему is_main=True.
Если у сущности нет фото с order=0, устанавливает is_main=True для первого фото.
"""
ProductPhoto = apps.get_model('products', 'ProductPhoto')
ProductKitPhoto = apps.get_model('products', 'ProductKitPhoto')
ProductCategoryPhoto = apps.get_model('products', 'ProductCategoryPhoto')
Product = apps.get_model('products', 'Product')
ProductKit = apps.get_model('products', 'ProductKit')
ProductCategory = apps.get_model('products', 'ProductCategory')
# Обрабатываем ProductPhoto
for product in Product.objects.all():
photos = ProductPhoto.objects.filter(product=product).order_by('order', '-created_at')
if photos.exists():
main_photo = photos.filter(order=0).first() or photos.first()
main_photo.is_main = True
main_photo.save(update_fields=['is_main'])
# Обрабатываем ProductKitPhoto
for kit in ProductKit.objects.all():
photos = ProductKitPhoto.objects.filter(kit=kit).order_by('order', '-created_at')
if photos.exists():
main_photo = photos.filter(order=0).first() or photos.first()
main_photo.is_main = True
main_photo.save(update_fields=['is_main'])
# Обрабатываем ProductCategoryPhoto
for category in ProductCategory.objects.all():
photos = ProductCategoryPhoto.objects.filter(category=category).order_by('order', '-created_at')
if photos.exists():
main_photo = photos.filter(order=0).first() or photos.first()
main_photo.is_main = True
main_photo.save(update_fields=['is_main'])
def reverse_main_photo(apps, schema_editor):
"""
Reverse migration: сбрасывает is_main в False для всех фото.
"""
ProductPhoto = apps.get_model('products', 'ProductPhoto')
ProductKitPhoto = apps.get_model('products', 'ProductKitPhoto')
ProductCategoryPhoto = apps.get_model('products', 'ProductCategoryPhoto')
ProductPhoto.objects.filter(is_main=True).update(is_main=False)
ProductKitPhoto.objects.filter(is_main=True).update(is_main=False)
ProductCategoryPhoto.objects.filter(is_main=True).update(is_main=False)
class Migration(migrations.Migration):
dependencies = [
('products', '0003_alter_productcategoryphoto_options_and_more'),
]
operations = [
migrations.RunPython(set_main_photo_from_order, reverse_main_photo),
]