-
-{% endblock %}
+
+
diff --git a/myproject/accounts/views.py b/myproject/accounts/views.py
index 6873c5f..2a949e2 100644
--- a/myproject/accounts/views.py
+++ b/myproject/accounts/views.py
@@ -61,7 +61,7 @@ def login_view(request):
def logout_view(request):
logout(request)
- return redirect('index')
+ return redirect('/')
@login_required
@@ -142,7 +142,7 @@ def password_reset_confirm(request, token):
user = CustomUser.objects.get(password_reset_token=token)
except CustomUser.DoesNotExist:
messages.error(request, 'Ссылка для восстановления пароля недействительна.')
- return redirect('index')
+ return redirect('/')
if request.method == 'POST':
password1 = request.POST.get('password1')
@@ -178,7 +178,7 @@ def password_setup_confirm(request, token):
)
except TenantRegistration.DoesNotExist:
messages.error(request, 'Ссылка для настройки пароля недействительна.')
- return redirect('index')
+ return redirect('/')
# Проверить истечение токена (7 дней)
if registration.password_setup_token_created_at:
@@ -188,24 +188,30 @@ def password_setup_confirm(request, token):
request,
'Ссылка для настройки пароля истекла. Пожалуйста, свяжитесь с поддержкой.'
)
- return redirect('index')
+ return redirect('/')
# Получить тенант и пользователя-владельца
from django.db import connection
tenant = registration.tenant
if not tenant:
messages.error(request, 'Тенант не найден.')
- return redirect('index')
+ return redirect('/')
# Переключиться на схему тенанта чтобы найти владельца
connection.set_tenant(tenant)
- User = get_user_model()
- try:
- owner = User.objects.get(email=registration.owner_email)
- except User.DoesNotExist:
- connection.set_schema_to_public()
- messages.error(request, 'Пользователь не найден.')
- return redirect('index')
+ from accounts.models import CustomUser
+
+ # Создаём пользователя если он не существует (для случаев когда активация прошла без создания пользователя)
+ owner, created = CustomUser.objects.get_or_create(
+ email=registration.owner_email,
+ defaults={
+ 'name': registration.owner_name,
+ 'is_active': False,
+ }
+ )
+ if created:
+ owner.is_email_confirmed = True
+ owner.save()
# Обработать POST - установить пароль
if request.method == 'POST':
diff --git a/myproject/customers/migrations/0001_initial.py b/myproject/customers/migrations/0001_initial.py
index 020d30a..c5f5d19 100644
--- a/myproject/customers/migrations/0001_initial.py
+++ b/myproject/customers/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# 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.db.models.deletion
import phonenumber_field.modelfields
diff --git a/myproject/customers/migrations/0002_initial.py b/myproject/customers/migrations/0002_initial.py
index b6cc60d..bfe63e3 100644
--- a/myproject/customers/migrations/0002_initial.py
+++ b/myproject/customers/migrations/0002_initial.py
@@ -1,4 +1,4 @@
-# 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.db.models.deletion
from django.db import migrations, models
diff --git a/myproject/discounts/migrations/0001_initial.py b/myproject/discounts/migrations/0001_initial.py
index e48157c..a4b5177 100644
--- a/myproject/discounts/migrations/0001_initial.py
+++ b/myproject/discounts/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.10 on 2026-01-10 21:07
+# Generated by Django 5.0.10 on 2026-01-14 07:04
import django.db.models.deletion
from django.db import migrations, models
@@ -9,10 +9,10 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- ('accounts', '0002_remove_customuser_first_name_and_more'),
+ ('accounts', '0001_initial'),
('customers', '0002_initial'),
('orders', '0002_initial'),
- ('products', '0002_alter_configurableproduct_archived_by_and_more'),
+ ('products', '0001_initial'),
]
operations = [
@@ -34,6 +34,7 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('min_order_amount', models.DecimalField(blank=True, decimal_places=2, help_text='Скидка применяется только если сумма заказа >= этого значения', max_digits=10, null=True, verbose_name='Мин. сумма заказа')),
('is_auto', models.BooleanField(default=False, help_text='Применяется автоматически при выполнении условий', verbose_name='Автоматическая')),
+ ('combine_mode', models.CharField(choices=[('stack', 'Складывать (суммировать)'), ('max_only', 'Только максимум'), ('exclusive', 'Исключающая (отменяет остальные)')], default='max_only', help_text='stack = суммировать с другими, max_only = применить максимальную, exclusive = отменить остальные', max_length=20, verbose_name='Режим объединения')),
('categories', models.ManyToManyField(blank=True, related_name='discounts', to='products.productcategory', verbose_name='Категории')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_discounts', to='accounts.customuser', verbose_name='Создал')),
('excluded_products', models.ManyToManyField(blank=True, related_name='excluded_from_discounts', to='products.product', verbose_name='Исключенные товары')),
diff --git a/myproject/discounts/migrations/0002_add_combine_mode.py b/myproject/discounts/migrations/0002_add_combine_mode.py
deleted file mode 100644
index 5c9e161..0000000
--- a/myproject/discounts/migrations/0002_add_combine_mode.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-10 23:55
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('discounts', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='discount',
- name='combine_mode',
- field=models.CharField(choices=[('stack', 'Складывать (суммировать)'), ('max_only', 'Только максимум'), ('exclusive', 'Исключающая (отменяет остальные)')], default='max_only', help_text='stack = суммировать с другими, max_only = применить максимальную, exclusive = отменить остальные', max_length=20, verbose_name='Режим объединения'),
- ),
- ]
diff --git a/myproject/integrations/migrations/0001_add_integration_models.py b/myproject/integrations/migrations/0001_initial.py
similarity index 73%
rename from myproject/integrations/migrations/0001_add_integration_models.py
rename to myproject/integrations/migrations/0001_initial.py
index f9b1932..b2fa599 100644
--- a/myproject/integrations/migrations/0001_add_integration_models.py
+++ b/myproject/integrations/migrations/0001_initial.py
@@ -1,5 +1,6 @@
-# Generated by Django 5.0.10 on 2026-01-11 21:19
+# Generated by Django 5.0.10 on 2026-01-14 07:04
+import django.db.models.deletion
import integrations.fields
from django.db import migrations, models
@@ -9,6 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
+ ('products', '0001_initial'),
]
operations = [
@@ -54,4 +56,22 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'WooCommerce',
},
),
+ migrations.CreateModel(
+ name='IntegrationCategoryMapping',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('integration_type', models.CharField(choices=[('recommerce', 'Recommerce'), ('woocommerce', 'WooCommerce')], db_index=True, max_length=20, verbose_name='Интеграция')),
+ ('external_category_sku', models.CharField(help_text='SKU или ID категории на внешней площадке', max_length=100, verbose_name='Артикул категории во внешней системе')),
+ ('external_category_name', models.CharField(blank=True, help_text='Для справки, не обязательно', max_length=200, verbose_name='Название категории во внешней системе')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
+ ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='integration_mappings', to='products.productcategory', verbose_name='Категория')),
+ ],
+ options={
+ 'verbose_name': 'Маппинг категории',
+ 'verbose_name_plural': 'Маппинги категорий',
+ 'indexes': [models.Index(fields=['integration_type', 'external_category_sku'], name='integration_integra_450473_idx')],
+ 'unique_together': {('category', 'integration_type')},
+ },
+ ),
]
diff --git a/myproject/integrations/migrations/0002_integrationcategorymapping.py b/myproject/integrations/migrations/0002_integrationcategorymapping.py
deleted file mode 100644
index 5893598..0000000
--- a/myproject/integrations/migrations/0002_integrationcategorymapping.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-13 21:08
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('integrations', '0001_add_integration_models'),
- ('products', '0004_configurableproduct_primary_category_and_more'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='IntegrationCategoryMapping',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('integration_type', models.CharField(choices=[('recommerce', 'Recommerce'), ('woocommerce', 'WooCommerce')], db_index=True, max_length=20, verbose_name='Интеграция')),
- ('external_category_sku', models.CharField(help_text='SKU или ID категории на внешней площадке', max_length=100, verbose_name='Артикул категории во внешней системе')),
- ('external_category_name', models.CharField(blank=True, help_text='Для справки, не обязательно', max_length=200, verbose_name='Название категории во внешней системе')),
- ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')),
- ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')),
- ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='integration_mappings', to='products.productcategory', verbose_name='Категория')),
- ],
- options={
- 'verbose_name': 'Маппинг категории',
- 'verbose_name_plural': 'Маппинги категорий',
- 'indexes': [models.Index(fields=['integration_type', 'external_category_sku'], name='integration_integra_450473_idx')],
- 'unique_together': {('category', 'integration_type')},
- },
- ),
- ]
diff --git a/myproject/inventory/migrations/0001_initial.py b/myproject/inventory/migrations/0001_initial.py
index 81891f8..7711b90 100644
--- a/myproject/inventory/migrations/0001_initial.py
+++ b/myproject/inventory/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# 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.db.models.deletion
import phonenumber_field.modelfields
diff --git a/myproject/inventory/migrations/0002_initial.py b/myproject/inventory/migrations/0002_initial.py
index 623af8b..7c7667d 100644
--- a/myproject/inventory/migrations/0002_initial.py
+++ b/myproject/inventory/migrations/0002_initial.py
@@ -1,4 +1,4 @@
-# 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.db.models.deletion
from django.db import migrations, models
diff --git a/myproject/orders/migrations/0001_initial.py b/myproject/orders/migrations/0001_initial.py
index ed7c1fa..343564f 100644
--- a/myproject/orders/migrations/0001_initial.py
+++ b/myproject/orders/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# 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.db.models.deletion
import phonenumber_field.modelfields
diff --git a/myproject/orders/migrations/0002_initial.py b/myproject/orders/migrations/0002_initial.py
index c9108f4..9da36be 100644
--- a/myproject/orders/migrations/0002_initial.py
+++ b/myproject/orders/migrations/0002_initial.py
@@ -1,4 +1,4 @@
-# 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.db.models.deletion
from django.db import migrations, models
diff --git a/myproject/orders/migrations/0003_order_applied_discount_order_applied_promo_code_and_more.py b/myproject/orders/migrations/0003_order_applied_discount_order_applied_promo_code_and_more.py
deleted file mode 100644
index 3bdc4ab..0000000
--- a/myproject/orders/migrations/0003_order_applied_discount_order_applied_promo_code_and_more.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-10 21:12
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('discounts', '0001_initial'),
- ('orders', '0002_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='order',
- name='applied_discount',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='discounts.discount', verbose_name='Примененная скидка'),
- ),
- migrations.AddField(
- model_name='order',
- name='applied_promo_code',
- field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Использованный промокод'),
- ),
- migrations.AddField(
- model_name='order',
- name='discount_amount',
- field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Сумма скидки'),
- ),
- migrations.AddField(
- model_name='orderitem',
- name='applied_discount',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items', to='discounts.discount', verbose_name='Скидка на позицию'),
- ),
- migrations.AddField(
- model_name='orderitem',
- name='discount_amount',
- field=models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Сумма скидки'),
- ),
- ]
diff --git a/myproject/orders/migrations/0004_remove_old_discount_fields.py b/myproject/orders/migrations/0004_remove_old_discount_fields.py
deleted file mode 100644
index b4233c3..0000000
--- a/myproject/orders/migrations/0004_remove_old_discount_fields.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-11 10:39
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('orders', '0003_order_applied_discount_order_applied_promo_code_and_more'),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name='order',
- name='applied_discount',
- ),
- migrations.RemoveField(
- model_name='order',
- name='applied_promo_code',
- ),
- migrations.RemoveField(
- model_name='order',
- name='discount_amount',
- ),
- migrations.RemoveField(
- model_name='orderitem',
- name='applied_discount',
- ),
- migrations.RemoveField(
- model_name='orderitem',
- name='discount_amount',
- ),
- ]
diff --git a/myproject/platform_admin/migrations/0001_initial.py b/myproject/platform_admin/migrations/0001_initial.py
index fd3f6f4..2eee819 100644
--- a/myproject/platform_admin/migrations/0001_initial.py
+++ b/myproject/platform_admin/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.10 on 2026-01-08 15:56
+# Generated by Django 5.0.10 on 2026-01-14 07:03
import django.utils.timezone
from django.db import migrations, models
diff --git a/myproject/products/migrations/0001_initial.py b/myproject/products/migrations/0001_initial.py
index 68d1d4c..11b6f3b 100644
--- a/myproject/products/migrations/0001_initial.py
+++ b/myproject/products/migrations/0001_initial.py
@@ -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='Витрина')),
],
diff --git a/myproject/products/migrations/0002_alter_configurableproduct_archived_by_and_more.py b/myproject/products/migrations/0002_alter_configurableproduct_archived_by_and_more.py
deleted file mode 100644
index b799a2c..0000000
--- a/myproject/products/migrations/0002_alter_configurableproduct_archived_by_and_more.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-09 20:42
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-def clear_invalid_user_references(apps, schema_editor):
- """
- Очистить ссылки на PlatformAdmin в полях archived_by/deleted_by/user,
- т.к. они теперь ссылаются на CustomUser.
-
- Устанавливаем NULL для всех существующих ссылок на пользователей,
- чтобы избежать ошибок referential integrity при изменении типа ForeignKey.
- """
- ProductCategory = apps.get_model('products', 'ProductCategory')
- Product = apps.get_model('products', 'Product')
- ProductKit = apps.get_model('products', 'ProductKit')
- ConfigurableProduct = apps.get_model('products', 'ConfigurableProduct')
- ProductImportJob = apps.get_model('products', 'ProductImportJob')
-
- # Очищаем все существующие ссылки (ставим NULL)
- ProductCategory.objects.all().update(deleted_by=None)
- Product.objects.all().update(archived_by=None)
- ProductKit.objects.all().update(archived_by=None)
- ConfigurableProduct.objects.all().update(archived_by=None)
-
- # ProductImportJob.user не может быть NULL (CASCADE), поэтому удаляем записи
- # если они были созданы PlatformAdmin (что маловероятно)
- ProductImportJob.objects.all().delete()
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('accounts', '0001_initial'),
- ('products', '0001_initial'),
- ]
-
- operations = [
- # Сначала очищаем некорректные ссылки
- migrations.RunPython(clear_invalid_user_references, reverse_code=migrations.RunPython.noop),
- # Затем изменяем типы полей
- migrations.AlterField(
- model_name='configurableproduct',
- name='archived_by',
- field=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='Архивировано пользователем'),
- ),
- migrations.AlterField(
- model_name='product',
- name='archived_by',
- field=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='Архивировано пользователем'),
- ),
- migrations.AlterField(
- model_name='productcategory',
- name='deleted_by',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deleted_categories', to='accounts.customuser', verbose_name='Удалена пользователем'),
- ),
- migrations.AlterField(
- model_name='productimportjob',
- name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='product_import_jobs', to='accounts.customuser', verbose_name='Пользователь'),
- ),
- migrations.AlterField(
- model_name='productkit',
- name='archived_by',
- field=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='Архивировано пользователем'),
- ),
- ]
diff --git a/myproject/products/migrations/0003_add_marketing_flags.py b/myproject/products/migrations/0003_add_marketing_flags.py
deleted file mode 100644
index be5223d..0000000
--- a/myproject/products/migrations/0003_add_marketing_flags.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-12 21:26
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('products', '0002_alter_configurableproduct_archived_by_and_more'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='configurableproduct',
- name='is_new',
- field=models.BooleanField(default=False, help_text='Отображать как новый товар', verbose_name='Новинка'),
- ),
- migrations.AddField(
- model_name='configurableproduct',
- name='is_popular',
- field=models.BooleanField(default=False, help_text='Отображать как популярный товар', verbose_name='Популярный'),
- ),
- migrations.AddField(
- model_name='configurableproduct',
- name='is_special',
- field=models.BooleanField(default=False, help_text='Отображать как спецпредложение (акция)', verbose_name='Спецпредложение'),
- ),
- migrations.AddField(
- model_name='product',
- name='is_new',
- field=models.BooleanField(default=False, help_text='Отображать как новый товар', verbose_name='Новинка'),
- ),
- migrations.AddField(
- model_name='product',
- name='is_popular',
- field=models.BooleanField(default=False, help_text='Отображать как популярный товар', verbose_name='Популярный'),
- ),
- migrations.AddField(
- model_name='product',
- name='is_special',
- field=models.BooleanField(default=False, help_text='Отображать как спецпредложение (акция)', verbose_name='Спецпредложение'),
- ),
- migrations.AddField(
- model_name='productkit',
- name='is_new',
- field=models.BooleanField(default=False, help_text='Отображать как новый товар', verbose_name='Новинка'),
- ),
- migrations.AddField(
- model_name='productkit',
- name='is_popular',
- field=models.BooleanField(default=False, help_text='Отображать как популярный товар', verbose_name='Популярный'),
- ),
- migrations.AddField(
- model_name='productkit',
- name='is_special',
- field=models.BooleanField(default=False, help_text='Отображать как спецпредложение (акция)', verbose_name='Спецпредложение'),
- ),
- ]
diff --git a/myproject/products/migrations/0004_configurableproduct_primary_category_and_more.py b/myproject/products/migrations/0004_configurableproduct_primary_category_and_more.py
deleted file mode 100644
index dfad758..0000000
--- a/myproject/products/migrations/0004_configurableproduct_primary_category_and_more.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Generated by Django 5.0.10 on 2026-01-13 21:08
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('products', '0003_add_marketing_flags'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='configurableproduct',
- name='primary_category',
- field=models.ForeignKey(blank=True, help_text='Используется для интеграций с внешними площадками', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_%(class)ss', to='products.productcategory', verbose_name='Основная категория'),
- ),
- migrations.AddField(
- model_name='product',
- name='primary_category',
- field=models.ForeignKey(blank=True, help_text='Используется для интеграций с внешними площадками', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_%(class)ss', to='products.productcategory', verbose_name='Основная категория'),
- ),
- migrations.AddField(
- model_name='productkit',
- name='primary_category',
- field=models.ForeignKey(blank=True, help_text='Используется для интеграций с внешними площадками', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_%(class)ss', to='products.productcategory', verbose_name='Основная категория'),
- ),
- ]
diff --git a/myproject/products/migrations/0005_rename_primary_category_to_external_category.py b/myproject/products/migrations/0005_rename_primary_category_to_external_category.py
deleted file mode 100644
index ad4a8a2..0000000
--- a/myproject/products/migrations/0005_rename_primary_category_to_external_category.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Generated migration to rename primary_category to external_category
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('products', '0004_configurableproduct_primary_category_and_more'),
- ]
-
- operations = [
- # Rename primary_category to external_category for Product
- migrations.RenameField(
- model_name='product',
- old_name='primary_category',
- new_name='external_category',
- ),
- # Update related_name for Product
- migrations.AlterField(
- model_name='product',
- name='external_category',
- field=models.ForeignKey(
- blank=True,
- help_text='Категория для интеграций с внешними площадками (Recommerce, WooCommerce и др.)',
- null=True,
- on_delete=models.SET_NULL,
- related_name='external_products',
- to='products.productcategory',
- verbose_name='Внешняя категория'
- ),
- ),
- # Rename primary_category to external_category for ProductKit
- migrations.RenameField(
- model_name='productkit',
- old_name='primary_category',
- new_name='external_category',
- ),
- # Update related_name for ProductKit
- migrations.AlterField(
- model_name='productkit',
- name='external_category',
- field=models.ForeignKey(
- blank=True,
- help_text='Категория для интеграций с внешними площадками (Recommerce, WooCommerce и др.)',
- null=True,
- on_delete=models.SET_NULL,
- related_name='external_productkits',
- to='products.productcategory',
- verbose_name='Внешняя категория'
- ),
- ),
- # Rename primary_category to external_category for ConfigurableProduct
- migrations.RenameField(
- model_name='configurableproduct',
- old_name='primary_category',
- new_name='external_category',
- ),
- # Update related_name for ConfigurableProduct
- migrations.AlterField(
- model_name='configurableproduct',
- name='external_category',
- field=models.ForeignKey(
- blank=True,
- help_text='Категория для интеграций с внешними площадками (Recommerce, WooCommerce и др.)',
- null=True,
- on_delete=models.SET_NULL,
- related_name='external_configurableproducts',
- to='products.productcategory',
- verbose_name='Внешняя категория'
- ),
- ),
- ]
diff --git a/myproject/products/templates/products/products_list.html b/myproject/products/templates/products/products_list.html
index 23703f4..9106a66 100644
--- a/myproject/products/templates/products/products_list.html
+++ b/myproject/products/templates/products/products_list.html
@@ -237,11 +237,13 @@
+ {% if recommerce_integration_enabled %}
Синхронизация с Recommerce
+ {% endif %}