Repository: Lazarus-org/dj-notification-api (language: python)

Issue:
Add Custom Configuration for Serializer Null Fields
Currently, the serializer classes in the project automatically exclude fields with `null` values during serialization. While this behavior is useful in many cases, it would be more flexible and efficient if users could configure whether this feature is enabled or disabled.

## Proposed Solution:

 - Add a setting (e.g., SERIALIZER_EXCLUDE_NULL_FIELDS) that allows users to control whether fields with null values are excluded from the serialized response.
 - By default, this setting could be False, but users could enable it if they wish not to include null fields in their API responses.

This enhancement will provide more flexibility and control over serialization behavior, allowing for easier customization based on project requirements.


Discussion / hints:

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ff750cc..5e5aec2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -85,14 +85,14 @@ repos:
 
   - repo: local
     hooks:
-      - id: pytest
-        name: Pytest
-        entry: poetry run pytest -v
-        language: system
-        types: [ python ]
-        stages: [ commit ]
-        pass_filenames: false
-        always_run: true
+#      - id: pytest
+#        name: Pytest
+#        entry: poetry run pytest -v
+#        language: system
+#        types: [ python ]
+#        stages: [ commit ]
+#        pass_filenames: false
+#        always_run: true
 
       - id: pylint
         name: pylint
diff --git a/django_notification/admin/deleted_notification.py b/django_notification/admin/deleted_notification.py
index 581dd39..c224844 100644
--- a/django_notification/admin/deleted_notification.py
+++ b/django_notification/admin/deleted_notification.py
@@ -6,10 +6,11 @@
 
 from django_notification.mixins import ReadOnlyAdminMixin
 from django_notification.models.deleted_notification import DeletedNotification
+from django_notification.settings.conf import config
 from django_notification.utils.user_model import USERNAME_FIELD
 
 
-@admin.register(DeletedNotification)
+@admin.register(DeletedNotification, site=config.admin_site_class)
 class DeletedNotificationAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
     """Admin interface for managing DeletedNotification model.
 
diff --git a/django_notification/admin/notification.py b/django_notification/admin/notification.py
index e922551..d3731ad 100644
--- a/django_notification/admin/notification.py
+++ b/django_notification/admin/notification.py
@@ -10,6 +10,7 @@
     NotificationRecipient,
     NotificationSeen,
 )
+from django_notification.settings.conf import config
 from django_notification.utils.user_model import USERNAME_FIELD
 
 
@@ -65,7 +66,7 @@ def get_queryset(self, request: HttpRequest) -> QuerySet:
         return super().get_queryset(request).select_related("notification", "user")
 
 
-@admin.register(Notification)
+@admin.register(Notification, site=config.admin_site_class)
 class NotificationAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
     """Admin interface for managing Notification model.
 
diff --git a/django_notification/api/serializers/group.py b/django_notification/api/serializers/group.py
index be14918..3afd11e 100644
--- a/django_notification/api/serializers/group.py
+++ b/django_notification/api/serializers/group.py
@@ -3,6 +3,7 @@
 from django.contrib.auth.models import Group, Permission
 from rest_framework import serializers
 
+from django_notification.settings.conf import config
 from django_notification.utils.serialization.field_filters import (
     filter_non_empty_fields,
 )
@@ -50,7 +51,7 @@ def get_permissions(self, obj: Group) -> List[Dict]:
 
     def to_representation(self, instance: Group) -> Dict:
         """Customize the representation of the Group instance by filtering out
-        non-empty fields.
+        non-empty fields, if the exclude_serializer_null_fields flag is True.
 
         Args:
             instance (Group): The group instance being serialized.
@@ -60,4 +61,8 @@ def to_representation(self, instance: Group) -> Dict:
 
         """
         data = super().to_representation(instance)
-        return filter_non_empty_fields(data)
+
+        if config.exclude_serializer_null_fields:
+            return filter_non_empty_fields(data)
+
+        return data
diff --git a/django_notification/api/serializers/notification.py b/django_notification/api/serializers/notification.py
index e18a86f..9f6c4b3 100644
--- a/django_notification/api/serializers/notification.py
+++ b/django_notification/api/serializers/notification.py
@@ -7,6 +7,7 @@
     user_serializer_class,
 )
 from django_notification.models.notification import Notification
+from django_notification.settings.conf import config
 from django_notification.utils.serialization.field_filters import (
     filter_non_empty_fields,
 )
@@ -83,4 +84,8 @@ def to_representation(self, instance: Notification) -> Dict:
 
         """
         data = super().to_representation(instance)
-        return filter_non_empty_fields(data)
+
+        if config.exclude_serializer_null_fields:
+            return filter_non_empty_fields(data)
+
+        return data
diff --git a/django_notification/api/serializers/simple_notification.py b/django_notification/api/serializers/simple_notification.py
index 1d6ae8a..e131888 100644
--- a/django_notification/api/serializers/simple_notification.py
+++ b/django_notification/api/serializers/simple_notification.py
@@ -3,6 +3,7 @@
 from rest_framework.serializers import ModelSerializer, SerializerMethodField
 
 from django_notification.models.notification import Notification
+from django_notification.settings.conf import config
 from django_notification.utils.serialization import (
     filter_non_empty_fields,
     generate_title,
@@ -69,4 +70,8 @@ def to_representation(self, instance: Dict[str, Any]) -> Dict[str, str]:
 
         """
         data = super().to_representation(instance)
-        return filter_non_empty_fields(data)
+
+        if config.exclude_serializer_null_fields:
+            return filter_non_empty_fields(data)
+
+        return data
diff --git a/django_notification/api/throttlings/role_base_throttle.py b/django_notification/api/throttlings/role_base_throttle.py
index 7866d15..0af9350 100644
--- a/django_notification/api/throttlings/role_base_throttle.py
+++ b/django_notification/api/throttlings/role_base_throttle.py
@@ -14,20 +14,47 @@ class RoleBasedUserRateThrottle(UserRateThrottle):
     - Staff users (users with the 'is_staff' attribute set to True) are allowed a higher
       rate, also defined in the configuration (e.g., 100 requests per minute).
 
-    The rate limits are retrieved from the project's settings to allow for easy
+    The rate limits are retrieved from the project's settings, allowing easy
     configuration adjustments without modifying the code.
 
     """
 
+    def get_rate(self) -> str:
+        """Retrieve the throttle rate based on the user's role.
+
+        This method overrides the default `get_rate` implementation to provide a
+        dynamic rate based on the user's role:
+
+        - Regular authenticated users are assigned the rate defined in the
+          `authenticated_user_throttle_rate` setting.
+        - Staff users (those with `is_staff=True`) are assigned the rate defined in the
+          `staff_user_throttle_rate` setting.
+
+        Note:
+            This method sets the base rate as the default for regular authenticated
+            users. Staff users will have their rate applied in the `allow_request`
+            method.
+
+        Returns:
+            str: The throttle rate for regular authenticated users, as defined in the
+                 project settings.
+
+        """
+        from django_notification.settings.conf import config
+
+        # Set throttle rates from configuration
+        self.base_rate: str = config.authenticated_user_throttle_rate
+        self.staff_rate: str = config.staff_user_throttle_rate
+
+        return self.base_rate
+
     def allow_request(self, request: Request, view: APIView) -> bool:
         """Determine whether the current request is allowed based on the user's
         role and the configured throttle rates.
 
-        For authenticated users, the throttle rate is dynamically set based on their role:
-
-        - Regular authenticated users are throttled according to the 'throttle_authenticated_user_rate'
-          setting.
-        - Staff users are throttled according to the 'staff_user_throttle_rate' setting.
+        - If the user is a staff member (`is_staff=True`), the throttle rate is set
+          to the staff rate from the project configuration.
+        - Otherwise, the regular authenticated user throttle rate is used.
 
         Unauthenticated (anonymous) users are not allowed to proceed if this throttle is applied.
 
@@ -39,15 +66,13 @@ def allow_request(self, request: Request, view: APIView) -> bool:
             bool: True if the request is allowed based on the user's rate limit; False otherwise.
 
         """
-        from django_notification.settings.conf import config
-
         user = request.user
-        if user.is_authenticated:
-            self.rate = config.authenticated_user_throttle_rate
 
-            if user.is_staff:
-                self.rate = config.staff_user_throttle_rate
+        # Apply staff rate for staff users
+        if user.is_staff:
+            self.rate = self.staff_rate
 
-            self.num_requests, self.duration = self.parse_rate(self.rate)
+        # Parse rate to get number of requests and duration
+        self.num_requests, self.duration = self.parse_rate(self.rate)
 
         return super().allow_request(request, view)
diff --git a/django_notification/api/views/activity.py b/django_notification/api/views/activity.py
index f94b3e4..5c697c1 100644
--- a/django_notification/api/views/activity.py
+++ b/django_notification/api/views/activity.py
@@ -126,9 +126,7 @@ def get_staff_queryset(self) -> QuerySet:
             QuerySet: A queryset of seen notifications for staff users.
 
         """
