Skip to content

Commit

Permalink
[api] Added API endpoints for notifications #3
Browse files Browse the repository at this point in the history
Added API endpoint for listing and marking notifications as read.
  • Loading branch information
pandafy committed Jun 23, 2020
1 parent 04a9eff commit 10e1ff7
Show file tree
Hide file tree
Showing 16 changed files with 560 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ before_install:
- pip install -U pip wheel
- pip install $DJANGO
- pip install -U -r requirements-test.txt
# temporary: remove when openwisp-users with API support is released
- pip install -U https://github.com/openwisp/openwisp-users/tarball/master#egg=openwisp_users[rest]

install:
- python setup.py -q develop
Expand Down
107 changes: 106 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ Setup (integrate into an existing Django project)
'allauth',
'allauth.account',
'allauth.socialaccount',
# rest framework
'rest_framework',
'drf_yasg',
'django_filters',
'openwisp_users',
'django.contrib.admin',
# notifications module
Expand Down Expand Up @@ -348,6 +352,90 @@ This setting takes the URL of the logo to be displayed on email notification.
**Note**: Provide a URL which points to the logo on your own web server. Ensure that the URL provided is
publicly accessible from the internet. Otherwise, the logo may not be displayed in email.

REST API
--------

Live documentation
~~~~~~~~~~~~~~~~~~

.. image:: docs/images/api-docs.png

A general live API documentation (following the OpenAPI specification) is available at ``/api/v1/docs/``.

Browsable web interface
~~~~~~~~~~~~~~~~~~~~~~~

.. image:: docs/images/api-ui.png

Additionally, opening any of the endpoints `listed below <#list-of-endpoints>`_
directly in the browser will show the `browsable API interface of Django-REST-Framework
<https://www.django-rest-framework.org/topics/browsable-api/>`_,
which makes it even easier to find out the details of each endpoint.

Authentication
~~~~~~~~~~~~~~

See openwisp-users: `authenticating with the user token
<https://github.com/openwisp/openwisp-users#authenticating-with-the-user-token>`_.

When browsing the API via the `Live documentation <#live-documentation>`_
or the `Browsable web interface <#browsable-web-interface>`_, you can use
the session authentication by logging in the django admin.

Pagination
~~~~~~~~~~

The *list* endpoint support the ``page_size`` parameter that allows paginating
the results in conjunction with the ``page`` parameter.

.. code-block:: text
GET /api/v1/openwisp_notifications/notifications/?page_size=10
GET api/v1/openwisp_notifications/notifications/?page_size=10&page=2
List of endpoints
~~~~~~~~~~~~~~~~~

Since the detailed explanation is contained in the `Live documentation <#live-documentation>`_
and in the `Browsable web page <#browsable-web-interface>`_ of each endpoint,
here we'll provide just a list of the available endpoints,
for further information please open the URL of the endpoint in your browser.

List user's notifications
#########################

.. code-block:: text
GET /api/v1/notifications/
Mark all user's notifications read
##################################

.. code-block:: text
POST /api/v1/notifications/read/
Get notification details
########################

.. code-block:: text
GET /api/v1/notifications/{pk}/
Mark a notification read
#########################

.. code-block:: text
PATCH /api/v1/notifications/{pk}/
Delete Notification
###################

.. code-block:: text
DELETE /api/v1/notifications/{pk}/
Installing for development
--------------------------

Expand Down Expand Up @@ -409,7 +497,7 @@ Extending openwisp-notifications
--------------------------------

One of the core values of the OpenWISP project is `Software Reusability <http://openwisp.io/docs/general/values.html#software-reusability-means-long-term-sustainability>`_,
for this reason *openwisp-notification* provides a set of base classes which can be imported, extended
for this reason *openwisp-notification* provpkes a set of base classes which can be imported, extended
and reused to create derivative apps.

In order to implement your custom version of *openwisp-notifications*, you need to perform the steps
Expand Down Expand Up @@ -660,6 +748,23 @@ to find out how to do this.
**Note**: Some tests will fail if ``templatetags`` and ``admin/base.html`` are not configured properly.
See preceeding sections to configure them properly.

Other base classes that can be inherited and extended
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following steps are not required and are intended for more advanced customization.

API views
#########

