diff --git a/backend/notifications/migrations/0010_drop_old_tables.py b/backend/notifications/migrations/0010_drop_old_tables.py index 5fd5d1eb..b8f8614f 100644 --- a/backend/notifications/migrations/0010_drop_old_tables.py +++ b/backend/notifications/migrations/0010_drop_old_tables.py @@ -24,4 +24,13 @@ class Migration(migrations.Migration): """, reverse_sql=migrations.RunSQL.noop, ), + # Удаляем модели из состояния Django (state_operations) + # Порядок важен: сначала связанные модели, потом основные + migrations.DeleteModel(name='UserNotificationSettings'), + migrations.DeleteModel(name='NotificationTemplate'), + migrations.DeleteModel(name='TelegramUser'), + migrations.DeleteModel(name='WebPushSubscription'), + migrations.DeleteModel(name='Notification'), + migrations.DeleteModel(name='NotificationType'), + migrations.DeleteModel(name='NotificationCategory'), ] diff --git a/backend/notifications/migrations/0011_create_v2_models.py b/backend/notifications/migrations/0011_create_v2_models.py deleted file mode 100644 index 82a20a31..00000000 --- a/backend/notifications/migrations/0011_create_v2_models.py +++ /dev/null @@ -1,75 +0,0 @@ -# Generated manually for creating v2 models - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('notifications', '0010_drop_old_tables'), - ] - - operations = [ - # Создаем новую модель Notification v2 - migrations.CreateModel( - name='Notification', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('verb', models.CharField(db_index=True, help_text='Действие (liked, commented, mentioned и т.д.)', max_length=255, verbose_name='Действие')), - ('description', models.TextField(blank=True, help_text='Описание уведомления', verbose_name='Описание')), - ('action_url', models.CharField(blank=True, help_text='URL для перехода', max_length=500, verbose_name='URL действия')), - ('data', models.JSONField(blank=True, default=dict, help_text='Дополнительные данные', verbose_name='Данные')), - ('unread', models.BooleanField(db_index=True, default=True, help_text='Непрочитано', verbose_name='Непрочитано')), - ('public', models.BooleanField(db_index=True, default=True, help_text='Публичное уведомление', verbose_name='Публичное')), - ('deleted', models.BooleanField(db_index=True, default=False, help_text='Удалено (мягкое удаление)', verbose_name='Удалено')), - ('emailed', models.BooleanField(default=False, help_text='Отправлено по email', verbose_name='Отправлено email')), - ('timestamp', models.DateTimeField(auto_now_add=True, db_index=True, help_text='Время создания', verbose_name='Время создания')), - ('timestamp_read', models.DateTimeField(blank=True, help_text='Время прочтения', null=True, verbose_name='Время прочтения')), - ('actor_object_id', models.PositiveIntegerField(blank=True, help_text='ID актора', null=True)), - ('action_object_object_id', models.PositiveIntegerField(blank=True, help_text='ID объекта действия', null=True)), - ('target_object_id', models.PositiveIntegerField(blank=True, help_text='ID целевого объекта', null=True)), - ('action_object_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.contenttype')), - ('actor_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.contenttype')), - ('recipient', models.ForeignKey(help_text='Кому адресовано уведомление', on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='Получатель')), - ('target_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.contenttype')), - ], - options={ - 'verbose_name': 'Уведомление', - 'verbose_name_plural': 'Уведомления', - 'db_table': 'notifications_notification_v2', - 'ordering': ['-timestamp'], - 'indexes': [ - models.Index(fields=['recipient', '-timestamp'], name='notificatio_recipie_91dc91_idx'), - models.Index(fields=['recipient', 'unread', '-timestamp'], name='notificatio_recipie_9b2135_idx'), - models.Index(fields=['verb', '-timestamp'], name='notificatio_verb_1060e9_idx'), - models.Index(fields=['-timestamp'], name='notificatio_timesta_e0a5cf_idx'), - ], - }, - ), - - # Создаем модель UserChannelPreferences - migrations.CreateModel( - name='UserChannelPreferences', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('web_enabled', models.BooleanField(default=True, help_text='Включить веб-уведомления', verbose_name='Веб-уведомления')), - ('email_enabled', models.BooleanField(default=True, help_text='Включить email уведомления', verbose_name='Email уведомления')), - ('push_enabled', models.BooleanField(default=True, help_text='Включить push уведомления', verbose_name='Push уведомления')), - ('email_frequency', models.CharField(choices=[('instant', 'Мгновенно'), ('daily', 'Ежедневно'), ('weekly', 'Еженедельно'), ('disabled', 'Отключено')], default='instant', help_text='Частота отправки email', max_length=20, verbose_name='Частота email')), - ('disabled_verbs', models.JSONField(blank=True, default=list, help_text='Список отключенных типов уведомлений (verb)', verbose_name='Отключенные типы')), - ('dnd_enabled', models.BooleanField(default=False, help_text='Включить режим "Не беспокоить"', verbose_name='Режим "Не беспокоить"')), - ('dnd_start_time', models.TimeField(blank=True, null=True, help_text='Начало режима "Не беспокоить"', verbose_name='Начало DND')), - ('dnd_end_time', models.TimeField(blank=True, null=True, help_text='Конец режима "Не беспокоить"', verbose_name='Конец DND')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='channel_preferences', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')), - ], - options={ - 'verbose_name': 'Настройки каналов', - 'verbose_name_plural': 'Настройки каналов', - 'db_table': 'notifications_userchannelpreferences', - }, - ), - ] diff --git a/backend/notifications/migrations/0011_initial.py b/backend/notifications/migrations/0011_initial.py new file mode 100644 index 00000000..d7ebb1f1 --- /dev/null +++ b/backend/notifications/migrations/0011_initial.py @@ -0,0 +1,70 @@ +# Generated by Django 5.2.4 on 2026-03-10 05:02 + +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('notifications', '0010_drop_old_tables'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserChannelPreferences', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('web_enabled', models.BooleanField(default=True, help_text='Показывать в интерфейсе сайта', verbose_name='Веб уведомления')), + ('email_enabled', models.BooleanField(default=False, verbose_name='Email уведомления')), + ('push_enabled', models.BooleanField(default=False, help_text='Web Push для браузера', verbose_name='Push уведомления')), + ('email_frequency', models.CharField(choices=[('instant', 'Мгновенно'), ('daily', 'Ежедневный дайджест'), ('weekly', 'Еженедельный дайджест'), ('never', 'Никогда')], default='instant', max_length=20, verbose_name='Частота email')), + ('dnd_enabled', models.BooleanField(default=False, help_text='Отключить все уведомления в определенное время', verbose_name='Режим "Не беспокоить"')), + ('dnd_start_time', models.TimeField(blank=True, help_text='Например, 22:00', null=True, verbose_name='Начало тихого режима')), + ('dnd_end_time', models.TimeField(blank=True, help_text='Например, 08:00', null=True, verbose_name='Конец тихого режима')), + ('disabled_verbs', models.JSONField(blank=True, default=list, help_text='Список verb для которых не показывать уведомления. Например: ["liked", "followed"]', verbose_name='Отключенные типы')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='channel_preferences', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')), + ], + options={ + 'verbose_name': 'Настройки каналов пользователя', + 'verbose_name_plural': 'Настройки каналов пользователей', + 'db_table': 'notifications_userchannelpreferences', + }, + ), + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('actor_object_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='ID актора')), + ('verb', models.CharField(db_index=True, default='notification', help_text='Например: liked, commented, approved, mentioned', max_length=255, verbose_name='Действие')), + ('action_object_object_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='ID объекта действия')), + ('target_object_id', models.CharField(blank=True, max_length=255, null=True, verbose_name='ID целевого объекта')), + ('description', models.TextField(blank=True, help_text='Человекочитаемое описание уведомления', null=True, verbose_name='Описание')), + ('action_url', models.CharField(blank=True, help_text='Куда перейти при клике на уведомление', max_length=500, null=True, verbose_name='URL действия')), + ('data', models.JSONField(blank=True, default=dict, help_text='Любые метаданные для рендеринга или логики', verbose_name='Дополнительные данные')), + ('unread', models.BooleanField(db_index=True, default=True, verbose_name='Непрочитано')), + ('timestamp_read', models.DateTimeField(blank=True, null=True, verbose_name='Время прочтения')), + ('public', models.BooleanField(db_index=True, default=True, help_text='Можно показывать другим пользователям', verbose_name='Публичное')), + ('deleted', models.BooleanField(db_index=True, default=False, help_text='Мягкое удаление', verbose_name='Удалено')), + ('emailed', models.BooleanField(db_index=True, default=False, verbose_name='Отправлено на email')), + ('timestamp', models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Создано')), + ('action_object_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.contenttype', verbose_name='Тип объекта действия')), + ('actor_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.contenttype', verbose_name='Тип актора')), + ('recipient', models.ForeignKey(help_text='Кому адресовано уведомление', on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='Получатель')), + ('target_content_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.contenttype', verbose_name='Тип целевого объекта')), + ], + options={ + 'verbose_name': 'Уведомление', + 'verbose_name_plural': 'Уведомления', + 'db_table': 'notifications_notification_v2', + 'ordering': ['-timestamp'], + 'indexes': [models.Index(fields=['recipient', '-timestamp'], name='notificatio_recipie_91dc91_idx'), models.Index(fields=['recipient', 'unread', '-timestamp'], name='notificatio_recipie_9b2135_idx'), models.Index(fields=['verb', '-timestamp'], name='notificatio_verb_1060e9_idx'), models.Index(fields=['-timestamp'], name='notificatio_timesta_e0a5cf_idx')], + }, + ), + ] diff --git a/backend/notifications/models.py b/backend/notifications/models.py index 6ae29eb1..7e4f02bc 100644 --- a/backend/notifications/models.py +++ b/backend/notifications/models.py @@ -117,6 +117,7 @@ class Notification(models.Model): # Что произошло verb = models.CharField( max_length=255, + default='notification', verbose_name='Действие', help_text='Например: liked, commented, approved, mentioned', db_index=True