From c893cc7bd998608a709ad43e812896320de994f0 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 21 Sep 2024 11:17:51 +0330 Subject: [PATCH 1/3] :sparkles::art: feat(constants): Add queryset default type annotations - Added type aliases for various notification-related models and parameters: - Recipient: Optional user model representing a notification recipient. - Recipients: Union type for a single user, queryset, or list of users. - Groups: Union type for a single group, queryset, or list of groups. - OptConditions: Optional query conditions using Django's Q object. - Link: Optional string for notification links. - Actor: Represents a model that acts on behalf of the user. - Target: Optional model representing the target of the notification. - ActionObject: Optional model representing the action object related to the notification. - Data: Optional JSONField for additional data related to the notification. - Description: Optional string for a description of the notification. --- django_notification/constants/qs_types.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 django_notification/constants/qs_types.py diff --git a/django_notification/constants/qs_types.py b/django_notification/constants/qs_types.py new file mode 100644 index 0000000..a440647 --- /dev/null +++ b/django_notification/constants/qs_types.py @@ -0,0 +1,18 @@ +from typing import List, Optional, Union + +from django.contrib.auth.models import Group +from django.db.models import JSONField, Model, Q, QuerySet + +from django_notification.utils.user_model import UserModel + +# Type Alias for Notification QuerySet +Recipient = Optional[UserModel] +Recipients = Optional[Union[UserModel, QuerySet, List[UserModel]]] +Groups = Optional[Union[Group, QuerySet, List[Group]]] +OptConditions = Optional[Q] +Link = Optional[str] +Actor = Model +Target = Optional[Model] +ActionObject = Optional[Model] +Data = Optional[JSONField] +Description = Optional[str] From 9e7f5bb81fcdcf9a74ccd297499a291898dd965e Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 21 Sep 2024 11:30:22 +0330 Subject: [PATCH 2/3] :zap::hammer::art: refactor(queryset): Update type annotations to apply constant types Closes #64 --- .../repository/queryset/notification.py | 96 ++++++++++--------- .../tests/repository/test_queryset.py | 2 +- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/django_notification/repository/queryset/notification.py b/django_notification/repository/queryset/notification.py index 170b888..17e5774 100644 --- a/django_notification/repository/queryset/notification.py +++ b/django_notification/repository/queryset/notification.py @@ -2,9 +2,21 @@ from django.contrib.auth.models import Group from django.db import transaction -from django.db.models import JSONField, Model, Q, QuerySet, Subquery +from django.db.models import Model, Q, QuerySet, Subquery from rest_framework.generics import get_object_or_404 +from django_notification.constants.qs_types import ( + ActionObject, + Actor, + Data, + Description, + Groups, + Link, + OptConditions, + Recipient, + Recipients, + Target, +) from django_notification.models.deleted_notification import DeletedNotification from django_notification.models.notification_seen import NotificationSeen from django_notification.utils.user_model import UserModel @@ -18,9 +30,7 @@ def with_related(self) -> QuerySet: "recipient", "group", "group__permissions", "seen_by" ) - def _get_deleted_notifications( - self, deleted_by: Optional[UserModel] = None - ) -> QuerySet: + def _get_deleted_notifications(self, deleted_by: Recipient = None) -> QuerySet: """Retrieve deleted notifications optionally filtered by user who delete the notification.""" queryset = DeletedNotification.objects.values("notification") @@ -30,9 +40,9 @@ def _get_deleted_notifications( def _get_notifications_queryset( self, - exclude_deleted_by: Optional[UserModel] = None, - display_detail: Optional[bool] = False, - conditions: Optional[Q] = None, + exclude_deleted_by: Recipient = None, + display_detail: bool = False, + conditions: OptConditions = None, ) -> Union[QuerySet, Dict]: """Return a queryset of notifications based on the given conditions. @@ -69,9 +79,9 @@ def _get_notifications_queryset( def all_notifications( self, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, - display_detail: Optional[bool] = False, + recipients: Recipients = None, + groups: Groups = None, + display_detail: bool = False, ) -> QuerySet: """Return all notifications excluding those that have been deleted. @@ -104,11 +114,11 @@ def all_notifications( def sent( self, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - exclude_deleted_by: Optional[UserModel] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, - display_detail: Optional[bool] = False, - conditions: Optional[Q] = Q(), + recipients: Recipients = None, + exclude_deleted_by: Recipient = None, + groups: Groups = None, + display_detail: bool = False, + conditions: Q = Q(), ) -> QuerySet: """Return all sent notifications. @@ -151,11 +161,11 @@ def sent( def unsent( self, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - exclude_deleted_by: Optional[UserModel] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, - display_detail: Optional[bool] = False, - conditions: Optional[Q] = Q(), + recipients: Recipients = None, + exclude_deleted_by: Recipient = None, + groups: Groups = None, + display_detail: bool = False, + conditions: Q = Q(), ) -> QuerySet: """Return all unsent notifications. @@ -198,10 +208,10 @@ def unsent( def seen( self, seen_by: UserModel, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, - display_detail: Optional[bool] = False, - conditions: Optional[Q] = Q(), + recipients: Recipients = None, + groups: Groups = None, + display_detail: bool = False, + conditions: Q = Q(), ) -> QuerySet: """Return all seen notifications by the given user.""" conditions &= Q(seen_by=seen_by) @@ -216,10 +226,10 @@ def seen( def unseen( self, unseen_by: UserModel, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, - display_detail: Optional[bool] = False, - conditions: Optional[Q] = Q(), + recipients: Recipients = None, + groups: Groups = None, + display_detail: bool = False, + conditions: Q = Q(), ) -> QuerySet: """Return notifications that the given user has not seen.""" return self.sent( @@ -251,10 +261,10 @@ def mark_all_as_seen(self, user: UserModel) -> int: NotificationSeen.objects.bulk_create(notifications_to_mark) return notifications.count() - def mark_as_sent( + def mark_all_as_sent( self, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, + recipients: Recipients = None, + groups: Groups = None, ) -> int: """Mark notifications as sent. @@ -270,7 +280,7 @@ def mark_as_sent( recipients=recipients, groups=groups, display_detail=True ).update(is_sent=True) - def deleted(self, deleted_by: Optional[UserModel] = None) -> QuerySet: + def deleted(self, deleted_by: Recipient = None) -> QuerySet: """Return all deleted notifications optionally filtered by the user who delete it.""" @@ -304,17 +314,17 @@ def clear_all(self, user: UserModel) -> None: def create_notification( self, verb: str, - actor: Model, - description: Optional[str] = None, - recipients: Optional[Union[UserModel, QuerySet, List[UserModel]]] = None, - groups: Optional[Union[Group, QuerySet, List[Group]]] = None, - status: Optional[str] = "INFO", + actor: Actor, + description: Description = None, + recipients: Recipients = None, + groups: Groups = None, + status: str = "INFO", public: bool = True, - target: Optional[Model] = None, - action_object: Optional[Model] = None, - link: Optional[str] = None, + target: Target = None, + action_object: ActionObject = None, + link: Link = None, is_sent: bool = False, - data: Optional[Dict] = None, + data: Data = None, ): """Create a new notification. @@ -380,7 +390,7 @@ def update_notification( notification_id: int, is_sent: Optional[bool] = None, public: Optional[bool] = None, - data: Optional[JSONField] = None, + data: Data = None, ): """Update the status of a notification selectively. @@ -415,7 +425,7 @@ def update_notification( def delete_notification( self, notification_id: int, - recipient: Optional[UserModel] = None, + recipient: Recipient = None, soft_delete: bool = True, ) -> None: """Delete a notification. diff --git a/django_notification/tests/repository/test_queryset.py b/django_notification/tests/repository/test_queryset.py index 4754155..d6f0523 100644 --- a/django_notification/tests/repository/test_queryset.py +++ b/django_notification/tests/repository/test_queryset.py @@ -171,7 +171,7 @@ def test_mark_as_sent(self, notifications: List[Notification]) -> None: ------- The `mark_as_sent` method is successfully called. (Placeholder assertion) """ - Notification.queryset.mark_as_sent() + Notification.queryset.mark_all_as_sent() assert True def test_deleted( From 041aaf1e8aa7c739d531a5b50dbd66f4520bb91f Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Sat, 21 Sep 2024 11:39:39 +0330 Subject: [PATCH 3/3] :art::zap::books: refactor(docs): Update API docstring and fix typos --- django_notification/api/views/activity.py | 34 +++++++++++-------- django_notification/api/views/notification.py | 8 ++--- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/django_notification/api/views/activity.py b/django_notification/api/views/activity.py index dc99c66..f94b3e4 100644 --- a/django_notification/api/views/activity.py +++ b/django_notification/api/views/activity.py @@ -38,34 +38,38 @@ class ActivityViewSet( Features: - List Activities: Lists all seen notifications for the user. The availability of - this method is controlled by the `api_allow_list` setting. + this method is controlled by settings. - Retrieve Activity: Retrieve detailed information about a specific notification - by its ID. The availability of this method is controlled by the `api_allow_retrieve` setting. + by its ID. The availability of this method is controlled by settings. - Clear Activities: Allows users to soft-delete or clear all notifications. This - functionality is controlled by the `include_soft_delete` setting. + functionality is controlled by settings. - Delete Activities: Admin users can permanently delete notifications. This - feature is controlled by the `include_hard_delete` setting. + feature is controlled by settings. Customizations: - Dynamic Serializer: Based on user role and settings, the appropriate serializer is chosen between `NotificationSerializer` (for detailed information) and `SimpleNotificationSerializer` (for basic information). - Conditional Actions: Soft and hard deletion actions are conditionally enabled - based on settings (`include_soft_delete`, `include_hard_delete`) and permissions. - Admins have additional capabilities such as hard-deleting notifications. + based on settings and permissions. + Admins have additional capabilities such as hard-deleting notifications based on settings. - Filtering and Searching: This view supports filtering, searching, and ordering of notifications via `DjangoFilterBackend`, `SearchFilter`, and `OrderingFilter`. - Configuration via Settings: The viewset is dynamically configured using the `configure_attrs` method, which adjusts method availability and functionality based on project settings. + Parsers: + - Accepts various content types, such as `application/json` and `multipart/form-data`, + allowing flexibility in handling requests that include file uploads or JSON payloads. + Methods: - `GET /activities/`: List all seen notifications. - `GET /activities//`: Retrieve detailed information about a specific notification. - - `POST /activities/clear/`: Soft-delete or clear all activities (conditionally enabled). - - `POST /activities/clear//`: Soft-delete a specific notification (conditionally enabled). - - `POST /activities/delete/`: Permanently delete all activities (admin only, conditionally enabled). - - `POST /activities/delete//`: Permanently delete a specific notification (admin only, conditionally enabled). + - `GET /activities/clear_activities/`: Soft-delete or clear all activities (conditionally enabled). + - `GET /activities//clear_notification/`: Soft-delete a specific notification (conditionally enabled). + - `GET /activities/delete_activities/`: Permanently delete all activities (admin only, conditionally enabled). + - `GET /activities//delete_notification`: Permanently delete a specific notification (admin only, conditionally enabled). Permissions: - Regular Users: Can list, retrieve, and clear their own notifications. @@ -73,10 +77,12 @@ class ActivityViewSet( on the configuration. Settings: - - The availability of list and retrieve methods is controlled by `api_allow_list` - and `api_allow_retrieve` settings. - - The soft-delete and hard-delete functionalities are controlled by `include_soft_delete` - and `include_hard_delete` settings. + - The availability of list and retrieve methods is controlled by `DJANGO_NOTIFICATION_API_ALLOW_LIST` + and `DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE` settings. + - The soft-delete and hard-delete functionalities are controlled by `DJANGO_NOTIFICATION_API_INCLUDE_SOFT_DELETE` + and `DJANGO_NOTIFICATION_API_INCLUDE_HARD_DELETE` settings. + - The level of notification details returned is based on the user's role and the setting + (`DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS`). This viewset provides a flexible and configurable API for managing notification activities, with dynamic behavior driven by user roles and project settings. diff --git a/django_notification/api/views/notification.py b/django_notification/api/views/notification.py index 35cc949..5d44520 100644 --- a/django_notification/api/views/notification.py +++ b/django_notification/api/views/notification.py @@ -33,10 +33,10 @@ class NotificationViewSet( Features: - List Notifications: Retrieves a list of unseen notifications. The availability - of this method is controlled by a setting (`api_allow_list`). + of this method is controlled by settings. - Retrieve Notification: Fetch detailed information about a specific notification by its ID. Mark the notification as seen upon retrieval. The availability of this - method is controlled by a setting (`api_allow_retrieve`). + method is controlled by settings. - Mark All as Seen: Marks all unseen notifications for the user as seen. Customizations: @@ -64,9 +64,9 @@ class NotificationViewSet( Settings: - The availability of list and retrieve methods is determined by the configuration - (`api_allow_list`, `api_allow_retrieve`). + (`DJANGO_NOTIFICATION_API_ALLOW_LIST`, `DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE`). - The level of notification details returned is based on the user's role and the setting - (`include_serializer_full_details`). + (`DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS`). Parsers and filters are automatically applied based on the request's `Content-Type` and query parameters, making the viewset flexible and adaptable to various use cases.