From 83e2fa4d50f833ae567b18a87442a2f3ff68b0af Mon Sep 17 00:00:00 2001 From: Sebastien Mirolo Date: Mon, 17 Jun 2024 04:14:17 -0700 Subject: [PATCH] updates API documentation --- pages/api/assets.py | 2 +- pages/api/elements.py | 12 ++++---- pages/api/progress.py | 54 ++++++++++++++++---------------- pages/api/reactions.py | 28 +++++++++++++++++ pages/api/sequences.py | 63 +++++++++++++++----------------------- pages/helpers.py | 1 + pages/serializers.py | 27 +++++++++++----- pages/urls/api/__init__.py | 11 ++++--- pages/urls/api/noauth.py | 17 +++++----- 9 files changed, 121 insertions(+), 94 deletions(-) diff --git a/pages/api/assets.py b/pages/api/assets.py index 3153117..799c56c 100644 --- a/pages/api/assets.py +++ b/pages/api/assets.py @@ -129,7 +129,7 @@ def post(self, request, *args, **kwargs): .. code-block:: http - POST /api/assets HTTP/1.1 + POST /api/supplier-1/assets HTTP/1.1 responds diff --git a/pages/api/elements.py b/pages/api/elements.py index 3bb53c1..0053cc3 100644 --- a/pages/api/elements.py +++ b/pages/api/elements.py @@ -49,9 +49,8 @@ from ..mixins import AccountMixin, PageElementMixin, TrailMixin from ..models import (PageElement, RelationShip, build_content_tree, flatten_content_tree, Follow) -from ..serializers import (AssetSerializer, NodeElementSerializer, - NodeElementCreateSerializer, PageElementSerializer, - PageElementTagSerializer) +from ..serializers import (AssetSerializer, NodeElementCreateSerializer, + PageElementSerializer, PageElementTagSerializer) from ..utils import validate_title from .assets import process_upload @@ -489,7 +488,7 @@ def delete(self, request, *args, **kwargs): .. code-block:: http - DELETE /api/editables/alliance/content/boxes-enclosures/ HTTP/1.1 + DELETE /api/editables/alliance/content/boxes-enclosures HTTP/1.1 """ #pylint:disable=useless-super-delegation return super(PageElementEditableDetail, self).delete( @@ -505,7 +504,7 @@ def post(self, request, *args, **kwargs): .. code-block:: http - POST /api/editables/alliance/content/boxes-enclosures/ HTTP/1.1 + POST /api/editables/alliance/content/boxes-enclosures HTTP/1.1 .. code-block:: json @@ -537,7 +536,7 @@ def put(self, request, *args, **kwargs): .. code-block:: http - PUT /api/editables/alliance/content/boxes-enclosures/ HTTP/1.1 + PUT /api/editables/alliance/content/boxes-enclosures HTTP/1.1 .. code-block:: json @@ -709,6 +708,7 @@ class ImportDocxView(AccountMixin, PageElementMixin, generics.GenericAPIView): "text": "Hello" } """ + schema = None # XXX currently disabled in API documentation serializer_class = PageElementSerializer def upload_image(self, request): diff --git a/pages/api/progress.py b/pages/api/progress.py index 256f476..aaf07e1 100644 --- a/pages/api/progress.py +++ b/pages/api/progress.py @@ -26,14 +26,15 @@ from deployutils.helpers import datetime_or_now from rest_framework import response as api_response, status +from rest_framework.exceptions import ValidationError from rest_framework.generics import DestroyAPIView, ListAPIView, RetrieveAPIView from .. import settings +from ..compat import gettext_lazy as _ from ..docs import extend_schema from ..mixins import EnumeratedProgressMixin, SequenceProgressMixin from ..models import EnumeratedElements, EnumeratedProgress, LiveEvent -from ..serializers import (EnumeratedProgressSerializer, - AttendanceInputSerializer) +from ..serializers import EnumeratedProgressSerializer class EnumeratedProgressListAPIView(SequenceProgressMixin, ListAPIView): @@ -58,8 +59,8 @@ class EnumeratedProgressListAPIView(SequenceProgressMixin, ListAPIView): "previous": null, "results": [ { - "created_at": "2020-09-28T00:00:00.0000Z", "rank": 1, + "content": "ghg-emissions-scope3-details", "viewing_duration": "00:00:00" } ] @@ -148,8 +149,8 @@ class EnumeratedProgressRetrieveAPIView(EnumeratedProgressMixin, .. code-block:: json { - "created_at": "2020-09-28T00:00:00.0000Z", "rank": 1, + "content": "metal", "viewing_duration": "00:00:00" } """ @@ -177,7 +178,7 @@ def post(self, request, *args, **kwargs): { "rank": 1, - "created_at": "2020-09-28T00:00:00.0000Z", + "content": "metal", "viewing_duration": "00:00:56.000000" } """ @@ -218,18 +219,15 @@ class LiveEventAttendanceAPIView(EnumeratedProgressRetrieveAPIView): .. code-block:: json { - "created_at": "2020-09-28T00:00:00.0000Z", "rank": 1, - "viewing_duration": "00:00:00" + "content":"ghg-emissions-scope3-details", + "viewing_duration": "00:00:00", + "min_viewing_duration": "00:01:00" } """ rank_url_kwarg = 'rank' - def get_serializer_class(self): - if self.request.method.lower() == 'post': - return AttendanceInputSerializer - return super(LiveEventAttendanceAPIView, self).get_serializer_class() - + @extend_schema(request=None) def post(self, request, *args, **kwargs): """ Marks a user's attendance to a live event @@ -243,31 +241,33 @@ def post(self, request, *args, **kwargs): .. code-block:: http - POST /api/attendance/alliance/ghg-accounting-training/1/steve HTTP/1.1 + POST /api/attendance/alliance/ghg-accounting-training/1/steve \ +HTTP/1.1 responds .. code-block:: json { - "detail": "Attendance marked successfully" + "rank": 1, + "content":"ghg-emissions-scope3-details", + "viewing_duration": "00:00:00", + "min_viewing_duration": "00:01:00" } """ - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - progress = self.get_object() element = progress.step live_event = LiveEvent.objects.filter(element=element.content).first() # We use if live_event to confirm the existence of the LiveEvent object - if (live_event and - progress.viewing_duration <= element.min_viewing_duration): - progress.viewing_duration = element.min_viewing_duration - progress.save() - return api_response.Response( - {'detail': 'Attendance marked successfully'}, - status=status.HTTP_200_OK) - return api_response.Response( - {'detail': 'Attendance not marked'}, - status=status.HTTP_400_BAD_REQUEST) + if (not live_event or + progress.viewing_duration > element.min_viewing_duration): + raise ValidationError(_("Cannot mark attendance of %(user)s"\ + " to %(sequence)s:%(rank)s.") % { + 'user': self.user, 'sequence': self.sequence, + 'rank': self.kwargs.get(self.rank_url_kwarg)}) + + progress.viewing_duration = element.min_viewing_duration + progress.save() + serializer = self.get_serializer(instance=progress) + return api_response.Response(serializer.data) diff --git a/pages/api/reactions.py b/pages/api/reactions.py index 10958bc..7a9e880 100644 --- a/pages/api/reactions.py +++ b/pages/api/reactions.py @@ -273,6 +273,34 @@ def perform_create(self, serializer): class NewsFeedListAPIView(UserMixin, generics.ListAPIView): + """ + Retrieves relevant news for a user + + **Tags**: content + + **Examples** + + .. code-block:: http + + GET /api/content/steve/newsfeed HTTP/1.1 + + responds + + .. code-block:: json + + { + "count": 1, + "next": null, + "previous": null, + "results": [{ + "path": "/metal/boxes-and-enclosures/production/energy-efficiency/process-heating/combustion/adjust-air-fuel-ratio", + "text_updated_at": "2024-01-01T00:00:00Z", + "last_read_at": "2023-12-01T00:00:00Z", + "nb_comments_since_last_read": 5, + "descr": "" + }] + } + """ serializer_class = PageElementUpdateSerializer def get_queryset(self): diff --git a/pages/api/sequences.py b/pages/api/sequences.py index 789b07e..895a855 100644 --- a/pages/api/sequences.py +++ b/pages/api/sequences.py @@ -32,8 +32,8 @@ from ..mixins import AccountMixin, SequenceMixin from ..models import Sequence, EnumeratedElements -from ..serializers import (SequenceSerializer, SequenceCreateSerializer, - EnumeratedElementSerializer) +from ..serializers import (EnumeratedElementSerializer, SequenceSerializer, + SequenceUpdateSerializer, SequenceCreateSerializer) LOGGER = logging.getLogger(__name__) @@ -53,7 +53,7 @@ class SequencesIndexAPIView(ListAPIView): .. code-block:: http - GET /api/sequences HTTP/1.1 + GET /api/content/sequences HTTP/1.1 responds @@ -66,7 +66,7 @@ class SequencesIndexAPIView(ListAPIView): "results": [ { "created_at": "2024-01-01T00:00:00.0000Z", - "slug": "ghg-accounting-training", + "slug": "ghg-accounting-webinar", "title": "GHG Accounting Training", "account": "djaopsp", "has_certificate": true @@ -117,7 +117,7 @@ class SequenceListCreateAPIView(AccountMixin, ListCreateAPIView): "results": [ { "created_at": "2020-09-28T00:00:00.0000Z", - "slug": "ghg-accounting-training", + "slug": "ghg-accounting-webinar", "title": "GHG Accounting Training", "account": "djaopsp", "has_certificate": true @@ -171,7 +171,7 @@ def post(self, request, *args, **kwargs): .. code-block:: json { - "slug": "ghg-accounting-training", + "slug": "ghg-accounting-webinar", "title": "GHG Accounting Training" } @@ -181,7 +181,7 @@ def post(self, request, *args, **kwargs): { "created_at": "2023-01-01T04:00:00.000000Z", - "slug": "ghg-accounting-training", + "slug": "ghg-accounting-webinar", "title": "GHG Accounting Training", "account": null, "has_certificate": true @@ -206,7 +206,7 @@ class SequenceRetrieveUpdateDestroyAPIView(AccountMixin, SequenceMixin, .. code-block:: http - GET /api/editables/alliance/sequences/ghg-accounting-training HTTP/1.1 + GET /api/editables/alliance/sequences/ghg-accounting-webinar HTTP/1.1 responds @@ -214,28 +214,22 @@ class SequenceRetrieveUpdateDestroyAPIView(AccountMixin, SequenceMixin, { "created_at": "2023-12-29T04:33:33.078661Z", - "slug": "ghg-accounting-training", + "slug": "ghg-accounting-webinar", "title": "GHG Accounting Training", "account": null, - "has_certificate": true, - "results": [ - { - "rank": 1, - "content": "text-content", - "min_viewing_duration": "00:00:10" - }, - { - "rank": 2, - "content": "survey-event", - "min_viewing_duration": "00:00:20" - } - ] + "has_certificate": true } """ serializer_class = SequenceSerializer lookup_field = 'slug' lookup_url_kwarg = SequenceMixin.sequence_url_kwarg + def get_serializer_class(self): + if self.request.method.lower() == 'put': + return SequenceUpdateSerializer + return super(SequenceRetrieveUpdateDestroyAPIView, + self).get_serializer_class() + def get_object(self): return self.sequence @@ -249,7 +243,7 @@ def delete(self, request, *args, **kwargs): .. code-block:: http - DELETE /api/editables/alliance/sequences/ghg-accounting-training\ + DELETE /api/editables/alliance/sequences/ghg-accounting-webinar\ HTTP/1.1 """ @@ -265,7 +259,7 @@ def put(self, request, *args, **kwargs): .. code-block:: http - PUT /api/editables/alliance/sequences/ghg-accounting-training HTTP/1.1 + PUT /api/editables/alliance/sequences/ghg-accounting-webinar HTTP/1.1 .. code-block:: json @@ -281,12 +275,11 @@ def put(self, request, *args, **kwargs): { "created_at": "2023-12-29T04:33:33.078661Z", - "slug": "ghg-accounting-training", + "slug": "ghg-accounting-webinar", "title": "Updated GHG Accounting Training Title", "account": null, "has_certificate": false, - "extra": "Additional info", - "results": [] + "extra": "Additional info" } """ #pylint:disable=useless-parent-delegation @@ -304,7 +297,9 @@ class AddElementToSequenceAPIView(AccountMixin, SequenceMixin, .. code-block:: http - GET /api/editables/alliance/sequences/ghg-accounting-training/elements HTTP/1.1 + GET /api/editables/alliance/sequences/ghg-accounting-webinar/elements HTTP/1.1 + + responds .. code-block:: json @@ -325,14 +320,6 @@ class AddElementToSequenceAPIView(AccountMixin, SequenceMixin, } ] } - - responds - - .. code-block:: json - - { - "detail": "element added" - } """ serializer_class = EnumeratedElementSerializer @@ -349,7 +336,7 @@ def post(self, request, *args, **kwargs): .. code-block:: http - POST /api/editables/alliance/sequences/ghg-accounting-training/elements HTTP/1.1 + POST /api/editables/alliance/sequences/ghg-accounting-webinar/elements HTTP/1.1 .. code-block:: json @@ -415,7 +402,7 @@ class RemoveElementFromSequenceAPIView(AccountMixin, SequenceMixin, **Example** - DELETE /api/editables/alliance/sequences/ghg-accounting-training/elements/1 HTTP/1.1 + DELETE /api/editables/alliance/sequences/ghg-accounting-webinar/elements/1 HTTP/1.1 responds diff --git a/pages/helpers.py b/pages/helpers.py index b587b80..11267cb 100644 --- a/pages/helpers.py +++ b/pages/helpers.py @@ -63,6 +63,7 @@ def get_extra(obj, attr_name, default=None): extra = obj.get('extra') return extra.get(attr_name, default) if extra else default + def update_context_urls(context, urls): if 'urls' in context: for key, val in six.iteritems(urls): diff --git a/pages/serializers.py b/pages/serializers.py index 4950c3c..0b9cfc0 100644 --- a/pages/serializers.py +++ b/pages/serializers.py @@ -296,6 +296,9 @@ def to_internal_value(self, data): class PageElementUpdateSerializer(PageElementSerializer): + """ + Serializer for news updates + """ text_updated_at = serializers.DateTimeField( required=False, help_text=_("Datetime of last update on the page element's text")) @@ -391,19 +394,26 @@ def get_elements(obj): many=True).data -class SequenceCreateSerializer(SequenceSerializer): +class SequenceUpdateSerializer(SequenceSerializer): """ Serializer to create a `Sequence` """ slug = serializers.SlugField(required=False, help_text=_("Unique identifier for the sequence")) + + class Meta(SequenceSerializer.Meta): + fields = SequenceSerializer.Meta.fields + + +class SequenceCreateSerializer(SequenceUpdateSerializer): + """ + Serializer to create a `Sequence` + """ title = serializers.CharField(required=True, help_text=_("Title of the sequence")) - class Meta(SequenceSerializer.Meta): - """ - Same fields as `SequenceSerializer` - """ + class Meta(SequenceUpdateSerializer.Meta): + fields = SequenceUpdateSerializer.Meta.fields class EnumeratedProgressSerializer(EnumeratedElementSerializer): @@ -420,8 +430,9 @@ class Meta(EnumeratedElementSerializer.Meta): 'certificate', 'viewing_duration',) -class AttendanceInputSerializer(serializers.Serializer): +class ValidationErrorSerializer(NoModelSerializer): """ - Serializer to validate input to mark users' attendance - to a LiveEvent(LiveEventAttendanceAPIView) + Details when an error occurs """ + detail = serializers.CharField(help_text=_("Describes the reason for"\ + " the error in plain text")) diff --git a/pages/urls/api/__init__.py b/pages/urls/api/__init__.py index aa8ebc9..9229dd2 100644 --- a/pages/urls/api/__init__.py +++ b/pages/urls/api/__init__.py @@ -22,10 +22,11 @@ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -'''API URLs for the pages application''' +""" +API URLs for the pages application +""" -from ...api.elements import PageElementIndexAPIView -from ...api.sequences import SequencesIndexAPIView +from ...api.elements import PageElementAPIView, PageElementIndexAPIView from ...compat import include, path urlpatterns = [ @@ -33,10 +34,10 @@ path('attendance/', include('pages.urls.api.sequences')), path('content/', include('pages.urls.api.readers')), path('content/', include('pages.urls.api.noauth')), + path('', PageElementAPIView.as_view(), + name="api_content"), path('content', PageElementIndexAPIView.as_view(), name="api_content_index"), path('progress/', include('pages.urls.api.progress')), - path('sequences', SequencesIndexAPIView.as_view(), - name='api_sequences_index'), path('', include('pages.urls.api.assets')) ] diff --git a/pages/urls/api/noauth.py b/pages/urls/api/noauth.py index b1f4bee..8d6e861 100644 --- a/pages/urls/api/noauth.py +++ b/pages/urls/api/noauth.py @@ -26,15 +26,14 @@ API URLs for readers who could be unauthenticated """ from ...compat import path -from ...api.elements import (PageElementSearchAPIView, PageElementAPIView, - PageElementDetailAPIView) +from ...api.elements import PageElementSearchAPIView, PageElementDetailAPIView +from ...api.sequences import SequencesIndexAPIView urlpatterns = [ - path('search', - PageElementSearchAPIView.as_view(), name='api_page_element_search'), - path(r'detail/', - PageElementDetailAPIView.as_view(), - name='pages_api_pageelement'), - path('', - PageElementAPIView.as_view(), name="api_content"), + path('search', PageElementSearchAPIView.as_view(), + name='api_page_element_search'), + path('sequences', SequencesIndexAPIView.as_view(), + name='api_sequences_index'), + path(r'detail/', PageElementDetailAPIView.as_view(), + name='pages_api_pageelement') ]