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

Issue:
Documentation is not up to date with the package
The current documentation needs to be updated to reflect the recent additions and changes in functionality


Discussion / hints:

Fix patch:
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index a2d7c88..7fa7d46 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -51,6 +51,8 @@ we’re thrilled that you want to contribute to `dj-notification-api`! to ensure
 
 10. **Export Dependencies**: Export the project dependencies for development and production:
     ```bash
+    pip install poetry-plugin-export
+
     poetry export -f requirements.txt --output packages/requirements.txt --without-hashes
     poetry export -f requirements.txt --dev --output packages/requirements-dev.txt --without-hashes
     ```
diff --git a/README.md b/README.md
index 08eb873..69bb484 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,12 @@
 [![Open Issues](https://img.shields.io/github/issues/lazarus-org/dj-notification-api)](https://github.com/lazarus-org/dj-notification-api/issues)
 [![Last Commit](https://img.shields.io/github/last-commit/lazarus-org/dj-notification-api)](https://github.com/lazarus-org/dj-notification-api/commits/main)
 [![Languages](https://img.shields.io/github/languages/top/lazarus-org/dj-notification-api)](https://github.com/lazarus-org/dj-notification-api)
-[![Coverage](https://img.shields.io/codecov/c/github/lazarus-org/dj-notification-api/main)](https://codecov.io/gh/lazarus-org/dj-notification-api)
+[![Coverage](https://codecov.io/gh/lazarus-org/dj-notification-api/branch/main/graph/badge.svg)](https://codecov.io/gh/lazarus-org/dj-notification-api)
 
-[`dj-notification-api`](https://github.com/lazarus-org/dj-notification-api/) is a Django package developed by Lazarus for efficiently handling notifications through various APIs.
+[`dj-notification-api`](https://github.com/lazarus-org/dj-notification-api/) is a Django package developed by Lazarus designed to simplify and optimize the process of managing notifications through various APIs.
+With this package, you can easily integrate a notification system into your Django project by just installing it. it offers a fully customizable notification API that can be tailored to suit your specific needs.
+
+It also provides an intuitive and powerful admin interface, allowing you to manage, track, and configure notifications effortlessly. Enjoy the flexibility and performance optimizations built into the package.
 
 ## Project Detail
 
@@ -61,13 +64,15 @@ $ pipenv install dj-notification-api
 
 ## 2. Add to Installed Apps
 
-Once installed, add django_notification to the INSTALLED_APPS in your Django `settings.py` file:
+Once installed,  ensure that both `rest_framework` and `django_notification` are added to the `INSTALLED_APPS` in your Django `settings.py` file::
 
-```shell
+```python
 INSTALLED_APPS = [
-    ...
+    # ...
+    "rest_framework",  # Required for API support
+
     "django_notification",
-    ...
+    # ...
 ]
 ```
 
@@ -77,11 +82,11 @@ To enable filtering of notifications through the API, include `django_filters` i
 
 Add `django_filters` to your `INSTALLED_APPS`:
 
-```shell
+```python
 INSTALLED_APPS = [
-    ...
+    # ...
     "django_filters",
-    ...
+    # ...
 ]
 ```
 
@@ -126,7 +131,7 @@ actor = User.objects.get(username="admin")
 recipient = User.objects.get(username="john_doe")
 
 # Create a new notification
-Notification.queryset.create_notification(
+Notification.objects.create_notification(
     verb="Logged in to Admin panel",
     actor=actor,
     recipients=[recipient],
@@ -250,10 +255,13 @@ Content-Type: application/json
                 "email": "example@domain.com"
             }
         ],
+        "group": [],
         "verb": "Logged in to Admin panel",
         "status": "INFO",
-        "link": "<link>",
         "actor_content_type": 4,
+        "target_content_type": null,
+        "action_object_content_type": null,
+        "link": "<link>",
         "is_sent": true,
         "seen_by": [
             {
@@ -262,6 +270,7 @@ Content-Type: application/json
             }
         ],
         "public": true,
+        "data": null,
         "timestamp": "2024-09-05T13:34:20.969193Z"
     }
 ]
@@ -346,6 +355,10 @@ HTTP/1.1 204 No Content
 
 "detail": "Notification 3 deleted."
 
+```
+**Note**: you can exclude Any fields with a `null` value in the response output by adding this config in your `settings.py`:
+```python
+DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS = True
 ```
 
 ## Throttling
@@ -407,6 +420,38 @@ Each feature can be configured through the Django settings file. For further det
 
 This section provides a comprehensive guide on how to utilize the package's key features, including the functionality of the Django admin panels for managing notifications and deleted notifications, and queryset methods for handling notifications.
 
+## Admin Site
+
+If you are using a **custom admin site** in your project, you must pass your custom admin site configuration in your Django settings. Otherwise, Django may raise the following error during checks:
+
+```shell
+ERRORS:
+<class 'django_notification.admin.deleted_notification.DeletedNotificationAdmin'>:
+ (admin.E039) An admin for model "User" has to be registered to be referenced by DeletedNotificationAdmin.autocomplete_fields.
+```
+To resolve this, In your `settings.py`, add the following setting to specify the path to your custom admin site class instance:
+```python
+DJANGO_NOTIFICATION_ADMIN_SITE_CLASS = "path.to.your.custom.site"
+```
+example of a custom Admin Site:
+```python
+from django.contrib.admin import AdminSite
+
+class CustomAdminSite(AdminSite):
+    site_header = "Custom Admin"
+    site_title = "Custom Admin Portal"
+    index_title = "Welcome to the Custom Admin Portal"
+
+# Instantiate the custom admin site as example
+example_admin_site = CustomAdminSite(name='custom_admin')
+```
+and then reference the instance like this:
+
+```python
+DJANGO_NOTIFICATION_ADMIN_SITE_CLASS = "path.to.example_admin_site"
+```
+This setup allows `dj-notification-api` to use your custom admin site for it's Admin interface, preventing any errors and ensuring a smooth integration with the custom admin interface.
+
 ## Notifications Admin Panel
 
 The `NotificationAdmin` class provides a comprehensive admin interface for managing notifications in the Django admin panel. The features and functionality are described below:
@@ -520,9 +565,129 @@ Users can perform searching based on the following functionality:
 
 ---
 
-## QuerySet Methods
+## NotificationDataAccessLayer (Manager)
+
+The `django_notification` package provides a Manager class with various methods to interact with notifications in different contexts. Users typically use `Notification.objects.create_notification()` to create notifications, but other methods are available for querying and managing notifications. Below is an overview of the available methods:
+
+### Return All Notifications
+
+This method retrieves all notifications. It allows filtering based on recipients and groups.
+
+**Method Signature**
+
+```shell
+from django_notification.models.notification import Notification
+
+Notification.objects.all_notifications(
+    recipients,
+    groups,
+    display_detail,
+) -> QuerySet
+```
+**Arguments:**
+
+- **recipients** (`Optional[Union[UserModel, QuerySet, List[UserModel]]]`):
+  Optional filter for notifications based on recipients. Can be:
+  - A single `UserModel` instance.
+  - A `QuerySet` of `UserModel` instances.
+  - A list of `UserModel` instances.
+
+  If provided, the method returns notifications where the recipients are among the specified recipients.
+
+- **groups** (`Optional[Union[Group, QuerySet, List[Group]]]`):
+  Optional filter for notifications based on groups. Can be:
+  - A single `Group` instance.
+  - A `QuerySet` of `Group` instances.
+  - A list of `Group` instances.
+
+  If provided, the method returns notifications where the groups are among the specified groups.
+
+- **display_detail** (`Optional[bool]`):
+  Indicates whether to return simplified details.
+  - If `True`, the method returns a dictionary of simplified details for each notification using `.values()`.
+  - If `False`, the method returns a `QuerySet` of notification instances.
+
+**Returns:**
+
+- A `QuerySet` of all notifications, or a dictionary of simplified details if `display_detail` is `True`.
+
+**Example Usage:**
+
+To retrieve all notifications for a specific user:
+
+```python
+from django_notification.models.notification import Notification
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+user_instance = User.objects.first()
 
-The `django_notification` package provides a QuerySet class with various methods to interact with notifications in different contexts. Users typically use `Notification.queryset.create_notification()` to create notifications, but other methods are available for querying and managing notifications. Below is an overview of the available methods:
+all_notifications = Notification.objects.all_notifications(recipients=user_instance)
+```
+
+### Return All sent Notifications
+
+This method retrieves all sent notifications, excluding those that have been soft-deleted by the specified user. It allows filtering based on recipients, groups, and additional conditions.
+
+**Method Signature**
+
+```shell
+from django_notification.models.notification import Notification
+
+Notification.objects.sent(
+    recipients,
+    exclude_deleted_by,
+    groups,
+    display_detail,
+    conditions,
+) -> QuerySet
+```
+**Arguments:**
+
+- **recipients** (`Optional[Union[UserModel, QuerySet, List[UserModel]]]`):
+  Optional filter for notifications based on recipients. Can be:
+  - A single `UserModel` instance.
+  - A `QuerySet` of `UserModel` instances.
+  - A list of `UserModel` instances.
+
+  If provided, the method returns notifications where the recipients are among the specified recipients.
+
+- **exclude_deleted_by** (`Optional[UserModel]`):
+  Optional filter to exclude notifications that have been soft-deleted by a specific user. If provided, the method excludes all notifications that have been marked as deleted by this user.
+
+- **groups** (`Optional[Union[Group, QuerySet, List[Group]]]`):
+  Optional filter for notifications based on groups. Can be:
+  - A single `Group` instance.
+  - A `QuerySet` of `Group` instances.
+  - A list of `Group` instances.
+
+  If provided, the method returns notifications where the groups are among the specified groups.
+
+- **display_detail** (`Optional[bool]`):
+  Indicates whether to return simplified details.
+  - If `True`, the method returns a dictionary of simplified details for each notification using `.values()`.
+  - If `False`, the method returns a `QuerySet` of notification instances.
+
+- **conditions** (`Optional[Q]`):
+  Additional filter conditions. Accepts a `Q` object from `django.db.models` for specifying extra conditions that may be needed for various contexts. Defaults to `Q()` (no additional conditions).
+
+**Returns:**
+
+- A `QuerySet` of sent notifications, or a dictionary of simplified details if `display_detail` is `True`.
+
+**Example Usage:**
+
+To retrieve all sent notifications for a specific user, excluding those deleted by a user:
+
+```python
+from django_notification.models.notification import Notification
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+user_instance = User.objects.first()
+
+sent_notifications = Notification.objects.sent(recipients=user_instance, exclude_deleted_by=user_instance)
+```
 
 ### Return All Unsent Notifications
 
@@ -533,7 +698,7 @@ This method retrieves all unsent notifications, excluding those that have been s
 ```shell
 from django_notification.models.notification import Notification
 
-unsent(
+Notification.objects.unsent(
     recipients,
     exclude_deleted_by,
     groups,
@@ -576,12 +741,16 @@ unsent(
 
 **Example Usage:**
 
-To retrieve all unsent notifications for a specific user, excluding those deleted by a different user:
+To retrieve all unsent notifications for a specific user, excluding those deleted by a user:
 
 ```python
 from django_notification.models.notification import Notification
+from django.contrib.auth import get_user_model
 
-unsent_notifications = Notification.queryset.unsent(recipients=user_instance, exclude_deleted_by=user_instance)
+User = get_user_model()
+user_instance = User.objects.first()
+
+unsent_notifications = Notification.objects.unsent(recipients=user_instance, exclude_deleted_by=user_instance)
 ```
 
 ### Return All Seen Notifications
@@ -591,7 +760,7 @@ This method returns all notifications that have been seen by the given user.
 **Method Signature**
 
 ```shell
-seen(
+Notification.objects.seen(
     seen_by,
     recipients,
     groups,
@@ -607,7 +776,7 @@ This method returns all notifications that have been seen by the given user.
 **Method Signature**
 
 ```shell
-seen(
+Notification.objects.seen(
     seen_by,
     recipients,
     groups,
@@ -651,7 +820,7 @@ This method returns all notifications that the given user has not seen.
 **Method Signature**
 
 ```shell
-unseen(
+Notification.objects.unseen(
     unseen_by,
     recipients,
     groups,
@@ -693,7 +862,7 @@ This method marks all notifications as seen by the specified user if they are re
 **Method Signature**
 
 ```shell
-mark_all_as_seen(user) -> int
+Notification.objects.mark_all_as_seen(user) -> int
 ```
 
 **Arguments:**
@@ -713,7 +882,7 @@ This method marks notifications as sent for the specified recipients or groups.
 **Method Signature**
 
 ```shell
-mark_as_sent(
+Notification.objects.mark_as_sent(
     recipients,
     groups,
 ) -> int
@@ -739,7 +908,7 @@ This method returns all deleted (soft deleted) notifications, optionally filtere
 **Method Signature**
 
 ```shell
-deleted(
+Notification.objects.deleted(
     deleted_by=None
 ) -> QuerySet
 ```
@@ -761,7 +930,7 @@ This method moves notifications for the specified user into a 'deleted' state by
 **Method Signature**
 
 ```shell
-clear_all(user) -> None
+Notification.objects.clear_all(user) -> None
 ```
 
 **Arguments:**
@@ -783,7 +952,7 @@ The `create_notification` method provides a convenient way to generate and store
 ```shell
 from django_notification.models.notification import Notification
 
-Notification.queryset.create_notification(
+Notification.objects.create_notification(
     verb,
     actor,
     description,
@@ -833,7 +1002,7 @@ recipients = [User.objects.get(username='jane_doe')]
 description = "John Doe logged in to the admin panel."
 
 # Creating a notification
-notification = Notification.queryset.create_notification(
+notification = Notification.objects.create_notification(
     verb="Logged in to admin panel",
     actor=actor,
     description=description,
@@ -855,7 +1024,7 @@ This method updates some editable fields of a notification by its ID.
 **Method Signature**
 
 ```shell
-update_notification(
+Notification.objects.update_notification(
     notification_id,
     is_sent=None,
     public=None,
@@ -869,10 +1038,10 @@ update_notification(
   The ID of the notification to update.
 
 - **is_sent** (`bool`):
-  The updated sent status of the notification. Defaults to `True`.
+  The updated sent status of the notification.
 
 - **public** (`bool`):
-  The updated public status of the notification. Defaults to `True`.
+  The updated public status of the notification.
 
 - **data** (`Optional[JSONField]`):
   Optional additional data to store with the notification.
@@ -889,7 +1058,7 @@ This method deletes a notification by its ID. If `soft_delete` is `True`, it mov
 **Method Signature**
 
 ```shell
-delete_notification(
+Notification.objects.delete_notification(
     notification_id,
     recipient=None,
     soft_delete=True
@@ -927,7 +1096,9 @@ DJANGO_NOTIFICATION_API_INCLUDE_HARD_DELETE = False
 DJANGO_NOTIFICATION_ADMIN_HAS_ADD_PERMISSION = False
 DJANGO_NOTIFICATION_ADMIN_HAS_CHANGE_PERMISSION = False
 DJANGO_NOTIFICATION_ADMIN_HAS_DELETE_PERMISSION = False
+DJANGO_NOTIFICATION_ADMIN_SITE_CLASS = None
 DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS = False
+DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS = False
 DJANGO_NOTIFICATION_API_ALLOW_LIST = True
 DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE = True
 # DJANGO_NOTIFICATION_USER_SERIALIZER_FIELDS = [if not provided, gets USERNAME_FIELD and REQUIRED_FIELDS from user model]
@@ -992,6 +1163,14 @@ Below is a detailed description of each setting, so you can better understand an
 
 ----
 
+### `DJANGO_NOTIFICATION_ADMIN_SITE_CLASS`
+
+**Type**: `Optional[str]`
+**Default**: `None`
+**Description**: Optionally specifies A custom AdminSite class to apply on Admin interface. This allows for more customization on Admin interface, enabling you to apply your AdminSite class into `dj-notification-api` Admin interface.
+
+----
+
 ### `DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS`
 
 **Type**: `bool`
@@ -1000,6 +1179,14 @@ Below is a detailed description of each setting, so you can better understand an
 
 ----
 
+### `DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS`
+
+**Type**: `bool`
+**Default**: `False`
+**Description**: When set to `True`, API responses will exclude any fields that it's value is `null`.
+
+----
+
 ### `DJANGO_NOTIFICATION_API_ALLOW_LIST`
 
 **Type**: `bool`
@@ -1060,7 +1247,7 @@ Below is a detailed description of each setting, so you can better understand an
 
 **Type**: `str`
 **Default**: `"django_notification.api.throttlings.role_base_throttle.RoleBasedUserRateThrottle"`
-**Description**: Specifies the throttle class used to limit API requests. Customize this or set it to `None` if no throttling is needed.
+**Description**: Specifies the throttle class used to limit API requests. Customize this or set it to `None` if no throttling is needed or want to use rest_framework `DEFAULT_THROTTLE_CLASSES`.
 
 ----
 
@@ -1085,7 +1272,7 @@ Below is a detailed description of each setting, so you can better understand an
 **Type**: `List[str]`
 **Default**:
 ```python
-[
+DJANGO_NOTIFICATION_API_PARSER_CLASSES = [
   "rest_framework.parsers.JSONParser",
   "rest_framework.parsers.MultiPartParser",
   "rest_framework.parsers.FormParser",
@@ -1104,11 +1291,11 @@ Below is a detailed description of each setting, so you can better understand an
 
 In your `settings.py`:
 
-```shell
+```python
 INSTALLED_APPS = [
-    ...
+    # ...
     "django_filters",
-    ...
+    # ...
 ]
 ```
 
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..fefff3f 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,13 +31,14 @@ 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"]
     )
     search_fields: List[str] = field(default_factory=lambda: ["verb", "description"])
 
 
+# pylint: disable=too-many-instance-attributes
 @dataclass(frozen=True)
 class DefaultAPISettings:
     include_soft_delete: bool = True
@@ -44,6 +46,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.
diff --git a/docs/api_guide.rst b/docs/api_guide.rst
index 511af5e..8889c85 100644
--- a/docs/api_guide.rst
+++ b/docs/api_guide.rst
@@ -95,10 +95,13 @@ Here are some examples of responses for each action:
                   "email": "example@domain.com"
               }
           ],
+          "group": [],
           "verb": "Logged in to Admin panel",
           "status": "INFO",
-          "link": "<link>",
           "actor_content_type": 4,
+          "target_content_type": null,
+          "action_object_content_type": null,
+          "link": "<link>",
           "is_sent": true,
           "seen_by": [
               {
@@ -107,6 +110,7 @@ Here are some examples of responses for each action:
               }
           ],
           "public": true,
+          "data": null,
           "timestamp": "2024-09-05T13:34:20.969193Z"
       }
    ]
@@ -196,6 +200,10 @@ This response is returned when ``DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DET
 
    "detail": "Notification 3 deleted."
 
+**Note**: you can exclude Any fields with a ``null`` value in the response output by adding this config in your ``settings.py``:
+.. code-block:: python
+
+   DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS = True
 
 Throttling
 ----------
@@ -223,7 +231,7 @@ Options include:
 
 - **Filtering**: By default filtering feature is not included, If you want to use this, you need to add ``django_filters`` to your `INSTALLED_APPS` and provide the path to the ``NotificationFilter`` class (``"django_notification.api.filters.notification_filter.NotificationFilter"``). Alternatively, you can use a custom filter class if needed.
 
-  - **Note**: for more clearification, refer to the `DJANGO_NOTIFICATION_API_FILTERSET_CLASS` in :doc:`Settings <settings>` section.
+  - **Note**: for more clarification, refer to the `DJANGO_NOTIFICATION_API_FILTERSET_CLASS` in :doc:`Settings <settings>` section.
 
 - **Ordering**: Results can be ordered by fields such as ``id``, ``timestamp``, or ``public``.
 
diff --git a/docs/index.rst b/docs/index.rst
index 1272fcf..783678a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -55,7 +55,10 @@ Welcome to dj-notification-api Documentation!
 
 |br|
 
-`dj-notification-api` is a Django package created by Lazarus to streamline the management of notifications via APIs. It provides robust features for creating, retrieving, and organizing notifications, making it easy to integrate notification functionality into your Django applications.
+`dj-notification-api` is a Django package developed by Lazarus designed to simplify and optimize the process of managing notifications through various APIs.
+With this package, you can easily integrate a notification system into your Django project by just installing it. it offers a fully customizable notification API that can be tailored to suit your specific needs.
+
+It also provides an intuitive and powerful admin interface, allowing you to manage, track, and configure notifications effortlessly. Enjoy the flexibility and performance optimizations built into the package.
 
 Supported Versions
 ------------------
diff --git a/docs/quick_start.rst b/docs/quick_start.rst
index 2e3f9bc..b14e57b 100644
--- a/docs/quick_start.rst
+++ b/docs/quick_start.rst
@@ -34,29 +34,30 @@ If you're using pipenv, install the package with:
 2. Add to Installed Apps
 ------------------------
 
-Once installed, add `django_notification` to the `INSTALLED_APPS` in your Django `settings.py` file:
+Once installed,  ensure that both ``rest_framework`` and ``django_notification`` are added to the ``INSTALLED_APPS`` in your Django ``settings.py`` file:
 
-.. code-block:: shell
+.. code-block:: python
 
    INSTALLED_APPS = [
-       ...
+       # ...
+       "rest_framework",  # Required for API support
        "django_notification",
-       ...
+       # ...
    ]
 
 3. (Optional) Configure API Filters
 -----------------------------------
 
-To enable filtering of notifications through the API, include ``django_filters`` in your `INSTALLED_APPS` and configure the filter settings.
+To enable filtering of notifications through the API, include ``django_filters`` in your ``INSTALLED_APPS`` and configure the filter settings.
 
 Add `django_filters` to your `INSTALLED_APPS`:
 
-.. code-block:: shell
+.. code-block:: python
 
    INSTALLED_APPS = [
-       ...
+       # ...
        "django_filters",
-       ...
+       # ...
    ]
 
 Then, set the filter class configuration in your ``settings.py``:
@@ -114,7 +115,7 @@ To create notifications and use them in your project, use the `Notification` mod
    recipient = User.objects.get(username="john_doe")
 
    # Create a new notification
-   Notification.queryset.create_notification(
+   Notification.objects.create_notification(
        verb="Logged in to Admin panel",
        actor=actor,
        recipients=[recipient],
diff --git a/docs/settings.rst b/docs/settings.rst
index c73ab94..1dac1a3 100644
--- a/docs/settings.rst
+++ b/docs/settings.rst
@@ -15,7 +15,9 @@ Below is an example configuration with default values:
     DJANGO_NOTIFICATION_ADMIN_HAS_ADD_PERMISSION = False
     DJANGO_NOTIFICATION_ADMIN_HAS_CHANGE_PERMISSION = False
     DJANGO_NOTIFICATION_ADMIN_HAS_DELETE_PERMISSION = False
+    DJANGO_NOTIFICATION_ADMIN_SITE_CLASS = None
     DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS = False
+    DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS = False
     DJANGO_NOTIFICATION_API_ALLOW_LIST = True
     DJANGO_NOTIFICATION_API_ALLOW_RETRIEVE = True
     # DJANGO_NOTIFICATION_USER_SERIALIZER_FIELDS = [if not provided, gets USERNAME_FIELD and REQUIRED_FIELDS from user model]
@@ -97,6 +99,16 @@ Below is a detailed description of each setting, so you can better understand an
 
 ----
 
+``DJANGO_NOTIFICATION_ADMIN_SITE_CLASS``:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+**Type**: ``Optional[str]``
+
+**Default**: ``None``
+
+**Description**: Optionally specifies A custom AdminSite class to apply on Admin interface. This allows for more customization on Admin interface, enabling you to apply your AdminSite class into `dj-notification-api` Admin interface.
+
+----
+
 ``DJANGO_NOTIFICATION_SERIALIZER_INCLUDE_FULL_DETAILS``:
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 **Type**: ``bool``
@@ -107,6 +119,16 @@ Below is a detailed description of each setting, so you can better understand an
 
 ----
 
+``DJANGO_NOTIFICATION_SERIALIZER_EXCLUDE_NULL_FIELDS``:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+**Type**: ``bool``
+
+**Default**: ``False``
+
+**Description**: When set to ``True``, API responses will exclude any fields that it's value is ``null``.
+
+----
+
 ``DJANGO_NOTIFICATION_API_ALLOW_LIST``:
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 **Type**: ``bool``
@@ -183,7 +205,7 @@ Below is a detailed description of each setting, so you can better understand an
 
 **Default**: ``"django_notification.api.throttlings.role_base_throttle.RoleBasedUserRateThrottle"``
 
-**Description**:  Specifies the throttle class used to limit API requests. Customize this or set it to ``None`` if no throttling is needed.
+**Description**:  Specifies the throttle class used to limit API requests. Customize this or set it to ``None`` if no throttling is needed or want to use ``rest_framework`` `DEFAULT_THROTTLE_CLASSES`.
 
 ----
 
@@ -214,7 +236,7 @@ Below is a detailed description of each setting, so you can better understand an
 **Default**:
   .. code-block:: python
 
-    [
+    DJANGO_NOTIFICATION_API_PARSER_CLASSES = [
         "rest_framework.parsers.JSONParser",
         "rest_framework.parsers.MultiPartParser",
         "rest_framework.parsers.FormParser",
@@ -222,6 +244,7 @@ Below is a detailed description of each setting, so you can better understand an
 
 **Description**: Specifies the parsers used to handle API request data formats. You can modify this list to add your parsers or set ``None`` if no parser needed.
 
+----
 
 ``DJANGO_NOTIFICATION_API_FILTERSET_CLASS``:
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -233,14 +256,12 @@ Below is a detailed description of each setting, so you can better understand an
 
 in your settings.py:
 
-.. code-block:: shell
+.. code-block:: python
 
   INSTALLED_APPS = [
-
-  ...
-  "django_filters",
-  ...
-
+      # ...
+      "django_filters",
+      # ...
   ]
 
 and then apply this setting:
diff --git a/docs/usage.rst b/docs/usage.rst
index 9c14f8f..9e07e42 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -1,8 +1,48 @@
 Usage
 =====
 
-This section provides a comprehensive guide on how to utilize the package's key features, including the functionality of the Django admin panels for managing notifications and deleted notifications, and queryset methods for handling notifications.
+This section provides a comprehensive guide on how to utilize the package's key features, including the functionality of the Django admin panels for managing notifications and deleted notifications, and Manager methods for handling notifications.
 
+Admin Site
+----------
+
+If you are using a **custom admin site** in your project, you must pass your custom admin site configuration in your Django settings. Otherwise, Django may raise the following error during checks:
+
+.. code-block:: shell
+
+  ERRORS:
+  <class 'django_notification.admin.deleted_notification.DeletedNotificationAdmin'>:
+   (admin.E039) An admin for model "User" has to be registered to be referenced by DeletedNotificationAdmin.autocomplete_fields.
+
+
+To resolve this, In your ``settings.py``, add the following setting to specify the path to your custom admin site class instance
+
+.. code-block:: python
+
+  DJANGO_NOTIFICATION_ADMIN_SITE_CLASS = "path.to.your.custom.site"
+
+example of a custom Admin Site:
+.. code-block:: python
+
+  from django.contrib.admin import AdminSite
+
+
+  class CustomAdminSite(AdminSite):
+      site_header = "Custom Admin"
+      site_title = "Custom Admin Portal"
+      index_title = "Welcome to the Custom Admin Portal"
+
+
+  # Instantiate the custom admin site as example
+  example_admin_site = CustomAdminSite(name="custom_admin")
+
+and then reference the instance like this:
+
+.. code-block:: python
+
+  DJANGO_NOTIFICATION_ADMIN_SITE_CLASS = "path.to.example_admin_site"
+
+This setup allows `dj-notification-api` to use your custom admin site for it's Admin interface, preventing any errors and ensuring a smooth integration with the custom admin interface.
 
 Notifications Admin Panel
 -------------------------
@@ -129,16 +169,16 @@ Users can do the searching based on this functionality:
 
 ----
 
-QuerySet Methods
-----------------
+NotificationDataAccessLayer (Manager)
+-------------------------------------
 
-The ``django_notification`` provides a QuerySet Class with various methods to interact with notifications in different contexts. Users typically use `Notification.queryset.create_notification()` to create notifications, but other methods are available for querying and managing notifications. Below is an overview of the available methods:
+The ``django_notification`` provides a Manager Class with various methods to interact with notifications in different contexts. Users typically use `Notification.objects.create_notification()` to create notifications, but other methods are available for querying and managing notifications. Below is an overview of the available methods:
 
 
 Return All Notifications
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
-The `all_notifications` method retrieves all notifications that have not been deleted. It provides filtering options based on recipients and groups, and can return simplified details if specified.
+The ``all_notifications`` method retrieves all notifications that have not been deleted. It provides filtering options based on recipients and groups, and can return simplified details if specified.
 
 **Method Signature**
 
@@ -146,7 +186,7 @@ The `all_notifications` method retrieves all notifications that have not been de
 
     from django_notification.models.notification import Notification
 
-    all_notifications(recipients, groups, display_detail)
+    Notification.objects.all_notifications(recipients, groups, display_detail)
 
 **Arguments:**
 
@@ -186,7 +226,7 @@ To retrieve all notifications for a specific user:
 
     from django_notification.models.notification import Notification
 
-    notifications = Notification.queryset.all_notifications(
+    notifications = Notification.objects.all_notifications(
         recipients=user_instance, display_detail=True
     )
 
@@ -194,7 +234,7 @@ To retrieve all notifications for a specific user:
 Return All Sent Notifications
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This method retrieves all sent notifications, excluding those that have been soft-deleted by the specified user. It allows filtering based on recipients, groups, and additional conditions.
+The ``sent`` method retrieves all sent notifications, excluding those that have been soft-deleted by the specified user. It allows filtering based on recipients, groups, and additional conditions.
 
 **Method Signature**
 
@@ -202,7 +242,7 @@ This method retrieves all sent notifications, excluding those that have been sof
 
     from django_notification.models.notification import Notification
 
-    sent(
+    Notification.objects.sent(
         recipients,
         exclude_deleted_by,
         groups,
@@ -254,7 +294,7 @@ To retrieve all sent notifications for a specific user, excluding those deleted
 
     from django_notification.models.notification import Notification
 
-    notifications = Notification.queryset.sent(
+    notifications = Notification.objects.sent(
         recipients=user_instance, exclude_deleted_by=user_instance
     )
 
@@ -262,21 +302,21 @@ To retrieve all sent notifications for a specific user, excluding those deleted
 Return All Unsent Notifications
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This method retrieves all unsent notifications, excluding those that have been soft-deleted by the specified user. It allows filtering based on recipients, groups, and additional conditions.
+The ``unsent`` method retrieves all unsent notifications, excluding those that have been soft-deleted by the specified user. It allows filtering based on recipients, groups, and additional conditions.
 
 **Method Signature**
 
 .. code-block:: shell
 
-    from django_notification.models.notification import Notification
+  from django_notification.models.notification import Notification
 
-    unsent(
-        recipients,
-        exclude_deleted_by,
-        groups,
-        display_detail,
-        conditions,
-    ) -> QuerySet
+  Notification.objects.unsent(
+      recipients,
+      exclude_deleted_by,
+      groups,
+      display_detail,
+      conditions,
+  ) -> QuerySet
 
 **Arguments:**
 
@@ -322,7 +362,7 @@ To retrieve all unsent notifications for a specific user, excluding those delete
 
     from django_notification.models.notification import Notification
 
-    unsent_notifications = Notification.queryset.unsent(
+    unsent_notifications = Notification.objects.unsent(
         recipients=user_instance, exclude_deleted_by=user_instance
     )
 
@@ -331,19 +371,21 @@ To retrieve all unsent notifications for a specific user, excluding those delete
 Return All Seen Notifications
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This method returns all notifications that have been seen by the given user.
+The ``seen`` method returns all notifications that have been seen by the given user.
 
 **Method Signature**
 
 .. code-block:: shell
 
-    seen(
-        seen_by,
-        recipients,
-        groups,
-        display_detail,
-        conditions,
-    ) -> QuerySet
+  from django_notification.models.notification import Notification
+
+  Notification.objects.seen(
+      seen_by,
+      recipients,
+      groups,
+      display_detail,
+      conditions,
+  ) -> QuerySet
 
 **Arguments:**
 
@@ -379,19 +421,21 @@ This method returns all notifications that have been seen by the given user.
 Return All Unseen Notifications
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This method returns all notifications that the given user has not seen.
+The ``unseen`` method returns all notifications that the given user has not seen.
 
 **Method Signature**
 
 .. code-block:: shell
 
-    unseen(
-        unseen_by,
-        recipients,
-        groups,
-        display_detail,
-        conditions,
-    ) -> QuerySet
+  from django_notification.models.notification import Notification
+
+  Notification.objects.unseen(
+      unseen_by,
+      recipients,
+      groups,
+      display_detail,
+      conditions,
+  ) -> QuerySet
 
 
 **Arguments:**
@@ -423,15 +467,20 @@ This method returns all notifications that the given user has not seen.
 
 - A `QuerySet` of unseen notifications, or a dictionary of simplified details if `display_detail` is `True`.
 
+----
 
+Mark All as Seen
+------------------------------
 
-This method marks all notifications as seen by the specified user if they are recipients or group members or ADMIN.
+The ``mark_all_as_seen`` method marks all notifications as seen by the specified user if they are recipients or group members or ADMIN.
 
 **Method Signature**
 
 .. code-block:: shell
 
-    mark_all_as_seen(user) -> int
+  from django_notification.models.notification import Notification
+
+  Notification.objects.mark_all_as_seen(user) -> int
 
 **Arguments:**
 
@@ -453,7 +502,9 @@ This method marks notifications as sent for the specified recipients or groups.
 
 .. code-block:: shell
 
-    mark_as_sent(
+    from django_notification.models.notification import Notification
+
+    Notification.objects.mark_as_sent(
     recipients,
     groups,
     ) -> int
@@ -481,9 +532,11 @@ This method returns all deleted (soft deleted) notifications, optionally filtere
 
 .. code-block:: shell
 
-    deleted(
-        deleted_by=None
-    ) -> QuerySet
+  from django_notification.models.notification import Notification
+
+  Notification.objects.deleted(
+      deleted_by=None
+  ) -> QuerySet
 
 **Arguments:**
 
@@ -504,7 +557,9 @@ This method moves notifications for the specified user into a 'deleted' state by
 
 .. code-block:: shell
 
-    clear_all(user) -> None
+  from django_notification.models.notification import Notification
+
+  Notification.objects.clear_all(user) -> None
 
 **Arguments:**
 
@@ -528,7 +583,7 @@ The `create_notification` method provides a convenient way to generate and store
 
     from django_notification.models.notification import Notification
 
-    Notification.queryset.create_notification(
+    Notification.objects.create_notification(
         verb,
         actor,
         description,
@@ -580,7 +635,7 @@ Here's an example of how to use the ``create_notification`` method to generate a
     description = "John Doe logged in to the admin panel."
 
     # Creating a notification
-    notification = Notification.queryset.create_notification(
+    notification = Notification.objects.create_notification(
         verb="Logged in to admin panel",
         actor=actor,
         description=description,
@@ -604,7 +659,11 @@ This method updates some editable fields of a notification by its ID.
 
 .. code-block:: python
 
-    update_notification(notification_id, is_sent=None, public=None, data=None)
+  from django_notification.models.notification import Notification
+
+  Notification.objects.update_notification(
+      notification_id, is_sent=None, public=None, data=None
+  )
 
 **Arguments:**
 
@@ -635,11 +694,13 @@ This method deletes a notification by its ID. If `soft_delete` is `True`, it mov
 
 .. code-block:: shell
 
-    delete_notification(
-        notification_id,
-        recipient=None,
-        soft_delete=True
-    ) -> None
+  from django_notification.models.notification import Notification
+
+  Notification.objects.delete_notification(
+      notification_id,
+      recipient=None,
+      soft_delete=True
+  ) -> None
 
 **Arguments:**
 
diff --git a/poetry.lock b/poetry.lock
index fbbe386..1945dd3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -117,13 +117,13 @@ tzdata = ["tzdata"]
 
 [[package]]
 name = "bandit"
-version = "1.7.9"
+version = "1.7.10"
 description = "Security oriented static analyser for python code."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"},
-    {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"},
+    {file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
+    {file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
 ]
 
 [package.dependencies]
@@ -390,13 +390,13 @@ files = [
 
 [[package]]
 name = "commitizen"
-version = "3.29.0"
+version = "3.29.1"
 description = "Python commitizen client tool"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "commitizen-3.29.0-py3-none-any.whl", hash = "sha256:0c6c479dbee6d19292315c6fca3782cf5c1f7f1638bc4bb5ab4cfb67f4e11894"},
-    {file = "commitizen-3.29.0.tar.gz", hash = "sha256:586b30c1976850d244b836cd4730771097ba362c9c1684d1f8c379176c2ea532"},
+    {file = "commitizen-3.29.1-py3-none-any.whl", hash = "sha256:83f6563fae6a6262238e4424c55db5743eaa9827d2044dc23719466e4e78a0ca"},
+    {file = "commitizen-3.29.1.tar.gz", hash = "sha256:b9a56190f4f3b20c73600e5ba448c7b81e0e6f87be3092aec1db4de75bf0fa91"},
 ]
 
 [package.dependencies]
@@ -593,19 +593,19 @@ Django = ">=4.2"
 
 [[package]]
 name = "django-stubs"
-version = "5.0.4"
+version = "5.1.0"
 description = "Mypy stubs for Django"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "django_stubs-5.0.4-py3-none-any.whl", hash = "sha256:c2502f5ecbae50c68f9a86d52b5b2447d8648fd205036dad0ccb41e19a445927"},
-    {file = "django_stubs-5.0.4.tar.gz", hash = "sha256:78e3764488fdfd2695f12502136548ec22f8d4b1780541a835042b8238d11514"},
+    {file = "django_stubs-5.1.0-py3-none-any.whl", hash = "sha256:b98d49a80aa4adf1433a97407102d068de26c739c405431d93faad96dd282c40"},
+    {file = "django_stubs-5.1.0.tar.gz", hash = "sha256:86128c228b65e6c9a85e5dc56eb1c6f41125917dae0e21e6cfecdf1b27e630c5"},
 ]
 
 [package.dependencies]
 asgiref = "*"
 django = "*"
-django-stubs-ext = ">=5.0.4"
+django-stubs-ext = ">=5.1.0"
 tomli = {version = "*", markers = "python_version < \"3.11\""}
 types-PyYAML = "*"
 typing-extensions = ">=4.11.0"
@@ -617,13 +617,13 @@ redis = ["redis"]
 
 [[package]]
 name = "django-stubs-ext"
-version = "5.0.4"
+version = "5.1.0"
 description = "Monkey-patching and extensions for django-stubs"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "django_stubs_ext-5.0.4-py3-none-any.whl", hash = "sha256:910cbaff3d1e8e806a5c27d5ddd4088535aae8371ea921b7fd680fdfa5f14e30"},
-    {file = "django_stubs_ext-5.0.4.tar.gz", hash = "sha256:85da065224204774208be29c7d02b4482d5a69218a728465c2fbe41725fdc819"},
+    {file = "django_stubs_ext-5.1.0-py3-none-any.whl", hash = "sha256:a455fc222c90b30b29ad8c53319559f5b54a99b4197205ddbb385aede03b395d"},
+    {file = "django_stubs_ext-5.1.0.tar.gz", hash = "sha256:ed7d51c0b731651879fc75f331fb0806d98b67bfab464e96e2724db6b46ef926"},
 ]
 
 [package.dependencies]
@@ -701,18 +701,18 @@ test = ["pytest (>=6)"]
 
 [[package]]
 name = "filelock"
-version = "3.16.0"
+version = "3.16.1"
 description = "A platform independent file lock."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "filelock-3.16.0-py3-none-any.whl", hash = "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609"},
-    {file = "filelock-3.16.0.tar.gz", hash = "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec"},
+    {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
+    {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
 ]
 
 [package.extras]
-docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.1.1)", "pytest (>=8.3.2)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.3)"]
+docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
 typing = ["typing-extensions (>=4.12.2)"]
 
 [[package]]
@@ -763,13 +763,13 @@ license = ["ukkonen"]
 
 [[package]]
 name = "idna"
-version = "3.9"
+version = "3.10"
 description = "Internationalized Domain Names in Applications (IDNA)"
 optional = false
 python-versions = ">=3.6"
 files = [
-    {file = "idna-3.9-py3-none-any.whl", hash = "sha256:69297d5da0cc9281c77efffb4e730254dd45943f45bbfb461de5991713989b1e"},
-    {file = "idna-3.9.tar.gz", hash = "sha256:e5c5dafde284f26e9e0f28f6ea2d6400abd5ca099864a67f576f3981c6476124"},
+    {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+    {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
 ]
 
 [package.extras]
@@ -1092,13 +1092,13 @@ files = [
 
 [[package]]
 name = "platformdirs"
-version = "4.3.3"
+version = "4.3.6"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "platformdirs-4.3.3-py3-none-any.whl", hash = "sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5"},
-    {file = "platformdirs-4.3.3.tar.gz", hash = "sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0"},
+    {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
+    {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
 ]
 
 [package.extras]
@@ -1155,18 +1155,18 @@ wcwidth = "*"
 
 [[package]]
 name = "pydantic"
-version = "2.9.1"
+version = "2.9.2"
 description = "Data validation using Python type hints"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"},
-    {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"},
+    {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
+    {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
 ]
 
 [package.dependencies]
 annotated-types = ">=0.6.0"
-pydantic-core = "2.23.3"
+pydantic-core = "2.23.4"
 typing-extensions = [
     {version = ">=4.6.1", markers = "python_version < \"3.13\""},
     {version = ">=4.12.2", markers = "python_version >= \"3.13\""},
@@ -1178,100 +1178,100 @@ timezone = ["tzdata"]
 
 [[package]]
 name = "pydantic-core"
-version = "2.23.3"
+version = "2.23.4"
 description = "Core functionality for Pydantic validation and serialization"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"},
-    {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"},
-    {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"},
-    {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"},
-    {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"},
-    {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"},
-    {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"},
-    {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"},
-    {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"},
-    {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"},
-    {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"},
-    {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"},
-    {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"},
-    {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"},
-    {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"},
-    {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"},
-    {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"},
-    {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"},
-    {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"},
-    {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"},
-    {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"},
-    {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
+    {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
+    {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
+    {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
+    {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
+    {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
+    {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
+    {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
+    {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
+    {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
+    {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
+    {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
+    {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
+    {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
+    {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
+    {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
+    {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
+    {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
+    {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
+    {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
+    {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
+    {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
 ]
 
 [package.dependencies]
@@ -1355,13 +1355,13 @@ pylint = ">=1.7"
 
 [[package]]
 name = "pyproject-api"
-version = "1.7.1"
+version = "1.8.0"
 description = "API to interact with the python pyproject.toml based projects"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pyproject_api-1.7.1-py3-none-any.whl", hash = "sha256:2dc1654062c2b27733d8fd4cdda672b22fe8741ef1dde8e3a998a9547b071eeb"},
-    {file = "pyproject_api-1.7.1.tar.gz", hash = "sha256:7ebc6cd10710f89f4cf2a2731710a98abce37ebff19427116ff2174c9236a827"},
+    {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"},
+    {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"},
 ]
 
 [package.dependencies]
@@ -1369,8 +1369,8 @@ packaging = ">=24.1"
 tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
 
 [package.extras]
-docs = ["furo (>=2024.5.6)", "sphinx-autodoc-typehints (>=2.2.1)"]
-testing = ["covdefaults (>=2.3)", "pytest (>=8.2.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=70.1)"]
+docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"]
+testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"]
 
 [[package]]
 name = "pytest"
@@ -1880,13 +1880,13 @@ files = [
 
 [[package]]
 name = "tox"
-version = "4.18.1"
+version = "4.20.0"
 description = "tox is a generic virtualenv management and test command line tool"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "tox-4.18.1-py3-none-any.whl", hash = "sha256:35d472032ee1f73fe20c3e0e73d7073a4e85075c86ff02c576f9fc7c6a15a578"},
-    {file = "tox-4.18.1.tar.gz", hash = "sha256:3c0c96bc3a568a5c7e66387a4cfcf8c875b52e09f4d47c9f7a277ec82f1a0b11"},
+    {file = "tox-4.20.0-py3-none-any.whl", hash = "sha256:21a8005e3d3fe5658a8e36b8ca3ed13a4230429063c5cc2a2fdac6ee5aa0de34"},
+    {file = "tox-4.20.0.tar.gz", hash = "sha256:5b78a49b6eaaeab3ae4186415e7c97d524f762ae967c63562687c3e5f0ec23d5"},
 ]
 
 [package.dependencies]
@@ -1907,13 +1907,13 @@ testing = ["build[virtualenv] (>=1.2.2)", "covdefaults (>=2.3)", "detect-test-po
 
 [[package]]
 name = "types-pyyaml"
-version = "6.0.12.20240808"
+version = "6.0.12.20240917"
 description = "Typing stubs for PyYAML"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "types-PyYAML-6.0.12.20240808.tar.gz", hash = "sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af"},
-    {file = "types_PyYAML-6.0.12.20240808-py3-none-any.whl", hash = "sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35"},
+    {file = "types-PyYAML-6.0.12.20240917.tar.gz", hash = "sha256:d1405a86f9576682234ef83bcb4e6fff7c9305c8b1fbad5e0bcd4f7dbdc9c587"},
+    {file = "types_PyYAML-6.0.12.20240917-py3-none-any.whl", hash = "sha256:392b267f1c0fe6022952462bf5d6523f31e37f6cea49b14cee7ad634b6301570"},
 ]
 
 [[package]]
@@ -1929,13 +1929,13 @@ files = [
 
 [[package]]
 name = "tzdata"
-version = "2024.1"
+version = "2024.2"
 description = "Provider of IANA time zone data"
 optional = false
 python-versions = ">=2"
 files = [
-    {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
-    {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
+    {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
+    {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
 ]
 
 [[package]]
@@ -1967,13 +1967,13 @@ zstd = ["zstandard (>=0.18.0)"]
 
 [[package]]
 name = "virtualenv"
-version = "20.26.4"
+version = "20.26.5"
 description = "Virtual Python Environment builder"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "virtualenv-20.26.4-py3-none-any.whl", hash = "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55"},
-    {file = "virtualenv-20.26.4.tar.gz", hash = "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c"},
+    {file = "virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6"},
+    {file = "virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4"},
 ]
 
 [package.dependencies]
@@ -2018,4 +2018,4 @@ type = ["pytest-mypy"]
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.8,<4.0"
-content-hash = "6ff61355ba75be473fe852cd6b929a15842525574c4186cc6242a68c268cae70"
+content-hash = "f6ca4aaf736318e4e6924e107938c46b4b5d62139eae7d76090ed536ecb6ca68"
diff --git a/pyproject.toml b/pyproject.toml
index c828e21..a4209f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,7 +48,7 @@ django = [
   { version = ">=4.2,<5.0", python = ">=3.8,<3.10" },
   { version = ">=4.2,<5.3", python = ">=3.10" },      # Django 4.2 and 5.x for Python 3.10+
 ]
-djangorestframework = "^3.15.2"
+djangorestframework = "^3.12.0"
 django-filter = "^24.3"
 
 [tool.poetry.group.dev.dependencies]
@@ -193,7 +193,7 @@ markers = [
   "settings_checks: Marks tests for settings validation, ensuring that required settings are correctly configured.",
   "validators: Marks tests for custom validators used throughout the project.",
   "config_validators: Marks tests for configuration validators that validate the config values in project settings.",
-  "queryset: Marks tests for custom queryset methods, ensuring they perform for filtering and querying the database.",
+  "managers: Marks tests for custom managers, ensuring they perform for filtering and querying the database.",
   "decorators: Marks tests for custom Python decorators used in the project.",
   "decorators_action: Marks tests for action-related decorators.",
 ]


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",