From 72b67d5c6a1e356be596041d6879d324937bf5ec Mon Sep 17 00:00:00 2001 From: lizerk Date: Mon, 9 Mar 2026 17:15:08 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=81=D0=B8=D0=BD=D1=82=D0=B0=D0=BA?= =?UTF-8?q?=D1=81=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Исправлено: - channels.py: удалены осиротевшие строки после рефакторинга - api/v1/notifications/urls.py: исправлен file для реэкспорта - notifications/urls.py: создан для обратной совместимости Миграции: - 0010_userchannelpreferences_and_more.py: переход на v2 модели * Notification с GenericForeignKey (actor/action_object/target) * UserChannelPreferences вместо UserNotificationSettings * Удалены старые модели (NotificationCategory, NotificationType и тд.) * Переименована таблица в notifications_notification_v2 --- backend/api/v1/notifications/urls.py | 2 - backend/notifications/channels.py | 8 - .../0010_userchannelpreferences_and_more.py | 317 ++++++++++++++++++ backend/notifications/urls.py | 8 + 4 files changed, 325 insertions(+), 10 deletions(-) create mode 100644 backend/notifications/migrations/0010_userchannelpreferences_and_more.py create mode 100644 backend/notifications/urls.py diff --git a/backend/api/v1/notifications/urls.py b/backend/api/v1/notifications/urls.py index 2148b851..c749f14e 100644 --- a/backend/api/v1/notifications/urls.py +++ b/backend/api/v1/notifications/urls.py @@ -10,5 +10,3 @@ DEPRECATED: Этот файл оставлен для обратной совм from notifications.api.urls import urlpatterns, app_name # noqa # app_name = 'notifications_api_v1' - уже импортирован - path('push/unsubscribe/', views.unsubscribe_push, name='push_unsubscribe'), -] diff --git a/backend/notifications/channels.py b/backend/notifications/channels.py index 18b4f0c0..8ab7130e 100644 --- a/backend/notifications/channels.py +++ b/backend/notifications/channels.py @@ -74,14 +74,6 @@ def route_notification_to_channels(sender, instance, created, **kwargs): if prefs.push_enabled: send_push_notification.delay(notification.id) - - Args: - notification: Объект Notification - - DEPRECATED: Используйте PushNotificationSender().send() - """ - sender = PushNotificationSender() - return sender.send(notification) # === Digest Email (для email_frequency = daily/weekly) === diff --git a/backend/notifications/migrations/0010_userchannelpreferences_and_more.py b/backend/notifications/migrations/0010_userchannelpreferences_and_more.py new file mode 100644 index 00000000..d18fa8d7 --- /dev/null +++ b/backend/notifications/migrations/0010_userchannelpreferences_and_more.py @@ -0,0 +1,317 @@ +# Generated by Django 5.2.4 on 2026-03-09 14:14 + +import datetime +import django.db.models.deletion +import django.utils.timezone +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('notifications', '0009_add_document_related_notification_type'), + 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='Отключенные типы')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Создано')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Обновлено')), + ], + options={ + 'verbose_name': 'Настройки каналов пользователя', + 'verbose_name_plural': 'Настройки каналов пользователей', + 'db_table': 'notifications_user_channel_preferences', + }, + ), + migrations.RemoveField( + model_name='notificationtype', + name='category', + ), + migrations.AlterUniqueTogether( + name='notificationtemplate', + unique_together=None, + ), + migrations.RemoveField( + model_name='notificationtemplate', + name='notification_type', + ), + migrations.RemoveField( + model_name='usernotificationsettings', + name='notification_type', + ), + migrations.RemoveField( + model_name='notification', + name='notification_type', + ), + migrations.RemoveField( + model_name='telegramuser', + name='user', + ), + migrations.AlterUniqueTogether( + name='usernotificationsettings', + unique_together=None, + ), + migrations.RemoveField( + model_name='usernotificationsettings', + name='user', + ), + migrations.AlterUniqueTogether( + name='webpushsubscription', + unique_together=None, + ), + migrations.RemoveField( + model_name='webpushsubscription', + name='user', + ), + migrations.AlterModelOptions( + name='notification', + options={'ordering': ['-timestamp'], 'verbose_name': 'Уведомление', 'verbose_name_plural': 'Уведомления'}, + ), + migrations.RemoveIndex( + model_name='notification', + name='notificatio_recipie_a972ce_idx', + ), + migrations.RemoveIndex( + model_name='notification', + name='notificatio_recipie_4e3567_idx', + ), + migrations.RemoveIndex( + model_name='notification', + name='notificatio_notific_c4ec61_idx', + ), + migrations.RemoveIndex( + model_name='notification', + name='notificatio_group_k_c495c5_idx', + ), + migrations.RemoveField( + model_name='notification', + name='action_text', + ), + migrations.RemoveField( + model_name='notification', + name='archived_at', + ), + migrations.RemoveField( + model_name='notification', + name='content_type', + ), + migrations.RemoveField( + model_name='notification', + name='created_at', + ), + migrations.RemoveField( + model_name='notification', + name='group_key', + ), + migrations.RemoveField( + model_name='notification', + name='grouped_count', + ), + migrations.RemoveField( + model_name='notification', + name='is_archived', + ), + migrations.RemoveField( + model_name='notification', + name='is_grouped', + ), + migrations.RemoveField( + model_name='notification', + name='is_read', + ), + migrations.RemoveField( + model_name='notification', + name='message', + ), + migrations.RemoveField( + model_name='notification', + name='metadata', + ), + migrations.RemoveField( + model_name='notification', + name='object_id', + ), + migrations.RemoveField( + model_name='notification', + name='read_at', + ), + migrations.RemoveField( + model_name='notification', + name='sent_at', + ), + migrations.RemoveField( + model_name='notification', + name='sent_email', + ), + migrations.RemoveField( + model_name='notification', + name='sent_telegram', + ), + migrations.RemoveField( + model_name='notification', + name='sent_web', + ), + migrations.RemoveField( + model_name='notification', + name='sent_wechat', + ), + migrations.RemoveField( + model_name='notification', + name='sent_whatsapp', + ), + migrations.RemoveField( + model_name='notification', + name='short_message', + ), + migrations.RemoveField( + model_name='notification', + name='title', + ), + migrations.RemoveField( + model_name='notification', + name='updated_at', + ), + migrations.AddField( + model_name='notification', + name='action_object_content_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.contenttype', verbose_name='Тип объекта действия'), + ), + migrations.AddField( + model_name='notification', + name='action_object_object_id', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='ID объекта действия'), + ), + migrations.AddField( + model_name='notification', + name='actor_content_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.contenttype', verbose_name='Тип актора'), + ), + migrations.AddField( + model_name='notification', + name='actor_object_id', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='ID актора'), + ), + migrations.AddField( + model_name='notification', + name='data', + field=models.JSONField(blank=True, default=dict, help_text='Любые метаданные для рендеринга или логики', verbose_name='Дополнительные данные'), + ), + migrations.AddField( + model_name='notification', + name='deleted', + field=models.BooleanField(db_index=True, default=False, help_text='Мягкое удаление', verbose_name='Удалено'), + ), + migrations.AddField( + model_name='notification', + name='description', + field=models.TextField(blank=True, help_text='Человекочитаемое описание уведомления', null=True, verbose_name='Описание'), + ), + migrations.AddField( + model_name='notification', + name='emailed', + field=models.BooleanField(db_index=True, default=False, verbose_name='Отправлено на email'), + ), + migrations.AddField( + model_name='notification', + name='public', + field=models.BooleanField(db_index=True, default=True, help_text='Можно показывать другим пользователям', verbose_name='Публичное'), + ), + migrations.AddField( + model_name='notification', + name='target_content_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.contenttype', verbose_name='Тип целевого объекта'), + ), + migrations.AddField( + model_name='notification', + name='target_object_id', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='ID целевого объекта'), + ), + migrations.AddField( + model_name='notification', + name='timestamp', + field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Создано'), + ), + migrations.AddField( + model_name='notification', + name='timestamp_read', + field=models.DateTimeField(blank=True, null=True, verbose_name='Время прочтения'), + ), + migrations.AddField( + model_name='notification', + name='unread', + field=models.BooleanField(db_index=True, default=True, verbose_name='Непрочитано'), + ), + migrations.AddField( + model_name='notification', + name='verb', + field=models.CharField(db_index=True, default=datetime.datetime(2026, 3, 9, 14, 14, 58, 351424, tzinfo=datetime.timezone.utc), help_text='Например: liked, commented, approved, mentioned', max_length=255, verbose_name='Действие'), + preserve_default=False, + ), + migrations.AlterField( + model_name='notification', + name='action_url', + field=models.CharField(blank=True, help_text='Куда перейти при клике на уведомление', max_length=500, null=True, verbose_name='URL действия'), + ), + migrations.AlterField( + model_name='notification', + name='recipient', + field=models.ForeignKey(help_text='Кому адресовано уведомление', on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='Получатель'), + ), + migrations.AddIndex( + model_name='notification', + index=models.Index(fields=['recipient', '-timestamp'], name='notificatio_recipie_91dc91_idx'), + ), + migrations.AddIndex( + model_name='notification', + index=models.Index(fields=['recipient', 'unread', '-timestamp'], name='notificatio_recipie_9b2135_idx'), + ), + migrations.AddIndex( + model_name='notification', + index=models.Index(fields=['verb', '-timestamp'], name='notificatio_verb_1060e9_idx'), + ), + migrations.AddIndex( + model_name='notification', + index=models.Index(fields=['-timestamp'], name='notificatio_timesta_e0a5cf_idx'), + ), + migrations.AlterModelTable( + name='notification', + table='notifications_notification_v2', + ), + migrations.AddField( + model_name='userchannelpreferences', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='channel_preferences', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь'), + ), + migrations.DeleteModel( + name='NotificationCategory', + ), + migrations.DeleteModel( + name='NotificationTemplate', + ), + migrations.DeleteModel( + name='NotificationType', + ), + migrations.DeleteModel( + name='TelegramUser', + ), + migrations.DeleteModel( + name='UserNotificationSettings', + ), + migrations.DeleteModel( + name='WebPushSubscription', + ), + ] diff --git a/backend/notifications/urls.py b/backend/notifications/urls.py new file mode 100644 index 00000000..f22b0e79 --- /dev/null +++ b/backend/notifications/urls.py @@ -0,0 +1,8 @@ +""" +URL routing для модуля notifications + +Для обратной совместимости импортирует из api.urls +""" +from notifications.api.urls import urlpatterns, app_name # noqa + +__all__ = ['urlpatterns', 'app_name']