diff --git a/CHANGELOG.md b/CHANGELOG.md index 812ab49..99a761b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,15 @@ ## 2.0.0 - Migrated to Github CI - Added docker environment and migrated to Poetry and pyproject.toml - - Added verbose_name migration - - Migrated from `jsonfield` to Django `JSONField` - - Converted the `level` field from a `CharField` to an `IntegerField` + - Added **verbose_name** migration + - Migrated the **data** field from `jsonfield` to Django `JSONField` + - Converted the **level** field from a `CharField` to an `IntegerField` - Extracted and improved the sample code - Fixed variable types on JS code - Added `/all/` for the path to all notifications - Remove slug2id and id2slug + - Changed **related_name** from `notifications` to `%(app_label)s_%(class)s_related` (default: `notifications_notification_related`) + - Changed **related_query_name** from `notifications` to `%(app_label)s_%(class)s` (default: `notifications_notification`) ## 1.8.0 diff --git a/Makefile b/Makefile index c30e11c..589fd82 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ server: run: server migrations: - poetry run python manage.py makemigrations + poetry run python notifications/manage.py makemigrations migrate: poetry run python manage.py migrate diff --git a/README.md b/README.md index 0543efd..f6ac576 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,8 @@ can do: ```python user = User.objects.get(pk=pk) - user.notifications.unread() + user.notifications_notification_related.unread() + # user.__related.unread() # If you are using a custom model ``` There are some other QuerySet methods, too. @@ -477,6 +478,8 @@ You will require to define `NOTIFICATIONS_NOTIFICATION_MODEL` setting in NOTIFICATIONS_NOTIFICATION_MODEL = 'your_app.Notification' ``` +> If you are using a custom notification model, your **related_name** will change to `__related`. + ## Notes ### Email Notification diff --git a/notifications/helpers.py b/notifications/helpers.py index 6d83940..26051e3 100644 --- a/notifications/helpers.py +++ b/notifications/helpers.py @@ -19,7 +19,7 @@ def get_num_to_fetch(request): def get_notification_list(request, method_name="all"): num_to_fetch = get_num_to_fetch(request) notification_list = [] - for notification in getattr(request.user.notifications, method_name)()[0:num_to_fetch]: + for notification in getattr(request.user.notifications_notification_related, method_name)()[0:num_to_fetch]: struct = model_to_dict(notification) struct["slug"] = notification.id if notification.actor: diff --git a/notifications/manage.py b/notifications/manage.py index 1b2eeda..01bc1fb 100644 --- a/notifications/manage.py +++ b/notifications/manage.py @@ -4,7 +4,7 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "notification.tests.settings_for_tests") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "notifications.tests.settings_for_tests") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) diff --git a/notifications/migrations/0015_alter_notification_action_object_content_type_and_more.py b/notifications/migrations/0015_alter_notification_action_object_content_type_and_more.py new file mode 100644 index 0000000..4e1799a --- /dev/null +++ b/notifications/migrations/0015_alter_notification_action_object_content_type_and_more.py @@ -0,0 +1,61 @@ +# Generated by Django 4.2.11 on 2024-04-21 14:38 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("contenttypes", "0002_remove_content_type_name"), + ("notifications", "0014_rename_new_level_notification_level"), + ] + + operations = [ + migrations.AlterField( + model_name="notification", + name="action_object_content_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_action_object_related", + to="contenttypes.contenttype", + verbose_name="action object content type", + ), + ), + migrations.AlterField( + model_name="notification", + name="actor_content_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_actor_related", + to="contenttypes.contenttype", + verbose_name="actor content type", + ), + ), + migrations.AlterField( + model_name="notification", + name="recipient", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_related", + related_query_name="%(app_label)s_%(class)s", + to=settings.AUTH_USER_MODEL, + verbose_name="recipient", + ), + ), + migrations.AlterField( + model_name="notification", + name="target_content_type", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(app_label)s_%(class)s_target_related", + to="contenttypes.contenttype", + verbose_name="target content type", + ), + ), + ] diff --git a/notifications/models/base.py b/notifications/models/base.py index 76c96dd..7fa44a6 100644 --- a/notifications/models/base.py +++ b/notifications/models/base.py @@ -57,14 +57,18 @@ class AbstractNotification(models.Model): recipient = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, - related_name="notifications", + related_name="%(app_label)s_%(class)s_related", + related_query_name="%(app_label)s_%(class)s", verbose_name=_("recipient"), blank=False, ) unread = models.BooleanField(_("unread"), default=True, blank=False, db_index=True) actor_content_type = models.ForeignKey( - ContentType, on_delete=models.CASCADE, related_name="notify_actor", verbose_name=_("actor content type") + ContentType, + on_delete=models.CASCADE, + related_name="%(app_label)s_%(class)s_actor_related", + verbose_name=_("actor content type"), ) actor_object_id = models.CharField(_("actor object id"), max_length=255) actor = GenericForeignKey("actor_content_type", "actor_object_id") @@ -76,7 +80,7 @@ class AbstractNotification(models.Model): target_content_type = models.ForeignKey( ContentType, on_delete=models.CASCADE, - related_name="notify_target", + related_name="%(app_label)s_%(class)s_target_related", verbose_name=_("target content type"), blank=True, null=True, @@ -88,7 +92,7 @@ class AbstractNotification(models.Model): action_object_content_type = models.ForeignKey( ContentType, on_delete=models.CASCADE, - related_name="notify_action_object", + related_name="%(app_label)s_%(class)s_action_object_related", verbose_name=_("action object content type"), blank=True, null=True, diff --git a/notifications/types.py b/notifications/notification_types.py similarity index 100% rename from notifications/types.py rename to notifications/notification_types.py diff --git a/notifications/querysets.py b/notifications/querysets.py index 179dca5..b7c2a90 100644 --- a/notifications/querysets.py +++ b/notifications/querysets.py @@ -4,8 +4,8 @@ from django.core.exceptions import ImproperlyConfigured from django.db import models from django.utils.translation import gettext_lazy as _ +from notifications.notification_types import AbstractUser from notifications.settings import notification_settings -from notifications.types import AbstractUser def assert_soft_delete() -> None: diff --git a/notifications/templatetags/notifications_tags.py b/notifications/templatetags/notifications_tags.py index be2b392..7c91ab8 100644 --- a/notifications/templatetags/notifications_tags.py +++ b/notifications/templatetags/notifications_tags.py @@ -12,7 +12,9 @@ register = Library() def get_cached_notification_unread_count(user): return cache.get_or_set( - "cache_notification_unread_count", user.notifications.unread().count, notification_settings.CACHE_TIMEOUT + "cache_notification_unread_count", + user.notifications_notification_related.unread().count, + notification_settings.CACHE_TIMEOUT, ) @@ -27,7 +29,7 @@ def notifications_unread(context): @register.filter def has_notification(user): if user: - return user.notifications.unread().exists() + return user.notifications_notification_related.unread().exists() return False diff --git a/notifications/tests/tests.py b/notifications/tests/tests.py index eef461a..a4f021f 100644 --- a/notifications/tests/tests.py +++ b/notifications/tests/tests.py @@ -82,29 +82,37 @@ class NotificationTestPages(TestCase): response = self.client.get(reverse("notifications:all")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.all())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.all()) + ) def test_unread_messages_pages(self): self.login("to", "pwd") response = self.client.get(reverse("notifications:unread")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.unread())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.unread()) + ) self.assertEqual(len(response.context["notifications"]), self.message_count) - for index, notification in enumerate(self.to_user.notifications.all()): + for index, notification in enumerate(self.to_user.notifications_notification_related.all()): if index % 3 == 0: response = self.client.get(reverse("notifications:mark_as_read", args=[notification.id])) self.assertEqual(response.status_code, 302) response = self.client.get(reverse("notifications:unread")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.unread())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.unread()) + ) self.assertTrue(len(response.context["notifications"]) < self.message_count) response = self.client.get(reverse("notifications:mark_all_as_read")) self.assertRedirects(response, reverse("notifications:unread")) response = self.client.get(reverse("notifications:unread")) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.unread())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.unread()) + ) self.assertEqual(len(response.context["notifications"]), 0) def test_next_pages(self): @@ -119,7 +127,7 @@ class NotificationTestPages(TestCase): ) self.assertRedirects(response, reverse("notifications:unread") + query_parameters) - slug = self.to_user.notifications.first().id + slug = self.to_user.notifications_notification_related.first().id response = self.client.get( reverse("notifications:mark_as_read", args=[slug]), data={ @@ -128,7 +136,7 @@ class NotificationTestPages(TestCase): ) self.assertRedirects(response, reverse("notifications:unread") + query_parameters) - slug = self.to_user.notifications.first().id + slug = self.to_user.notifications_notification_related.first().id response = self.client.get( reverse("notifications:mark_as_unread", args=[slug]), { @@ -137,7 +145,7 @@ class NotificationTestPages(TestCase): ) self.assertRedirects(response, reverse("notifications:unread") + query_parameters) - @override_settings(ALLOWED_HOSTS=["www.notifications.com"]) + @override_settings(ALLOWED_HOSTS=["www.notifications_notification_related.com"]) def test_malicious_next_pages(self): self.client.force_login(self.to_user) query_parameters = "?var1=hello&var2=world" @@ -154,36 +162,44 @@ class NotificationTestPages(TestCase): def test_delete_messages_pages(self): self.login("to", "pwd") - slug = self.to_user.notifications.first().id + slug = self.to_user.notifications_notification_related.first().id response = self.client.get(reverse("notifications:delete", args=[slug])) self.assertRedirects(response, reverse("notifications:all")) response = self.client.get(reverse("notifications:all")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.all())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.all()) + ) self.assertEqual(len(response.context["notifications"]), self.message_count - 1) response = self.client.get(reverse("notifications:unread")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.unread())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.unread()) + ) self.assertEqual(len(response.context["notifications"]), self.message_count - 1) @override_settings(DJANGO_NOTIFICATIONS_CONFIG={"SOFT_DELETE": True}) def test_soft_delete_messages_manager(self): self.login("to", "pwd") - slug = self.to_user.notifications.first().id + slug = self.to_user.notifications_notification_related.first().id response = self.client.get(reverse("notifications:delete", args=[slug])) self.assertRedirects(response, reverse("notifications:all")) response = self.client.get(reverse("notifications:all")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.active())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.active()) + ) self.assertEqual(len(response.context["notifications"]), self.message_count - 1) response = self.client.get(reverse("notifications:unread")) self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.context["notifications"]), len(self.to_user.notifications.unread())) + self.assertEqual( + len(response.context["notifications"]), len(self.to_user.notifications_notification_related.unread()) + ) self.assertEqual(len(response.context["notifications"]), self.message_count - 1) def test_unread_count_api(self): diff --git a/notifications/views.py b/notifications/views.py index 0c14434..062b7a7 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -34,20 +34,20 @@ class AllNotificationsList(NotificationViewList): def get_queryset(self): if notification_settings.SOFT_DELETE: - qset = self.request.user.notifications.active() + qset = self.request.user.notifications_notification_related.active() else: - qset = self.request.user.notifications.all() + qset = self.request.user.notifications_notification_related.all() return qset class UnreadNotificationsList(NotificationViewList): def get_queryset(self): - return self.request.user.notifications.unread() + return self.request.user.notifications_notification_related.unread() @login_required def mark_all_as_read(request): - request.user.notifications.mark_all_as_read() + request.user.notifications_notification_related.mark_all_as_read() _next = request.GET.get("next") @@ -112,7 +112,7 @@ def live_unread_notification_count(request): data = {"unread_count": 0} else: data = { - "unread_count": request.user.notifications.unread().count(), + "unread_count": request.user.notifications_notification_related.unread().count(), } return JsonResponse(data) @@ -126,7 +126,10 @@ def live_unread_notification_list(request): unread_list = get_notification_list(request, "unread") - data = {"unread_count": request.user.notifications.unread().count(), "unread_list": unread_list} + data = { + "unread_count": request.user.notifications_notification_related.unread().count(), + "unread_list": unread_list, + } return JsonResponse(data) @@ -139,7 +142,7 @@ def live_all_notification_list(request): all_list = get_notification_list(request) - data = {"all_count": request.user.notifications.count(), "all_list": all_list} + data = {"all_count": request.user.notifications_notification_related.count(), "all_list": all_list} return JsonResponse(data) @@ -148,6 +151,6 @@ def live_all_notification_count(request): data = {"all_count": 0} else: data = { - "all_count": request.user.notifications.count(), + "all_count": request.user.notifications_notification_related.count(), } return JsonResponse(data) diff --git a/sample_website/views.py b/sample_website/views.py index 98f4e23..b97f626 100644 --- a/sample_website/views.py +++ b/sample_website/views.py @@ -17,8 +17,8 @@ def live_tester(request): request, "test_live.html", { - "unread_count": request.user.notifications.unread().count(), - "notifications": request.user.notifications.all(), + "unread_count": request.user.notifications_notification_related.unread().count(), + "notifications": request.user.notifications_notification_related.all(), }, )