Обновления миграций в products и удаление старых миграций
This commit is contained in:
@@ -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.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
@@ -307,6 +307,7 @@ class Migration(migrations.Migration):
|
|||||||
name='ProductCategoryPhoto',
|
name='ProductCategoryPhoto',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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='Порядок')),
|
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, 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='Оригинальное фото')),
|
('image', models.ImageField(upload_to=products.models.photos.get_category_photo_upload_path, verbose_name='Оригинальное фото')),
|
||||||
@@ -317,7 +318,35 @@ class Migration(migrations.Migration):
|
|||||||
options={
|
options={
|
||||||
'verbose_name': 'Фото категории',
|
'verbose_name': 'Фото категории',
|
||||||
'verbose_name_plural': 'Фото категорий',
|
'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(
|
migrations.CreateModel(
|
||||||
@@ -368,6 +397,7 @@ class Migration(migrations.Migration):
|
|||||||
name='ProductKitPhoto',
|
name='ProductKitPhoto',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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='Порядок')),
|
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, 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='Оригинальное фото')),
|
('image', models.ImageField(upload_to=products.models.photos.get_kit_photo_upload_path, verbose_name='Оригинальное фото')),
|
||||||
@@ -378,13 +408,14 @@ class Migration(migrations.Migration):
|
|||||||
options={
|
options={
|
||||||
'verbose_name': 'Фото комплекта',
|
'verbose_name': 'Фото комплекта',
|
||||||
'verbose_name_plural': 'Фото комплектов',
|
'verbose_name_plural': 'Фото комплектов',
|
||||||
'ordering': ['order', '-created_at'],
|
'ordering': ['-is_main', 'order', '-created_at'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProductPhoto',
|
name='ProductPhoto',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('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='Порядок')),
|
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, 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='Оригинальное фото')),
|
('image', models.ImageField(upload_to=products.models.photos.get_product_photo_upload_path, verbose_name='Оригинальное фото')),
|
||||||
@@ -395,7 +426,7 @@ class Migration(migrations.Migration):
|
|||||||
options={
|
options={
|
||||||
'verbose_name': 'Фото товара',
|
'verbose_name': 'Фото товара',
|
||||||
'verbose_name_plural': 'Фото товаров',
|
'verbose_name_plural': 'Фото товаров',
|
||||||
'ordering': ['order', '-created_at'],
|
'ordering': ['-is_main', 'order', '-created_at'],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -539,6 +570,22 @@ class Migration(migrations.Migration):
|
|||||||
model_name='productcategoryphoto',
|
model_name='productcategoryphoto',
|
||||||
index=models.Index(fields=['quality_warning', 'category'], name='products_pr_quality_fc505a_idx'),
|
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(
|
migrations.AddIndex(
|
||||||
model_name='configurableproductoption',
|
model_name='configurableproductoption',
|
||||||
index=models.Index(fields=['parent'], name='products_co_parent__36761a_idx'),
|
index=models.Index(fields=['parent'], name='products_co_parent__36761a_idx'),
|
||||||
@@ -591,6 +638,10 @@ class Migration(migrations.Migration):
|
|||||||
model_name='productkitphoto',
|
model_name='productkitphoto',
|
||||||
index=models.Index(fields=['quality_warning', 'kit'], name='products_pr_quality_867664_idx'),
|
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(
|
migrations.AddIndex(
|
||||||
model_name='productphoto',
|
model_name='productphoto',
|
||||||
index=models.Index(fields=['quality_level'], name='products_pr_quality_d8f85c_idx'),
|
index=models.Index(fields=['quality_level'], name='products_pr_quality_d8f85c_idx'),
|
||||||
@@ -603,6 +654,10 @@ class Migration(migrations.Migration):
|
|||||||
model_name='productphoto',
|
model_name='productphoto',
|
||||||
index=models.Index(fields=['quality_warning', 'product'], name='products_pr_quality_6e8b51_idx'),
|
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(
|
migrations.AddIndex(
|
||||||
model_name='productkit',
|
model_name='productkit',
|
||||||
index=models.Index(fields=['is_temporary'], name='products_pr_is_temp_e407a2_idx'),
|
index=models.Index(fields=['is_temporary'], name='products_pr_is_temp_e407a2_idx'),
|
||||||
|
|||||||
@@ -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')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -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'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -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),
|
|
||||||
]
|
|
||||||
Reference in New Issue
Block a user