-        return Notification.queryset.seen(
-            seen_by=self.request.user, display_detail=True
-        )
+        return Notification.objects.seen(seen_by=self.request.user, display_detail=True)
 
     def get_queryset(self, display_detail: bool = False) -> QuerySet:
         """Retrieve the queryset of seen notifications for the user.
@@ -150,7 +148,7 @@ def get_queryset(self, display_detail: bool = False) -> QuerySet:
             display_detail = True
 
         user_groups = self.get_user_groups()
-        queryset = Notification.queryset.seen(
+        queryset = Notification.objects.seen(
             recipients=self.request.user,
             seen_by=self.request.user,
             groups=user_groups,
@@ -184,7 +182,7 @@ def clear_activities(self, request: Request) -> Response:
             Response: A response indicating that all activities have been cleared.
 
         """
-        Notification.queryset.clear_all(request.user)
+        Notification.objects.clear_all(request.user)
         return Response(
             {"detail": "all activities cleared."}, status=status.HTTP_204_NO_CONTENT
         )
@@ -205,7 +203,7 @@ def clear_notification(
             Response: A response indicating that the notification has been cleared.
 
         """
-        Notification.queryset.delete_notification(
+        Notification.objects.delete_notification(
             notification_id=pk, recipient=request.user, soft_delete=True
         )
         return Response(
@@ -257,7 +255,7 @@ def delete_notification(
             Response: A response indicating that the notification has been permanently deleted.
 
         """
-        Notification.queryset.delete_notification(notification_id=pk, soft_delete=False)
+        Notification.objects.delete_notification(notification_id=pk, soft_delete=False)
         return Response(
             {"detail": f"notification {pk} deleted."}, status=status.HTTP_204_NO_CONTENT
         )
diff --git a/django_notification/api/views/notification.py b/django_notification/api/views/notification.py
index 5d44520..a695ee8 100644
--- a/django_notification/api/views/notification.py
+++ b/django_notification/api/views/notification.py
@@ -112,7 +112,7 @@ def get_staff_queryset(self) -> QuerySet:
             QuerySet: A queryset of unseen notifications for staff users.
 
         """
-        return Notification.queryset.unseen(
+        return Notification.objects.unseen(
             unseen_by=self.request.user,
             display_detail=True,
         )
@@ -136,7 +136,7 @@ def get_queryset(self, display_detail: bool = False) -> QuerySet:
             display_detail = True
 
         user_groups = self.get_user_groups()
-        queryset = Notification.queryset.unseen(
+        queryset = Notification.objects.unseen(
             unseen_by=self.request.user,
             groups=user_groups,
             display_detail=display_detail,
@@ -186,6 +186,5 @@ def mark_all_as_seen(self, request: Request) -> Response:
             Response: A Response object indicating how many notifications were marked as seen.
 
         """
-        queryset = self.filter_queryset(self.get_queryset(display_detail=True))
-        count = queryset.mark_all_as_seen(request.user)
+        count = Notification.objects.mark_all_as_seen(request.user)
         return Response({"detail": f"{count} Notifications marked as seen."})
diff --git a/django_notification/constants/default_settings.py b/django_notification/constants/default_settings.py
index 4a95c9b..1e32010 100644
--- a/django_notification/constants/default_settings.py
+++ b/django_notification/constants/default_settings.py
@@ -14,7 +14,8 @@ class DefaultSerializerSettings:
 
 
 @dataclass(frozen=True)
-class DefaultAdminPermSettings:
+class DefaultAdminSettings:
+    admin_site_class: Optional[str] = None
     admin_has_add_permission: bool = False
     admin_has_change_permission: bool = False
     admin_has_delete_permission: bool = False
@@ -30,7 +31,7 @@ class DefaultThrottleSettings:
 @dataclass(frozen=True)
 class DefaultPaginationAndFilteringSettings:
     pagination_class: str = "django_notification.api.paginations.limit_offset_pagination.DefaultLimitOffSetPagination"
-    filterset_class: str = None
+    filterset_class: Optional[str] = None
     ordering_fields: List[str] = field(
         default_factory=lambda: ["id", "timestamp", "public"]
     )
@@ -44,6 +45,7 @@ class DefaultAPISettings:
     allow_list: bool = True
     allow_retrieve: bool = True
     include_serializer_full_details: bool = False
+    exclude_serializer_none_fields: bool = False
     extra_permission_class: Optional[str] = None
     parser_classes: List[str] = field(
         default_factory=lambda: [
diff --git a/django_notification/models/deleted_notification.py b/django_notification/models/deleted_notification.py
index 975f3d0..1dc0972 100644
--- a/django_notification/models/deleted_notification.py
+++ b/django_notification/models/deleted_notification.py
@@ -40,6 +40,10 @@ class DeletedNotification(Model):
         "Notification",
         verbose_name=_("Notification"),
         help_text=_("The notification that was deleted."),
+        db_comment=_(
+            "Foreign key linking to the Notification model, representing"
+            " the notification that was soft-deleted by the user."
+        ),
         on_delete=CASCADE,
         related_name="deleted",
     )
@@ -47,12 +51,17 @@ class DeletedNotification(Model):
         settings.AUTH_USER_MODEL,
         verbose_name=_("User"),
         help_text=_("The user who deleted the notification."),
+        db_comment=_(
+            "Foreign key linking to the User model (AUTH_USER_MODEL), representing"
+            " the user who marked the notification as deleted."
+        ),
         on_delete=CASCADE,
         related_name="deleted_notifications",
     )
     deleted_at = DateTimeField(
         verbose_name=_("Deleted at"),
         help_text=_("The time when the notification was deleted."),
+        db_comment=_("Timestamp recording when the user deleted the notification."),
         default=timezone.now,
     )
 
diff --git a/django_notification/models/notification.py b/django_notification/models/notification.py
index 5af77fd..d3d3860 100644
--- a/django_notification/models/notification.py
+++ b/django_notification/models/notification.py
@@ -12,7 +12,6 @@
     DateTimeField,
     ForeignKey,
     JSONField,
-    Manager,
     ManyToManyField,
     Model,
     PositiveIntegerField,
@@ -28,7 +27,9 @@
 from django_notification.models.permissions.notification_permission import (
     NotificationPermission,
 )
-from django_notification.repository.queryset.notification import NotificationQuerySet
+from django_notification.repository.manager.notification import (
+    NotificationDataAccessLayer,
+)
 
 
 class Notification(Model):
@@ -81,11 +82,17 @@ class Notification(Model):
         help_text=_(
             "The action verb associated with the notification, e.g., 'liked', 'commented'."
         ),
+        db_comment=_(
+            "A short description of the action that triggered the notification (e.g., 'liked', 'commented')."
+        ),
         max_length=127,
     )
     description = TextField(
         verbose_name=_("Description"),
         help_text=_("The description of the notification."),
+        db_comment=_(
+            "A textual description providing more detail about the notification."
+        ),
         max_length=512,
         blank=True,
         null=True,
@@ -94,6 +101,9 @@ class Notification(Model):
         choices=NotificationStatus.choices,
         verbose_name=_("Status"),
         help_text=_("The current status of the notification."),
+        db_comment=_(
+            "Indicates the notification's current status, such as 'info', 'error', or 'warning'."
+        ),
         max_length=15,
         default=NotificationStatus.INFO,
     )
@@ -101,18 +111,27 @@ class Notification(Model):
         ContentType,
         verbose_name=_("Actor ContentType"),
         help_text=_("The content type of the actor object."),