The API view classes can be extended into other django applications as well. Note
that it is not required for extending openwisp-notifications to your app and this change
is required only if you plan to make changes to the API views.

Create a view file as done in `views.py <https://github.com/openwisp/openwisp-notifications/blob/master/tests/openwisp2/sample_notifications/views.py>`_

For more information regarding Django REST Framework API views, please refer to the
`"Generic views" section in the Django REST Framework documentation <https://www.django-rest-framework.org/api-guide/generic-views/>`_.

Contributing
------------

Expand Down
Binary file added docs/images/api-docs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/api-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
44 changes: 44 additions & 0 deletions openwisp_notifications/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from django.urls import reverse
from openwisp_notifications.swapper import load_model
from openwisp_notifications.utils import _get_absolute_url
from rest_framework import serializers

Notification = load_model('Notification')


class ContentTypeField(serializers.Field):
def to_representation(self, obj):
return obj.model


class NotificationSerializer(serializers.ModelSerializer):
notification_url = serializers.SerializerMethodField()
actor_content_type = ContentTypeField(read_only=True)
target_content_type = ContentTypeField(read_only=True)
action_object_content_type = ContentTypeField(read_only=True)

class Meta:
model = Notification
exclude = ['description', 'deleted', 'public']
extra_fields = ['message', 'email_subject']

def get_field_names(self, declared_fields, info):
model_fields = super().get_field_names(declared_fields, info)

if getattr(self.Meta, 'extra_fields', None):
return model_fields + self.Meta.extra_fields
else:
return model_fields

def get_notification_url(self, obj):
url = reverse(
f'admin:{Notification._meta.app_label}_{Notification._meta.model_name}_change',
args=(obj.id,),
)
return _get_absolute_url(url)


class NotificationListSerializer(NotificationSerializer):
class Meta(NotificationSerializer.Meta):
fields = ['id', 'message', 'unread', 'notification_url', 'email_subject']
exclude = None
14 changes: 14 additions & 0 deletions openwisp_notifications/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.urls import path
from openwisp_notifications.api import views

app_name = 'openwisp_notifications'


def get_api_urls(api_views=None):
if not api_views:
api_views = views
return [
path('', views.notifications_list, name='notifications_list'),
path('read/', views.notifications_read_all, name='notifications_read_all'),
path('<uuid:pk>/', views.notification_detail, name='notification_detail'),
]
68 changes: 68 additions & 0 deletions openwisp_notifications/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from django_filters.rest_framework import DjangoFilterBackend
from openwisp_notifications.api.serializers import (
NotificationListSerializer,
NotificationSerializer,
)
from openwisp_notifications.swapper import load_model
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.generics import GenericAPIView, RetrieveDestroyAPIView
from rest_framework.mixins import ListModelMixin
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import DjangoModelPermissions
from rest_framework.response import Response

from openwisp_users.api.authentication import BearerAuthentication

Notification = load_model('Notification')


class NotificationPaginator(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100


class BaseNotificationView(GenericAPIView):
model = Notification
authentication_classes = [BearerAuthentication, SessionAuthentication]
permission_classes = [DjangoModelPermissions]
queryset = Notification.objects.all()

def get_queryset(self):
return self.queryset.filter(recipient=self.request.user)


class NotificationListView(BaseNotificationView, ListModelMixin):
serializer_class = NotificationListSerializer
pagination_class = NotificationPaginator
filter_backends = [DjangoFilterBackend]
filterset_fields = ['unread']

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)


class NotificationDetailView(BaseNotificationView, RetrieveDestroyAPIView):
serializer_class = NotificationSerializer
lookup_field = 'pk'

def patch(self, request, pk):
return self._mark_notification_read(pk)

def _mark_notification_read(self, notification_id):
notification = self.get_object()
notification.mark_as_read()
return Response(status=status.HTTP_200_OK,)


class NotificationReadAllView(BaseNotificationView):
def post(self, request, *args, **kwargs):
queryset = self.get_queryset()
queryset.update(unread=False)
return Response(status=status.HTTP_200_OK)


notifications_list = NotificationListView.as_view()
notification_detail = NotificationDetailView.as_view()
notifications_read_all = NotificationReadAllView.as_view()
Loading

0 comments on commit 10e1ff7

Please sign in to comment.