refactor(db): консолидация миграций и рефакторинг кода

Объединены изменения из промежуточных миграций в начальные миграции для упрощения истории базы данных.
Удалены миграции: accounts/0002, discounts/0002, orders/0003-0004, products/0002-0005, user_roles/0002, system_settings/0001-0002, integrations/0001-0002.
Добавлена автоматическая creation пользователя при установке пароля.
Обновлен UI страницы установки пароля с кастомным стилем.
Добавлен conditional rendering для кнопки синхронизации Recommerce.
Исправлены редиректы с 'index' на '/' в accounts views.
Добавлена проверка request.tenant в navbar и authenticate метод в auth backend.
This commit is contained in:
2026-01-14 16:30:28 +03:00
parent e7672588c6
commit caeb3f80bd
31 changed files with 238 additions and 558 deletions

View File

@@ -1,10 +1,9 @@
# Generated by Django 5.0.10 on 2026-01-08 15:58
# Generated by Django 5.0.10 on 2026-01-14 07:04
import django.core.validators
import django.db.models.deletion
import products.models.photos
from decimal import Decimal
from django.conf import settings
from django.db import migrations, models
@@ -13,9 +12,9 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('accounts', '0001_initial'),
('inventory', '0001_initial'),
('orders', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@@ -112,10 +111,13 @@ class Migration(migrations.Migration):
('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='Статус')),
('is_new', models.BooleanField(default=False, help_text='Отображать как новый товар', verbose_name='Новинка')),
('is_popular', models.BooleanField(default=False, help_text='Отображать как популярный товар', verbose_name='Популярный')),
('is_special', models.BooleanField(default=False, help_text='Отображать как спецпредложение (акция)', 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='Время архивирования')),
('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='Архивировано пользователем')),
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to='accounts.customuser', verbose_name='Архивировано пользователем')),
],
options={
'verbose_name': 'Вариативный товар',
@@ -196,6 +198,9 @@ class Migration(migrations.Migration):
('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='Статус')),
('is_new', models.BooleanField(default=False, help_text='Отображать как новый товар', verbose_name='Новинка')),
('is_popular', models.BooleanField(default=False, help_text='Отображать как популярный товар', verbose_name='Популярный')),
('is_special', models.BooleanField(default=False, help_text='Отображать как спецпредложение (акция)', 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='Время архивирования')),
@@ -206,7 +211,7 @@ class Migration(migrations.Migration):
('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='Архивировано пользователем')),
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to='accounts.customuser', verbose_name='Архивировано пользователем')),
],
options={
'verbose_name': 'Товар',
@@ -290,7 +295,7 @@ class Migration(migrations.Migration):
('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='Удалена пользователем')),
('deleted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_categories', to='accounts.customuser', 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={
@@ -303,6 +308,16 @@ class Migration(migrations.Migration):
name='categories',
field=models.ManyToManyField(blank=True, related_name='products', to='products.productcategory', verbose_name='Категории'),
),
migrations.AddField(
model_name='product',
name='external_category',
field=models.ForeignKey(blank=True, help_text='Категория для интеграций с внешними площадками (Recommerce, WooCommerce и др.)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='external_%(class)ss', to='products.productcategory', verbose_name='Внешняя категория'),
),
migrations.AddField(
model_name='configurableproduct',
name='external_category',
field=models.ForeignKey(blank=True, help_text='Категория для интеграций с внешними площадками (Recommerce, WooCommerce и др.)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='external_%(class)ss', to='products.productcategory', verbose_name='Внешняя категория'),
),
migrations.CreateModel(
name='ProductCategoryPhoto',
fields=[
@@ -341,7 +356,7 @@ class Migration(migrations.Migration):
('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='Пользователь')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_import_jobs', to='accounts.customuser', verbose_name='Пользователь')),
],
options={
'verbose_name': 'Задача импорта товаров',
@@ -359,6 +374,9 @@ class Migration(migrations.Migration):
('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='Статус')),
('is_new', models.BooleanField(default=False, help_text='Отображать как новый товар', verbose_name='Новинка')),
('is_popular', models.BooleanField(default=False, help_text='Отображать как популярный товар', verbose_name='Популярный')),
('is_special', models.BooleanField(default=False, help_text='Отображать как спецпредложение (акция)', 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='Время архивирования')),
@@ -368,8 +386,9 @@ class Migration(migrations.Migration):
('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='Архивировано пользователем')),
('archived_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='archived_%(class)s_set', to='accounts.customuser', verbose_name='Архивировано пользователем')),
('categories', models.ManyToManyField(blank=True, related_name='kits', to='products.productcategory', verbose_name='Категории')),
('external_category', models.ForeignKey(blank=True, help_text='Категория для интеграций с внешними площадками (Recommerce, WooCommerce и др.)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='external_%(class)ss', to='products.productcategory', 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='Заказ')),
('showcase', models.ForeignKey(blank=True, help_text='Витрина, на которой выложен временный комплект', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='temporary_kits', to='inventory.showcase', verbose_name='Витрина')),
],