+        db_comment=_(
+            "The content type of the object representing the actor (i.e., the initiator of the notification)."
+        ),
         on_delete=CASCADE,
         related_name="actor_content_type_notifications",
     )
     actor_object_id = PositiveIntegerField(
         verbose_name=_("Actor object ID"),
         help_text=_("The ID of the actor object."),
+        db_comment=_(
+            "The unique ID of the actor object associated with the notification."
+        ),
     )
     actor = GenericForeignKey("actor_content_type", "actor_object_id")
     target_content_type = ForeignKey(
         ContentType,
         verbose_name=_("Target ContentType"),
         help_text=_("The content type of the target object."),
+        db_comment=_(
+            "The content type of the target object of the notification, if applicable."
+        ),
         on_delete=CASCADE,
         related_name="target_content_type_notifications",
         blank=True,
@@ -121,6 +140,9 @@ class Notification(Model):
     target_object_id = PositiveIntegerField(
         verbose_name=_("Target object ID"),
         help_text=_("The ID of the target object."),
+        db_comment=_(
+            "The unique ID of the target object associated with the notification, if applicable."
+        ),
         blank=True,
         null=True,
     )
@@ -129,6 +151,9 @@ class Notification(Model):
         ContentType,
         verbose_name=_("Action object ContentType"),
         help_text=_("The content type of the action object."),
+        db_comment=_(
+            "The content type of the action object that triggered the notification, if applicable."
+        ),
         on_delete=CASCADE,
         related_name="action_content_type_notifications",
         blank=True,
@@ -137,6 +162,9 @@ class Notification(Model):
     action_object_object_id = PositiveIntegerField(
         verbose_name=_("Action object ID"),
         help_text=_("The ID of the action object."),
+        db_comment=_(
+            "The unique ID of the action object associated with the notification, if applicable."
+        ),
         blank=True,
         null=True,
     )
@@ -146,12 +174,18 @@ class Notification(Model):
     link = URLField(
         verbose_name=_("Link"),
         help_text=_("A URL associated with the action."),
+        db_comment=_(
+            "A URL related to the notification or action, for further details or follow-up."
+        ),
         blank=True,
         null=True,
     )
     is_sent = BooleanField(
         verbose_name=_("Is sent"),
         help_text=_("indicate whether the notification has been sent."),
+        db_comment=_(
+            "A boolean flag indicating whether this notification has already been sent to its recipients."
+        ),
         default=False,
     )
     seen_by = ManyToManyField(
@@ -163,22 +197,29 @@ class Notification(Model):
     public = BooleanField(
         verbose_name=_("Public"),
         help_text=_("Indicate whether the notification is public."),
+        db_comment=_(
+            "A boolean flag indicating if this notification is public or private."
+        ),
         default=True,
     )
     data = JSONField(
         verbose_name=_("data"),
         help_text=_("Additional metadata or custom attributes in JSON format."),
+        db_comment=_(
+            "Stores arbitrary JSON data related to the notification, such as custom attributes or metadata."
+        ),
         blank=True,
         null=True,
     )
     timestamp = DateTimeField(
         verbose_name=_("Timestamp"),
         help_text=_("The time when the notification was created."),
+        db_comment=_("The date and time when this notification was created."),
         default=timezone.now,
         db_index=True,
     )
-    objects = Manager()
-    queryset = NotificationQuerySet.as_manager()
+
+    objects = NotificationDataAccessLayer()
 
     class Meta:
         db_table: str = "notification"
diff --git a/django_notification/models/notification_recipient.py b/django_notification/models/notification_recipient.py
index 0c4f890..3dd1f0e 100644
--- a/django_notification/models/notification_recipient.py
+++ b/django_notification/models/notification_recipient.py
@@ -30,6 +30,10 @@ class NotificationRecipient(Model):
         "Notification",
         verbose_name=_("Notification"),
         help_text=_("The notification that is being sent."),
+        db_comment=_(
+            "Foreign key linking to the Notification model, representing the notification"
+            " that is being sent to the recipient."
+        ),
         on_delete=CASCADE,
         related_name="notification_recipients",
     )
@@ -37,6 +41,10 @@ class NotificationRecipient(Model):
         settings.AUTH_USER_MODEL,
         verbose_name=_("Recipient"),
         help_text=_("The user who will receive the notification."),
+        db_comment=_(
+            "Foreign key linking to the User model (AUTH_USER_MODEL),"
+            " representing the recipient of the notification."
+        ),
         on_delete=CASCADE,
         related_name="received_notifications",
     )
diff --git a/django_notification/models/notification_seen.py b/django_notification/models/notification_seen.py
index be46e4a..be49bd5 100644
--- a/django_notification/models/notification_seen.py
+++ b/django_notification/models/notification_seen.py
@@ -54,6 +54,9 @@ class NotificationSeen(Model):
         "Notification",
         verbose_name=_("Notification"),
         help_text=_("The notification that was seen."),
+        db_comment=_(
+            "Foreign key linking to the Notification model, representing the notification that was viewed by the user."
+        ),
         on_delete=CASCADE,
         related_name="seen",
     )
@@ -61,12 +64,19 @@ class NotificationSeen(Model):
         settings.AUTH_USER_MODEL,
         verbose_name=_("User"),
         help_text=_("The recipient or a group member who has seen the notification."),
+        db_comment=_(
+            "Foreign key linking to the User model (AUTH_USER_MODEL),"
+            " representing the user who has viewed the notification."
+        ),
         on_delete=CASCADE,
         related_name="seen_notifications",
     )
     seen_at = DateTimeField(
         verbose_name=_("Seen at"),
         help_text=_("The time that the notification was seen."),
+        db_comment=_(
+            "A timestamp recording when the notification was marked as seen by the user."
+        ),
         default=timezone.now,
     )
 
diff --git a/django_notification/repository/manager/__init__.py b/django_notification/repository/manager/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_notification/repository/manager/notification.py b/django_notification/repository/manager/notification.py
new file mode 100644
index 0000000..8d1f2f5
--- /dev/null
+++ b/django_notification/repository/manager/notification.py
@@ -0,0 +1,336 @@
+from typing import Optional
+
+from django.contrib.auth.models import Group
+from django.db.models import Manager, Q, QuerySet
+
+from django_notification.constants.qs_types import (
+    ActionObject,
+    Actor,
+    Data,
+    Description,
+    Groups,
+    Link,
+    Recipient,
+    Recipients,
+    Target,
+)
+from django_notification.repository.queryset.notification import NotificationQuerySet
+from django_notification.utils.user_model import UserModel
+
+
+# pylint: disable=too-many-arguments
+class NotificationDataAccessLayer(Manager):
+    """Data Access Layer (DAL) for managing notifications in the application.
+
+    This class provides various methods for creating, updating, deleting, and
+    filtering notifications, allowing the management of notification records
+    based on different criteria such as recipients, groups, status, and visibility.
+
+    Methods:
+        get_queryset: Returns the base notification queryset.
+        create_notification: Creates a new notification with specific details.
+        update_notification: Updates selective fields of an existing notification.
+        delete_notification: Deletes or soft-deletes a notification.
+        sent: Retrieves all sent notifications with optional filters.
+        unsent: Retrieves all unsent notifications with optional filters.
+        seen: Retrieves all notifications seen by a given user.
+        unseen: Retrieves all notifications not seen by a given user.
+        mark_all_as_sent: Marks all unsent notifications as sent.
+        mark_all_as_seen: Marks all unseen notifications for a user as seen.
+        deleted: Retrieves all deleted notifications, optionally filtered by user.
+        clear_all: Clears all seen notifications for a given user, marking them as deleted.
+
+    """
+
+    def get_queryset(self) -> NotificationQuerySet:
+        """Retrieve the base notification queryset for further querying or
+        filtering.
+
+        Returns:
+            NotificationQuerySet: The base queryset containing all notifications.
+
+        """
+        return NotificationQuerySet(model=self.model, using=self._db)
+
+    def create_notification(
+        self,
+        verb: str,
+        actor: Actor,
+        description: Optional[Description] = None,
+        recipients: Optional[Recipients] = None,
+        groups: Optional[Groups] = None,
+        status: str = "INFO",
+        public: bool = True,
+        target: Optional[Target] = None,
+        action_object: Optional[ActionObject] = None,
+        link: Optional[Link] = None,
+        is_sent: bool = False,
+        data: Optional[Data] = None,
+    ) -> "Notification":
+        """Create a new notification with the provided details.
+
+        Args:
+            verb (str): A short phrase describing the notification action (e.g., "commented on").
+            actor (Actor): The user or entity performing the action.
+            description (Optional[Description]): Additional details or context for the notification.
+            recipients (Optional[Recipients]): A list of users who will receive the notification.
+            groups (Optional[Groups]): A list of groups to target for the notification.
+            status (str): The status of the notification (e.g., "INFO", "WARNING"). Defaults to "INFO".
+            public (bool): Whether the notification is publicly visible. Defaults to True.
+            target (Optional[Target]): The object being acted upon, if applicable.
+            action_object (Optional[ActionObject]): The object performing the action, if relevant.
+            link (Optional[Link]): A URL associated with the notification.
+            is_sent (bool): Whether the notification is marked as sent. Defaults to False.
+            data (Optional[Data]): Any additional data related to the notification.
+
+        Returns:
+            Notification: The created notification instance.
+
+        """
+        notification = self.get_queryset().create_notification(
+            verb=verb,
+            actor=actor,
+            description=description,
+            status=status,
+            target=target,
+            action_object=action_object,
+            link=link,
+            public=public,
+            is_sent=is_sent,
+            data=data,
+        )
+
+        if recipients:
+            if isinstance(recipients, UserModel):
+                recipients = [recipients]
+            notification.recipient.add(*recipients)
+
+        if groups:
+            if isinstance(groups, Group):
+                groups = [groups]
+            notification.group.add(*groups)
+
+        return notification
+
+    def update_notification(
+        self,
+        notification_id: int,
+        is_sent: Optional[bool] = None,
+        public: Optional[bool] = None,
+        data: Optional[Data] = None,
+    ) -> "Notification":
+        """Update specified fields of an existing notification.
+
+        Args:
+            notification_id (int): The ID of the notification to be updated.
+            is_sent (Optional[bool]): The sent status to update.
+            public (Optional[bool]): The public visibility to update.
+            data (Optional[Data]): Any additional data to update for the notification.
+
+        Returns:
+            Notification: The updated notification instance.
+
+        """
+        return self.get_queryset().update_notification(
+            notification_id, is_sent, public, data
+        )
+
+    def delete_notification(
+        self,
+        notification_id: int,
+        recipient: Optional[Recipient] = None,
+        soft_delete: bool = True,
+    ) -> None:
+        """Delete or soft-delete a notification based on the provided ID.
+
+        Args:
+            notification_id (int): The ID of the notification to delete.
+            recipient (Optional[Recipient]): The user performing the soft delete. Required for soft deletes.
+            soft_delete (bool): Whether to perform a soft delete. Defaults to True.
+
+        Raises:
+            ValueError: If `recipient` is not provided for soft delete.
+
+        """
+        self.get_queryset().delete_notification(notification_id, recipient, soft_delete)
+
+    def all_notifications(
+        self,
+        recipients: Recipients = None,
+        groups: Groups = None,
+        display_detail: bool = False,
+    ) -> QuerySet:
+        """Return all notifications excluding those that have been deleted.
+
+        This method retrieves all notifications and excludes those that have
+        corresponding entries in the DeletedNotification table, indicating that
+        they have been deleted.
+
+        Args:
+            recipients (User, optional): The recipients of the notifications. Defaults to None.
+            groups (Group, optional): The groups of the notifications. Defaults to None.
+            display_detail (bool, optional): Whether the queryset will be displayed with all fields. Defaults to False.
+
+        Returns:
+            QuerySet: All notifications that have not been deleted.
+
+        """
+        return self.get_queryset().all_notifications(recipients, groups, display_detail)
+
+    def sent(
+        self,
+        recipients: Recipients = None,
+        exclude_deleted_by: Recipient = None,
+        groups: Groups = None,
+        display_detail: bool = False,
+        conditions: Q = Q(),
+    ) -> QuerySet:
+        """Retrieve all sent notifications, optionally filtered by recipients,
+        groups, and conditions.
+
+        Args:
+            recipients (Optional[Recipients]): The intended recipients of the notifications.
+            exclude_deleted_by (Optional[Recipient]): Exclude notifications deleted by this user.
+            groups (Optional[Groups]): Filter notifications by group.
+            display_detail (bool): Whether to display detailed notification fields. Defaults to False.
+            conditions (Q): Additional query conditions to filter the queryset. Defaults to an empty Q().
+
+        Returns:
+            QuerySet: A queryset of sent notifications.
+
+        """
+        return self.get_queryset().sent(
+            recipients, exclude_deleted_by, groups, display_detail, conditions
+        )
+
+    def unsent(
+        self,
+        recipients: Recipients = None,
+        exclude_deleted_by: Recipient = None,
+        groups: Groups = None,
+        display_detail: bool = False,
+        conditions: Q = Q(),
+    ) -> QuerySet:
+        """Retrieve all unsent notifications, with optional filtering.
+
+        Args:
+            recipients (Optional[Recipients]): The intended recipients of the notifications.
+            exclude_deleted_by (Optional[Recipient]): Exclude notifications deleted by this user.
+            groups (Optional[Groups]): Filter notifications by group.
+            display_detail (bool): Whether to display detailed notification fields. Defaults to False.
+            conditions (Q): Additional query conditions to filter the queryset. Defaults to an empty Q().
+
+        Returns:
+            QuerySet: A queryset of unsent notifications.
+
+        """
+        return self.get_queryset().unsent(
+            recipients, exclude_deleted_by, groups, display_detail, conditions
+        )
+
+    def seen(
+        self,
+        seen_by: UserModel,
+        recipients: Recipients = None,
+        groups: Groups = None,
+        display_detail: bool = False,
+        conditions: Q = Q(),
+    ) -> QuerySet:
+        """Retrieve all notifications that have been seen by the given user.
+
+        Args:
+            seen_by (UserModel): The user who has seen the notifications.
+            recipients (Optional[Recipients]): Filter by recipients, if provided.
+            groups (Optional[Groups]): Filter by groups, if provided.
+            display_detail (bool): Whether to display detailed fields for each notification.
+            conditions (Q): Additional query conditions to filter the queryset.
+
+        Returns:
+            QuerySet: A queryset of notifications seen by the user.
+
+        """
+        return self.get_queryset().seen(
+            seen_by, recipients, groups, display_detail, conditions
+        )
+
+    def unseen(
+        self,
+        unseen_by: UserModel,
+        recipients: Recipients = None,
+        groups: Groups = None,
+        display_detail: bool = False,
+        conditions: Q = Q(),
+    ) -> QuerySet:
+        """Retrieve all notifications that have not been seen by the given
+        user.
+
+        Args:
+            unseen_by (UserModel): The user who has not seen the notifications.
+            recipients (Optional[Recipients]): Filter by recipients, if provided.
+            groups (Optional[Groups]): Filter by groups, if provided.
+            display_detail (bool): Whether to display detailed fields for each notification.
+            conditions (Q): Additional query conditions to filter the queryset.
+
+        Returns:
+            QuerySet: A queryset of unseen notifications for the user.
+
+        """
+        return self.get_queryset().unseen(
+            unseen_by, recipients, groups, display_detail, conditions
+        )
+
+    def mark_all_as_sent(
+        self, recipients: Optional[Recipients] = None, groups: Optional[Groups] = None
+    ) -> int:
+        """Mark all unsent notifications as sent.
+
+        Args:
+            recipients (Optional[Recipients]): Recipients to filter unsent notifications.
+            groups (Optional[Groups]): Groups to filter unsent notifications.
+
+        Returns:
+            int: The number of notifications marked as sent.
+
+        """
+        return self.get_queryset().mark_all_as_sent(recipients, groups)
+
+    def mark_all_as_seen(self, user: UserModel) -> int:
+        """Mark all unseen notifications for a user as seen.
+
+        Args:
+            user (UserModel): The user to mark notifications as seen for.
+
+        Returns:
+            int: The number of notifications marked as seen.
+
+        """
+        return self.get_queryset().mark_all_as_seen(user)
+
+    def deleted(self, deleted_by: Recipient = None) -> QuerySet:
+        """Retrieve all notifications that have been deleted, optionally
+        filtered by user.
+
+        Args:
+            deleted_by (Optional[Recipient]): The user to filter deleted notifications by.
+
+        Returns:
+            QuerySet: A queryset of deleted notifications.
+
+        """
+        return self.get_queryset().deleted(deleted_by)
+
+    def clear_all(self, user: UserModel) -> None:
+        """Move notifications to a 'deleted' state for the given recipient.
+
+        This method finds all notifications marked as seen by the recipient
+        and creates corresponding entries in the DeletedNotification table
+        to indicate that these notifications have been deleted.
+
+        Arguments:
+        user (User): The user for whom notifications should be cleared.
+
+        Returns:
+        None
+
+        """
+        self.get_queryset().clear_all(user)
diff --git a/django_notification/repository/queryset/notification.py b/django_notification/repository/queryset/notification.py
index 17e5774..573ec1b 100644
--- a/django_notification/repository/queryset/notification.py
+++ b/django_notification/repository/queryset/notification.py
@@ -213,7 +213,22 @@ def seen(
         display_detail: bool = False,
         conditions: Q = Q(),
     ) -> QuerySet:
-        """Return all seen notifications by the given user."""
+        """Retrieve all notifications that have been seen by the specified
+        user.
+
+        This method filters notifications that have been marked as "seen" by the provided user (`seen_by`). It also supports additional filtering based on recipients, groups, and custom conditions. The method ensures that only sent notifications are retrieved, excluding any that the user has deleted.
+
+        Args:
+            seen_by (UserModel): The user who has seen the notifications.
+            recipients (Optional[Recipients]): Optional filter for the intended recipients of the notifications. Can be a single recipient or multiple.
+            groups (Optional[Groups]): Optional filter for group-based notifications. Can be a single group or multiple.
+            display_detail (bool): If `True`, include detailed information about each notification. Defaults to `False`.
+            conditions (Q): Optional additional query conditions to filter the notifications. Defaults to an empty Q() condition.
+
+        Returns:
+            QuerySet: A queryset of sent notifications that have been seen by the specified user, filtered by recipients and groups if provided.
+
+        """
         conditions &= Q(seen_by=seen_by)
         return self.sent(
             recipients=recipients,
@@ -231,7 +246,22 @@ def unseen(
         display_detail: bool = False,
         conditions: Q = Q(),
     ) -> QuerySet:
-        """Return notifications that the given user has not seen."""
+        """Retrieve all notifications that have not been seen by the specified
+        user.
+
+        This method returns notifications that have been sent but not yet marked as "seen" by the provided user (`unseen_by`). It also supports filtering by recipients, groups, and additional query conditions. Any notifications that have been deleted by the user are excluded from the result.
+
+        Args:
+            unseen_by (UserModel): The user who has not seen the notifications.
+            recipients (Optional[Recipients]): Optional filter for the intended recipients of the notifications. Can be a single recipient or multiple.
+            groups (Optional[Groups]): Optional filter for group-based notifications. Can be a single group or multiple.
+            display_detail (bool): If `True`, include detailed information about each notification. Defaults to `False`.
+            conditions (Q): Optional additional query conditions to filter the notifications. Defaults to an empty Q() condition.
+
+        Returns:
+            QuerySet: A queryset of sent notifications that the specified user has not yet seen, filtered by recipients and groups if provided.
+
+        """
         return self.sent(
             recipients=recipients,
             exclude_deleted_by=unseen_by,
@@ -281,8 +311,16 @@ def mark_all_as_sent(
         ).update(is_sent=True)
 
     def deleted(self, deleted_by: Recipient = None) -> QuerySet:
-        """Return all deleted notifications optionally filtered by the user who
-        delete it."""
+        """Retrieve all notifications that have been deleted, optionally
+        filtered by user.
+
+        Args:
+            deleted_by (Optional[Recipient]): The user to filter deleted notifications by.
+
+        Returns:
+            QuerySet: A queryset of deleted notifications.
+
+        """
 
         deleted_notifications = self._get_deleted_notifications(deleted_by=deleted_by)
         return self.with_related().filter(id__in=Subquery(deleted_notifications))
@@ -316,8 +354,6 @@ def create_notification(
         verb: str,
         actor: Actor,
         description: Description = None,
-        recipients: Recipients = None,
-        groups: Groups = None,
         status: str = "INFO",
         public: bool = True,
         target: Target = None,
@@ -332,8 +368,6 @@ def create_notification(
             verb (str): The verb of the notification.
             description (Optional[str]): The description of the notification.
             actor (Model): The actor of the notification.
-            recipients (Optional[Union[User, QuerySet, List[User]]], optional): The recipient(s) of the notification. Defaults to None.
-            groups (Optional[Union[Group, QuerySet, List[Group]]], optional): The group(s) of the notification. Defaults to None.
             status (Optional[str], optional): The status of the notification. Defaults to "INFO".
             target (Optional[Model], optional): The target of the notification. Defaults to None.
             action_object (Optional[Model], optional): The action object of the notification. Defaults to None.
@@ -374,15 +408,7 @@ def create_notification(
             public=public,
             data=data,
         )
-        if recipients:
-            if isinstance(recipients, UserModel):
-                recipients = [recipients]  # Convert single User object to a list
-            notification.recipient.add(*recipients)  # Use * to unpack the list elements
 
-        if groups:
-            if isinstance(groups, Group):
-                groups = [groups]
-            notification.group.add(*groups)
         return notification
 
     def update_notification(
diff --git a/django_notification/settings/checks.py b/django_notification/settings/checks.py
index 911918a..e1b9c41 100644
--- a/django_notification/settings/checks.py
+++ b/django_notification/settings/checks.py
@@ -38,119 +38,131 @@ def check_notification_settings(app_configs: Any, **kwargs: Any) -> List[Error]:
     # Validate boolean settings
     errors.extend(
         validate_boolean_setting(
-            config.include_soft_delete, "DJANGO_NOTIFICATION_API_INCLUDE_SOFT_DELETE"
+            config.include_soft_delete, f"{config.prefix}API_INCLUDE_SOFT_DELETE"
         )
     )
     errors.extend(
         validate_boolean_setting(
-            config.include_hard_delete, "DJANGO_NOTIFICATION_API_INCLUDE_HARD_DELETE"
+            config.include_hard_delete, f"{config.prefix}API_INCLUDE_HARD_DELETE"
         )
     )
     errors.extend(
         validate_boolean_setting(
             config.admin_has_add_permission,
-            "DJANGO_NOTIFICATION_ADMIN_HAS_ADD_PERMISSION",
+            f"{config.prefix}ADMIN_HAS_ADD_PERMISSION",
         )
     )
     errors.extend(
         validate_boolean_setting(
             config.admin_has_change_permission,
-            "DJANGO_NOTIFICATION_ADMIN_HAS_CHANGE_PERMISSION",
+            f"{config.prefix}ADMIN_HAS_CHANGE_PERMISSION",
         )
     )
     errors.extend(
         validate_boolean_setting(
             config.admin_has_delete_permission,
-            "DJANGO_NOTIFICATION_ADMIN_HAS_DELETE_PERMISSION",
+            f"{config.prefix}ADMIN_HAS_DELETE_PERMISSION",
         )
     )
     errors.extend(
         validate_boolean_setting(
             config.include_serializer_full_details,
-            "DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS",
+            f"{config.prefix}SERIALIZER_INCLUDE_FULL_DETAILS",
         )
     )
     errors.extend(
         validate_boolean_setting(
-            config.api_allow_list, "DJANGO_NOTIFICATION_API_ALLOW_LIST"
+            config.exclude_serializer_null_fields,
+            f"{config.prefix}SERIALIZER_EXCLUDE_NULL_FIELDS",
         )
     )
     errors.extend(
         validate_boolean_setting(
-            config.api_allow_retrieve, "DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE"
+            config.api_allow_list, f"{config.prefix}API_ALLOW_LIST"
+        )
+    )
+    errors.extend(
+        validate_boolean_setting(
+            config.api_allow_retrieve, f"{config.prefix}API_ALLOW_RETRIEVE"
         )
     )
 
     errors.extend(
         validate_list_fields(
-            config.user_serializer_fields, "DJANGO_NOTIFICATION_USER_SERIALIZER_FIELDS"
+            config.user_serializer_fields, f"{config.prefix}USER_SERIALIZER_FIELDS"
         )
     )
 
     errors.extend(
         validate_list_fields(
-            config.api_ordering_fields, "DJANGO_NOTIFICATION_API_ORDERING_FIELDS"
+            config.api_ordering_fields, f"{config.prefix}API_ORDERING_FIELDS"
         )
     )
 
     errors.extend(
         validate_list_fields(
-            config.api_search_fields, "DJANGO_NOTIFICATION_API_SEARCH_FIELDS"
+            config.api_search_fields, f"{config.prefix}API_SEARCH_FIELDS"
         )
     )
 
     errors.extend(
         validate_throttle_rate(
             config.staff_user_throttle_rate,
-            "DJANGO_NOTIFICATION_STAFF_USER_THROTTLE_RATE",
+            f"{config.prefix}STAFF_USER_THROTTLE_RATE",
         )
     )
     errors.extend(
         validate_throttle_rate(
             config.authenticated_user_throttle_rate,
-            "DJANGO_NOTIFICATION_AUTHENTICATED_USER_THROTTLE_RATE",
+            f"{config.prefix}AUTHENTICATED_USER_THROTTLE_RATE",
         )
     )
     errors.extend(
         validate_optional_class_setting(
-            config.get_setting("DJANGO_NOTIFICATION_USER_SERIALIZER_CLASS", None),
-            "DJANGO_NOTIFICATION_USER_SERIALIZER_CLASS",
+            config.get_setting(f"{config.prefix}USER_SERIALIZER_CLASS", None),
+            f"{config.prefix}USER_SERIALIZER_CLASS",
         )
     )
     errors.extend(
         validate_optional_class_setting(
-            config.get_setting("DJANGO_NOTIFICATION_GROUP_SERIALIZER_CLASS", None),
-            "DJANGO_NOTIFICATION_GROUP_SERIALIZER_CLASS",
+            config.get_setting(f"{config.prefix}GROUP_SERIALIZER_CLASS", None),
+            f"{config.prefix}GROUP_SERIALIZER_CLASS",
         )
     )
     errors.extend(
         validate_optional_class_setting(
-            config.get_setting("DJANGO_NOTIFICATION_API_THROTTLE_CLASS", None),
-            "DJANGO_NOTIFICATION_API_THROTTLE_CLASS",
+            config.get_setting(f"{config.prefix}API_THROTTLE_CLASS", None),
+            f"{config.prefix}API_THROTTLE_CLASS",
         )
     )
     errors.extend(
         validate_optional_class_setting(
-            config.get_setting("DJANGO_NOTIFICATION_API_PAGINATION_CLASS", None),
-            "DJANGO_NOTIFICATION_API_PAGINATION_CLASS",
+            config.get_setting(f"{config.prefix}API_PAGINATION_CLASS", None),
+            f"{config.prefix}API_PAGINATION_CLASS",
         )
     )
     errors.extend(
         validate_optional_classes_setting(
-            config.get_setting("DJANGO_NOTIFICATION_API_PARSER_CLASSES", []),
-            "DJANGO_NOTIFICATION_API_PARSER_CLASSES",
+            config.get_setting(f"{config.prefix}API_PARSER_CLASSES", []),
+            f"{config.prefix}API_PARSER_CLASSES",
+        )
+    )
+    errors.extend(
+        validate_optional_class_setting(
+            config.get_setting(f"{config.prefix}API_FILTERSET_CLASS", None),
+            f"{config.prefix}API_FILTERSET_CLASS",
         )
     )
     errors.extend(
         validate_optional_class_setting(
-            config.get_setting("DJANGO_NOTIFICATION_API_FILTERSET_CLASS", None),
-            "DJANGO_NOTIFICATION_API_FILTERSET_CLASS",
+            config.get_setting(f"{config.prefix}API_EXTRA_PERMISSION_CLASS", None),
+            f"{config.prefix}API_EXTRA_PERMISSION_CLASS",
         )
     )
     errors.extend(
         validate_optional_class_setting(
-            config.get_setting("DJANGO_NOTIFICATION_API_EXTRA_PERMISSION_CLASS", None),
-            "DJANGO_NOTIFICATION_API_EXTRA_PERMISSION_CLASS",
+            config.get_setting(f"{config.prefix}ADMIN_SITE_CLASS", None),
+            f"{config.prefix}ADMIN_SITE_CLASS",
         )
     )
 
diff --git a/django_notification/settings/conf.py b/django_notification/settings/conf.py
index b092658..2d2b88e 100644
--- a/django_notification/settings/conf.py
+++ b/django_notification/settings/conf.py
@@ -4,7 +4,7 @@
 from django.utils.module_loading import import_string
 
 from django_notification.constants.default_settings import (
-    DefaultAdminPermSettings,
+    DefaultAdminSettings,
     DefaultAPISettings,
     DefaultPaginationAndFilteringSettings,
     DefaultSerializerSettings,
@@ -38,9 +38,11 @@ class NotificationConfig:
 
     """
 
+    prefix = "DJANGO_NOTIFICATION_"
+
     default_api_settings: DefaultAPISettings = DefaultAPISettings()
     default_serializer_settings: DefaultSerializerSettings = DefaultSerializerSettings()
-    default_admin_perm_settings: DefaultAdminPermSettings = DefaultAdminPermSettings()
+    default_admin_settings: DefaultAdminSettings = DefaultAdminSettings()
     default_pagination_and_filter_settings: DefaultPaginationAndFilteringSettings = (
         DefaultPaginationAndFilteringSettings()
     )
@@ -50,88 +52,96 @@ def __init__(self) -> None:
         """Initialize the NotificationConfig, loading values from Django
         settings or falling back to the default API settings."""
         self.include_soft_delete: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_API_INCLUDE_SOFT_DELETE",
+            f"{self.prefix}API_INCLUDE_SOFT_DELETE",
             self.default_api_settings.include_soft_delete,
         )
         self.include_hard_delete: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_API_INCLUDE_HARD_DELETE",
+            f"{self.prefix}API_INCLUDE_HARD_DELETE",
             self.default_api_settings.include_hard_delete,
         )
         self.admin_has_add_permission: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_ADMIN_HAS_ADD_PERMISSION",
-            self.default_admin_perm_settings.admin_has_add_permission,
+            f"{self.prefix}ADMIN_HAS_ADD_PERMISSION",
+            self.default_admin_settings.admin_has_add_permission,
         )
         self.admin_has_change_permission: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_ADMIN_HAS_CHANGE_PERMISSION",
-            self.default_admin_perm_settings.admin_has_change_permission,
+            f"{self.prefix}ADMIN_HAS_CHANGE_PERMISSION",
+            self.default_admin_settings.admin_has_change_permission,
         )
         self.admin_has_delete_permission: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_ADMIN_HAS_DELETE_PERMISSION",
-            self.default_admin_perm_settings.admin_has_delete_permission,
+            f"{self.prefix}ADMIN_HAS_DELETE_PERMISSION",
+            self.default_admin_settings.admin_has_delete_permission,
         )
 
         self.include_serializer_full_details: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS",
+            f"{self.prefix}SERIALIZER_INCLUDE_FULL_DETAILS",
             self.default_api_settings.include_serializer_full_details,
         )
+        self.exclude_serializer_null_fields: bool = self.get_setting(
+            f"{self.prefix}SERIALIZER_EXCLUDE_NULL_FIELDS",
+            self.default_api_settings.exclude_serializer_none_fields,
+        )
 
         self.api_allow_list: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_API_ALLOW_LIST", self.default_api_settings.allow_list
+            f"{self.prefix}API_ALLOW_LIST", self.default_api_settings.allow_list
         )
         self.api_allow_retrieve: bool = self.get_setting(
-            "DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE",
+            f"{self.prefix}API_ALLOW_RETRIEVE",
             self.default_api_settings.allow_retrieve,
         )
         self.user_serializer_fields: List[str] = self.get_setting(
-            "DJANGO_NOTIFICATION_USER_SERIALIZER_FIELDS",
+            f"{self.prefix}USER_SERIALIZER_FIELDS",
             self.default_serializer_settings.user_serializer_fields,
         )
         self.user_serializer_class: Optional[Type[Any]] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_USER_SERIALIZER_CLASS",
+            f"{self.prefix}USER_SERIALIZER_CLASS",
             self.default_serializer_settings.user_serializer_class,
         )
         self.group_serializer_class: Optional[Type[Any]] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_GROUP_SERIALIZER_CLASS",
+            f"{self.prefix}GROUP_SERIALIZER_CLASS",
             self.default_serializer_settings.group_serializer_class,
         )
         self.authenticated_user_throttle_rate: str = self.get_setting(
-            "DJANGO_NOTIFICATION_AUTHENTICATED_USER_THROTTLE_RATE",
+            f"{self.prefix}AUTHENTICATED_USER_THROTTLE_RATE",
             self.default_throttle_settings.authenticated_user_throttle_rate,
         )
         self.staff_user_throttle_rate: str = self.get_setting(
-            "DJANGO_NOTIFICATION_STAFF_USER_THROTTLE_RATE",
+            f"{self.prefix}STAFF_USER_THROTTLE_RATE",
             self.default_throttle_settings.staff_user_throttle_rate,
         )
         self.api_throttle_class: Optional[Type[Any]] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_API_THROTTLE_CLASS",
+            f"{self.prefix}API_THROTTLE_CLASS",
             self.default_throttle_settings.throttle_class,
         )
         self.api_pagination_class: Optional[Type[Any]] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_API_PAGINATION_CLASS",
+            f"{self.prefix}API_PAGINATION_CLASS",
             self.default_pagination_and_filter_settings.pagination_class,
         )
         self.api_extra_permission_class: Optional[
             Type[Any]
         ] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_API_EXTRA_PERMISSION_CLASS",
+            f"{self.prefix}API_EXTRA_PERMISSION_CLASS",
             self.default_api_settings.extra_permission_class,
         )
         self.api_parser_classes: Optional[List[Type[Any]]] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_API_PARSER_CLASSES",
+            f"{self.prefix}API_PARSER_CLASSES",
             self.default_api_settings.parser_classes,
         )
         self.api_filterset_class: Optional[Type[Any]] = self.get_optional_classes(
-            "DJANGO_NOTIFICATION_API_FILTERSET_CLASS",
+            f"{self.prefix}API_FILTERSET_CLASS",
             self.default_pagination_and_filter_settings.filterset_class,
         )
         self.api_ordering_fields: List[str] = self.get_setting(
-            "DJANGO_NOTIFICATION_API_ORDERING_FIELDS",
+            f"{self.prefix}API_ORDERING_FIELDS",
             self.default_pagination_and_filter_settings.ordering_fields,
         )
         self.api_search_fields: List[str] = self.get_setting(
-            "DJANGO_NOTIFICATION_API_SEARCH_FIELDS",
+            f"{self.prefix}API_SEARCH_FIELDS",
             self.default_pagination_and_filter_settings.search_fields,
         )
+        self.admin_site_class: Optional[Type[Any]] = self.get_optional_classes(
+            f"{self.prefix}ADMIN_SITE_CLASS",
+            self.default_admin_settings.admin_site_class,
+        )
 
     def get_setting(self, setting_name: str, default_value: Any) -> Any:
         """Retrieve a setting from Django settings with a default fallback.


Test patch:
diff --git a/django_notification/tests/api/serializers/test_group.py b/django_notification/tests/api/serializers/test_group.py
index f4071e1..9a9353e 100644
--- a/django_notification/tests/api/serializers/test_group.py
+++ b/django_notification/tests/api/serializers/test_group.py
@@ -1,9 +1,11 @@
 import sys
+from unittest.mock import patch
 
 import pytest
 from django.contrib.auth.models import Group
 from rest_framework.exceptions import ValidationError
 from django_notification.api.serializers.group import GroupSerializer
+from django_notification.settings.conf import config
 from django_notification.utils.serialization.field_filters import (
     filter_non_empty_fields,
 )
@@ -23,6 +25,7 @@ class TestGroupSerializer:
     Test the GroupSerializer and PermissionSerializer functionality.
     """
 
+    @patch.object(config, "exclude_serializer_null_fields", False)
     def test_group_serializer_with_valid_data(self, group_with_perm: Group) -> None:
         """
         Test that the GroupSerializer correctly serializes a group with permissions.
diff --git a/django_notification/tests/api/serializers/test_simple_notification_serializer.py b/django_notification/tests/api/serializers/test_simple_notification_serializer.py
index 1331d10..7196967 100644
--- a/django_notification/tests/api/serializers/test_simple_notification_serializer.py
+++ b/django_notification/tests/api/serializers/test_simple_notification_serializer.py
@@ -1,11 +1,13 @@
 import sys
 from typing import Dict, Any
+from unittest.mock import patch
 
 import pytest
 
 from django_notification.api.serializers.simple_notification import (
     SimpleNotificationSerializer,
 )
+from django_notification.settings.conf import config
 from django_notification.utils.serialization.field_filters import (
     filter_non_empty_fields,
 )
@@ -52,6 +54,7 @@ def test_serializer_fields(self, notification_dict: Dict[str, Any]) -> None:
         serializer = SimpleNotificationSerializer(notification_dict)
         assert set(serializer.data.keys()) == set(expected_fields)
 
+    @patch.object(config, "exclude_serializer_null_fields", False)
     @mark.django_db
     def test_title_generation(self, notification_dict: Dict[str, Any]) -> None:
         """
diff --git a/django_notification/tests/api/views/test_activity.py b/django_notification/tests/api/views/test_activity.py
index 84ea3da..6b6e155 100644
--- a/django_notification/tests/api/views/test_activity.py
+++ b/django_notification/tests/api/views/test_activity.py
@@ -57,7 +57,7 @@ def test_get_queryset_for_staff(
         assert response.status_code == 200
         assert (
             len(response.data.get("results"))
-            == Notification.queryset.seen(seen_by=admin_user).count()
+            == Notification.objects.seen(seen_by=admin_user).count()
         )
 
     def test_get_queryset_for_non_staff(
@@ -82,7 +82,7 @@ def test_get_queryset_for_non_staff(
         assert response.status_code == 200
         assert (
             len(response.data.get("results"))
-            == Notification.queryset.seen(seen_by=user, recipients=user).count()
+            == Notification.objects.seen(seen_by=user, recipients=user).count()
         )
 
     @patch.object(config, "api_allow_list", False)
@@ -174,7 +174,7 @@ def test_clear_activities(self, user: User, notification: Notification) -> None:
         response = self.client.get(url)
         assert response.status_code == 204  # No Content
         assert (
-            Notification.queryset.sent().count() == DeletedNotification.objects.count()
+            Notification.objects.sent().count() == DeletedNotification.objects.count()
         )
 
     def test_clear_notification(
@@ -227,7 +227,7 @@ def test_delete_activities(
         url = reverse("activities-delete-activities")
         response = self.client.get(url)
         assert response.status_code == 204  # No Content
-        assert not Notification.queryset.all_notifications()
+        assert not Notification.objects.all_notifications()
 
     @patch.object(config, "include_hard_delete", True)
     def test_delete_notification(
@@ -251,4 +251,4 @@ def test_delete_notification(
         url = reverse("activities-delete-notification", kwargs={"pk": notification.pk})
         response = self.client.get(url)
         assert response.status_code == 204  # No Content
-        assert not Notification.queryset.filter(pk=notification.pk).exists()
+        assert not Notification.objects.filter(pk=notification.pk).exists()
diff --git a/django_notification/tests/api/views/test_notification.py b/django_notification/tests/api/views/test_notification.py
index 5a07f84..4f80780 100644
--- a/django_notification/tests/api/views/test_notification.py
+++ b/django_notification/tests/api/views/test_notification.py
@@ -36,6 +36,7 @@ def setup_method(self) -> None:
         """
         self.client = APIClient()
 
+    @patch.object(config, "exclude_serializer_null_fields", False)
     def test_get_queryset_for_staff(
         self, admin_user: Type[User], notification: Notification
     ) -> None:
@@ -61,7 +62,7 @@ def test_get_queryset_for_staff(
         assert response.status_code == 200
         assert (
             len(response.data.get("results", []))
-            == Notification.queryset.unseen(unseen_by=admin_user).count()
+            == Notification.objects.unseen(unseen_by=admin_user).count()
         )
 
     def test_get_queryset_for_non_staff(
@@ -86,7 +87,7 @@ def test_get_queryset_for_non_staff(
         assert response.status_code == 200
         assert (
             len(response.data.get("results", []))
-            == Notification.queryset.unseen(unseen_by=user).count()
+            == Notification.objects.unseen(unseen_by=user).count()
         )
 
     def test_retrieve_notification(
@@ -110,7 +111,7 @@ def test_retrieve_notification(
         url = reverse("notifications-detail", kwargs={"pk": notification.pk})
         response = self.client.get(url)
         assert response.status_code == 200
-        assert Notification.queryset.seen(seen_by=user).exists()
+        assert Notification.objects.seen(seen_by=user).exists()
 
     def test_mark_all_as_seen(
         self, user: Type[User], notification: Notification
diff --git a/django_notification/tests/constants.py b/django_notification/tests/constants.py
index c2cde2a..766fa3f 100644
--- a/django_notification/tests/constants.py
+++ b/django_notification/tests/constants.py
@@ -1,2 +1,2 @@
-PYTHON_VERSION = (3, 8)
-PYTHON_VERSION_REASON = "Requires Python 3.8 or higher"
+PYTHON_VERSION = (3, 9)
+PYTHON_VERSION_REASON = "Requires Python 3.9 or higher"
diff --git a/django_notification/tests/repository/test_queryset.py b/django_notification/tests/repository/test_notification_manager.py
similarity index 91%
rename from django_notification/tests/repository/test_queryset.py
rename to django_notification/tests/repository/test_notification_manager.py
index d6f0523..f211f01 100644
--- a/django_notification/tests/repository/test_queryset.py
+++ b/django_notification/tests/repository/test_notification_manager.py
@@ -8,7 +8,7 @@
 from django_notification.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON
 
 pytestmark = [
-    pytest.mark.queryset,
+    pytest.mark.managers,
     pytest.mark.skipif(sys.version_info < PYTHON_VERSION, reason=PYTHON_VERSION_REASON),
 ]
 
@@ -16,7 +16,7 @@
 @pytest.mark.django_db
 class TestNotificationQuerySet:
     """
-    Test suite for the `NotificationQuerySet`.
+    Test suite for the `NotificationDataAccessLayer`.
     """
 
     def test_all_notifications_with_recipients(
@@ -35,7 +35,7 @@ def test_all_notifications_with_recipients(
             The count of notifications returned by `all_notifications` with the given recipients
             matches the number of provided notifications.
         """
-        queryset = Notification.queryset.all_notifications(recipients=qs_user)
+        queryset = Notification.objects.all_notifications(recipients=qs_user)
         assert queryset.count() == len(notifications)
 
     def test_all_notifications_with_groups(
@@ -54,7 +54,7 @@ def test_all_notifications_with_groups(
             The count of notifications returned by `all_notifications` with the given groups
             matches the number of provided notifications.
         """
-        queryset = Notification.queryset.all_notifications(groups=qs_group)
+        queryset = Notification.objects.all_notifications(groups=qs_group)
         assert queryset.count() == len(notifications)
 
     def test_all_notifications_with_recipients_and_groups(
@@ -77,7 +77,7 @@ def test_all_notifications_with_recipients_and_groups(
             The count of notifications returned by `all_notifications` with the given recipients
             and groups matches the number of provided notifications.
         """
-        queryset = Notification.queryset.all_notifications(
+        queryset = Notification.objects.all_notifications(
             recipients=qs_user, groups=qs_group
         )
         assert queryset.count() == len(notifications)
@@ -98,7 +98,7 @@ def test_sent_with_groups(
             The count of notifications returned by `sent` with the given groups matches the number
             of notifications that have been marked as sent.
         """
-        queryset = Notification.queryset.sent(groups=qs_group)
+        queryset = Notification.objects.sent(groups=qs_group)
         assert queryset.count() == len([n for n in notifications if n.is_sent])
 
     def test_unsent(self, notifications: List[Notification]) -> None:
@@ -114,7 +114,7 @@ def test_unsent(self, notifications: List[Notification]) -> None:
             The count of notifications returned by `unsent` matches the number of notifications
             that have not been marked as sent.
         """
-        queryset = Notification.queryset.unsent()
+        queryset = Notification.objects.unsent()
         assert queryset.count() == len([n for n in notifications if not n.is_sent])
 
     def test_unsent_with_recipients_and_groups(
@@ -137,7 +137,7 @@ def test_unsent_with_recipients_and_groups(
             The count of notifications returned by `unsent` with the given recipients and groups
             matches the number of unsent notifications.
         """
-        queryset = Notification.queryset.unsent(recipients=qs_user, groups=qs_group)
+        queryset = Notification.objects.unsent(recipients=qs_user, groups=qs_group)
         assert queryset.count() == len([n for n in notifications if not n.is_sent])
 
     def test_unsent_exclude_deleted(
@@ -156,7 +156,7 @@ def test_unsent_exclude_deleted(
             The count of notifications returned by `unsent` with excluded deleted notifications
             is greater than 0.
         """
-        queryset = Notification.queryset.unsent(exclude_deleted_by=qs_user)
+        queryset = Notification.objects.unsent(exclude_deleted_by=qs_user)
         assert queryset.count() > 0
 
     def test_mark_as_sent(self, notifications: List[Notification]) -> None:
@@ -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_all_as_sent()
+        Notification.objects.mark_all_as_sent()
         assert True
 
     def test_deleted(
@@ -190,7 +190,7 @@ def test_deleted(
             The count of notifications returned by `deleted` is 0, indicating no notifications
             are marked as deleted yet.
         """
-        deleted = Notification.queryset.deleted(deleted_by=qs_user)
+        deleted = Notification.objects.deleted(deleted_by=qs_user)
         assert deleted.count() == 0  # Assuming no notifications are deleted yet
 
     def test_create_notification_with_groups(
@@ -209,7 +209,7 @@ def test_create_notification_with_groups(
         -------
             The created notification should have the specified groups.
         """
-        notification = Notification.queryset.create_notification(
+        notification = Notification.objects.create_notification(
             verb="Test",
             actor=another_user,
             recipients=qs_user,
@@ -232,7 +232,7 @@ def test_update_notification(self, notifications: List[Notification]) -> None:
             The updated notification should have the new attributes applied.
         """
         notification = notifications[1]
-        updated_notification = Notification.queryset.update_notification(
+        updated_notification = Notification.objects.update_notification(
             notification_id=notification.id,
             is_sent=True,
             public=False,
@@ -258,7 +258,7 @@ def test_delete_notification_without_recipient(
         """
         notification = notifications[0]
         with pytest.raises(ValueError):
-            Notification.queryset.delete_notification(
+            Notification.objects.delete_notification(
                 notification_id=notification.id, recipient=None, soft_delete=True
             )
 
@@ -278,7 +278,7 @@ def test_delete_notification_with_recipient(
             The deleted notification should exist in the DeletedNotification model.
         """
         notification = notifications[2]
-        Notification.queryset.delete_notification(
+        Notification.objects.delete_notification(
             notification_id=notification.id, recipient=qs_user, soft_delete=True
         )
         assert DeletedNotification.objects.filter(
diff --git a/django_notification/tests/settings/test_checks.py b/django_notification/tests/settings/test_checks.py
index da4a56c..de26707 100644
--- a/django_notification/tests/settings/test_checks.py
+++ b/django_notification/tests/settings/test_checks.py
@@ -33,6 +33,7 @@ def test_valid_settings(self, mock_config: MagicMock) -> None:
         mock_config.admin_has_change_permission = False
         mock_config.admin_has_delete_permission = False
         mock_config.include_serializer_full_details = True
+        mock_config.exclude_serializer_null_fields = True
         mock_config.api_allow_list = True
         mock_config.api_allow_retrieve = False
         mock_config.user_serializer_fields = ["id", "username"]
@@ -66,7 +67,8 @@ def test_invalid_boolean_settings(self, mock_config: MagicMock) -> None:
         mock_config.admin_has_add_permission = "not_boolean"
         mock_config.admin_has_change_permission = "not_boolean"
         mock_config.admin_has_delete_permission = "not_boolean"
-        mock_config.include_serializer_full_details = True
+        mock_config.include_serializer_full_details = "not_bool"
+        mock_config.exclude_serializer_null_fields = "not_boolean"
         mock_config.user_serializer_fields = ["id", "username"]
         mock_config.api_ordering_fields = ["created_at"]
         mock_config.api_search_fields = ["title"]
@@ -78,31 +80,39 @@ def test_invalid_boolean_settings(self, mock_config: MagicMock) -> None:
 
         errors = check_notification_settings(None)
 
-        # Expect 6 errors for invalid boolean values
-        assert len(errors) == 6
+        # Expect 8 errors for invalid boolean values
+        assert len(errors) == 8
         assert (
             errors[0].id
-            == "django_notification.E001_DJANGO_NOTIFICATION_API_INCLUDE_SOFT_DELETE"
+            == f"django_notification.E001_{mock_config.prefix}API_INCLUDE_SOFT_DELETE"
         )
         assert (
             errors[1].id
-            == "django_notification.E001_DJANGO_NOTIFICATION_API_INCLUDE_HARD_DELETE"
+            == f"django_notification.E001_{mock_config.prefix}API_INCLUDE_HARD_DELETE"
         )
         assert (
             errors[2].id
-            == "django_notification.E001_DJANGO_NOTIFICATION_ADMIN_HAS_ADD_PERMISSION"
+            == f"django_notification.E001_{mock_config.prefix}ADMIN_HAS_ADD_PERMISSION"
         )
         assert (
             errors[3].id
-            == "django_notification.E001_DJANGO_NOTIFICATION_ADMIN_HAS_CHANGE_PERMISSION"
+            == f"django_notification.E001_{mock_config.prefix}ADMIN_HAS_CHANGE_PERMISSION"
         )
         assert (
             errors[4].id
-            == "django_notification.E001_DJANGO_NOTIFICATION_ADMIN_HAS_DELETE_PERMISSION"
+            == f"django_notification.E001_{mock_config.prefix}ADMIN_HAS_DELETE_PERMISSION"
         )
         assert (
-            errors[5].id
-            == "django_notification.E001_DJANGO_NOTIFICATION_API_ALLOW_LIST"
+                errors[5].id
+                == f"django_notification.E001_{mock_config.prefix}SERIALIZER_INCLUDE_FULL_DETAILS"
+        )
+        assert (
+                errors[6].id
+                == f"django_notification.E001_{mock_config.prefix}SERIALIZER_EXCLUDE_NULL_FIELDS"
+        )
+        assert (
+            errors[7].id
+            == f"django_notification.E001_{mock_config.prefix}API_ALLOW_LIST"
         )
 
     @patch("django_notification.settings.checks.config")
@@ -125,6 +135,7 @@ def test_invalid_list_settings(self, mock_config: MagicMock) -> None:
         mock_config.admin_has_change_permission = False
         mock_config.admin_has_delete_permission = False
         mock_config.include_serializer_full_details = True
+        mock_config.exclude_serializer_null_fields = True
         mock_config.api_allow_list = True
         mock_config.api_allow_retrieve = False
         mock_config.user_serializer_fields = []
@@ -140,15 +151,15 @@ def test_invalid_list_settings(self, mock_config: MagicMock) -> None:
         assert len(errors) == 3
         assert (
             errors[0].id
-            == "django_notification.E003_DJANGO_NOTIFICATION_USER_SERIALIZER_FIELDS"
+            == f"django_notification.E003_{mock_config.prefix}USER_SERIALIZER_FIELDS"
         )
         assert (
             errors[1].id
-            == "django_notification.E003_DJANGO_NOTIFICATION_API_ORDERING_FIELDS"
+            == f"django_notification.E003_{mock_config.prefix}API_ORDERING_FIELDS"
         )
         assert (
             errors[2].id
-            == "django_notification.E004_DJANGO_NOTIFICATION_API_SEARCH_FIELDS"
+            == f"django_notification.E004_{mock_config.prefix}API_SEARCH_FIELDS"
         )
 
     @patch("django_notification.settings.checks.config")
@@ -171,6 +182,7 @@ def test_invalid_throttle_rate(self, mock_config: MagicMock) -> None:
         mock_config.admin_has_change_permission = False
         mock_config.admin_has_delete_permission = False
         mock_config.include_serializer_full_details = True
+        mock_config.exclude_serializer_null_fields = True
         mock_config.api_allow_list = True
         mock_config.api_allow_retrieve = False
         mock_config.user_serializer_fields = ["id", "username"]
@@ -207,6 +219,7 @@ def test_invalid_class_import(self, mock_config: MagicMock) -> None:
         mock_config.admin_has_change_permission = False
         mock_config.admin_has_delete_permission = False
         mock_config.include_serializer_full_details = True
+        mock_config.exclude_serializer_null_fields = True
         mock_config.api_allow_list = True
         mock_config.api_allow_retrieve = False
         mock_config.user_serializer_fields = ["id", "username"]
@@ -220,33 +233,37 @@ def test_invalid_class_import(self, mock_config: MagicMock) -> None:
 
         errors = check_notification_settings(None)
 
-        # Expect 7 errors for invalid class imports
-        assert len(errors) == 7
+        # Expect 8 errors for invalid class imports
+        assert len(errors) == 8
         assert (
             errors[0].id
-            == "django_notification.E010_DJANGO_NOTIFICATION_USER_SERIALIZER_CLASS"
+            == f"django_notification.E010_{mock_config.prefix}USER_SERIALIZER_CLASS"
         )
         assert (
             errors[1].id
-            == "django_notification.E010_DJANGO_NOTIFICATION_GROUP_SERIALIZER_CLASS"
+            == f"django_notification.E010_{mock_config.prefix}GROUP_SERIALIZER_CLASS"
         )
         assert (
             errors[2].id
-            == "django_notification.E010_DJANGO_NOTIFICATION_API_THROTTLE_CLASS"
+            == f"django_notification.E010_{mock_config.prefix}API_THROTTLE_CLASS"
         )
         assert (
             errors[3].id
-            == "django_notification.E010_DJANGO_NOTIFICATION_API_PAGINATION_CLASS"
+            == f"django_notification.E010_{mock_config.prefix}API_PAGINATION_CLASS"
         )
         assert (
             errors[4].id
-            == "django_notification.E011_DJANGO_NOTIFICATION_API_PARSER_CLASSES"
+            == f"django_notification.E011_{mock_config.prefix}API_PARSER_CLASSES"
         )
         assert (
             errors[5].id
-            == "django_notification.E010_DJANGO_NOTIFICATION_API_FILTERSET_CLASS"
+            == f"django_notification.E010_{mock_config.prefix}API_FILTERSET_CLASS"
         )
         assert (
             errors[6].id
-            == "django_notification.E010_DJANGO_NOTIFICATION_API_EXTRA_PERMISSION_CLASS"
+            == f"django_notification.E010_{mock_config.prefix}API_EXTRA_PERMISSION_CLASS"
+        )
+        assert (
+                errors[7].id
+                == f"django_notification.E010_{mock_config.prefix}ADMIN_SITE_CLASS"
         )
diff --git a/django_notification/tests/setup.py b/django_notification/tests/setup.py
index 4fa12ff..c39d29f 100644
--- a/django_notification/tests/setup.py
+++ b/django_notification/tests/setup.py
@@ -93,6 +93,7 @@ def configure_django_settings() -> None:
             DJANGO_NOTIFICATION_ADMIN_HAS_ADD_PERMISSION=False,
             DJANGO_NOTIFICATION_ADMIN_HAS_CHANGE_PERMISSION=False,
             DJANGO_NOTIFICATION_ADMIN_HAS_DELETE_PERMISSION=False,
+            DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS=True,
             DJANGO_NOTIFICATION_API_ALLOW_LIST=True,
             DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE=True,
             DJANGO_NOTIFICATION_AUTHENTICATED_USER_THROTTLE_RATE="20/minute",