From 5602422d296fdbcc49371d84a03d3875f2370e30 Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Sat, 29 May 2021 16:34:36 +0300 Subject: [PATCH] adds drf-yasg and automated generation of Swagger Schemas (#165) * adds drf-yasg and automated generation of Swagger Schemas * swagger url * swagger docs * adds swagger url on Readme * swagger API * Code of Conduct file * doc --- .github/ISSUE_TEMPLATE/bug_report.md | 31 +++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 +++ CODE_OF_CONDUCT.md | 13 ++ README.md | 24 ++-- cms/settings.py | 2 + cms/urls.py | 15 +- files/management_views.py | 43 ++++++ files/views.py | 159 ++++++++++++++++++++++ requirements.txt | 5 + users/views.py | 57 +++++++- 10 files changed, 347 insertions(+), 19 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 CODE_OF_CONDUCT.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6936501 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Issue report +about: Create a report to help us improve MediaCMS +title: '' +labels: 'issue: bug' +assignees: mgogoulos + +--- + +**Describe the issue** +A clear and concise description of what the issue is. + +**To Reproduce** +Steps to reproduce the issue: +1. Go to ... +2. Perform action ... +3. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + - OS: [e.g. Ubuntu Linux] + - Installation method: [Docker install, or single server install] + - Browser, if applicable + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a1d7f99 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea +title: '' +labels: 'issue: enhancement' +assignees: mgogoulos + +--- + +**Describe the feature you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..760ee26 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,13 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at https://www.contributor-covenant.org/version/1/0/0/code-of-conduct.html diff --git a/README.md b/README.md index 0f8b661..17d83f3 100644 --- a/README.md +++ b/README.md @@ -158,28 +158,13 @@ This software uses the following list of awesome technologies: ## Who is using it -- **EngageMedia** non-profit media, technology and culture organization - https://video.engagemedia.org +- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org - **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org - **Heritales** International Heritage Film Festival - https://stage.heritales.org -## Thanks To - -- **Anna Helme**, for such a great partnership all these years! - -- **Steve Anderson**, for trusting us and helping the Wordgames team make this real. - -- **Andrew Lowenthal, King Catoy, Rezwan Islam** and the rest of the great team of [Engage Media](https://engagemedia.org). - -- **Ioannis Korovesis, Ioannis Maistros, Diomidis Spinellis and Theodoros Karounos**, for their mentorship all these years, their contribution to science and the promotion of open source and free software technologies. - -- **Antonis Ikonomou**, for hosting us on the excellent [Innovathens](https://www.innovathens.gr) space. - -- **Werner Robitza**, for helping us with ffmpeg related stuff. - - ## How to contribute If you like the project, here's a few things you can do @@ -190,6 +175,13 @@ If you like the project, here's a few things you can do - Open issues, participate on discussions, report bugs, suggest ideas - Star the project - Add functionality, work on a PR, fix an issue! + +## Developers info + +- API documentation available under /swagger URL (example https://demo.mediacms.io/swagger/) +- We're working on proper documentation for users, managers and developers, until then checkout what's available on the docs/ folder of this repository - Before you send a PR, make sure your code is properly formatted. For that, use `pre-commit install` to install a pre-commit hook and run `pre-commit run --all` and fix everything before you commit. This pre-commit will check for your code lint everytime you commit a code. +- Checkout the [Code of conduct page](CODE_OF_CONDUCT.md) if you want to contribute to this repository + ## Contact info@mediacms.io diff --git a/cms/settings.py b/cms/settings.py index 7a4898f..9064588 100644 --- a/cms/settings.py +++ b/cms/settings.py @@ -292,6 +292,7 @@ INSTALLED_APPS = [ "uploader.apps.UploaderConfig", "djcelery_email", "ckeditor", + "drf_yasg", ] MIDDLEWARE = [ @@ -423,6 +424,7 @@ CELERY_BEAT_SCHEDULE = { # TODO: beat, delete chunks from media root # chunks_dir after xx days...(also uploads_dir) + LOCAL_INSTALL = False try: diff --git a/cms/urls.py b/cms/urls.py index d152981..6e52b9e 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -1,7 +1,17 @@ import debug_toolbar from django.conf.urls import include, url from django.contrib import admin -from django.urls import path +from django.urls import path, re_path +from drf_yasg import openapi +from drf_yasg.views import get_schema_view +from rest_framework.permissions import AllowAny + +schema_view = get_schema_view( + openapi.Info(title="MediaCMS API", default_version='v1', contact=openapi.Contact(url="https://mediacms.io"), x_logo={"url": "../../static/images/logo_dark.svg"}), + public=True, + permission_classes=(AllowAny,), +) + urlpatterns = [ url(r"^__debug__/", include(debug_toolbar.urls)), @@ -10,4 +20,7 @@ urlpatterns = [ url(r"^accounts/", include("allauth.urls")), url(r"^api-auth/", include("rest_framework.urls")), path("admin/", admin.site.urls), + re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), + re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), + path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ] diff --git a/files/management_views.py b/files/management_views.py index 3eb6f8a..09a0ec0 100644 --- a/files/management_views.py +++ b/files/management_views.py @@ -1,3 +1,5 @@ +from drf_yasg import openapi as openapi +from drf_yasg.utils import swagger_auto_schema from rest_framework import status from rest_framework.parsers import JSONParser from rest_framework.response import Response @@ -23,6 +25,17 @@ class MediaList(APIView): permission_classes = (IsMediacmsEditor,) parser_classes = (JSONParser,) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='sort_by', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Sort by any of: title, add_date, edit_date, views, likes, reported_times'), + openapi.Parameter(name='ordering', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Order by: asc, desc'), + openapi.Parameter(name='state', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Media state, options: private", "public", "unlisted'), + openapi.Parameter(name='encoding_status', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Encoding status, options "pending", "running", "fail", "success"'), + ], + tags=['Manage'], + operation_summary='Manage Media', + operation_description='Manage media for MediaCMS managers and reviewers', + ) def get(self, request, format=None): params = self.request.query_params ordering = params.get("ordering", "").strip() @@ -94,6 +107,12 @@ class MediaList(APIView): serializer = MediaSerializer(page, many=True, context={"request": request}) return paginator.get_paginated_response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Manage'], + operation_summary='Delete Media', + operation_description='Delete media for MediaCMS managers and reviewers', + ) def delete(self, request, format=None): tokens = request.GET.get("tokens") if tokens: @@ -112,6 +131,12 @@ class CommentList(APIView): permission_classes = (IsMediacmsEditor,) parser_classes = (JSONParser,) + @swagger_auto_schema( + manual_parameters=[], + tags=['Manage'], + operation_summary='Manage Comments', + operation_description='Manage comments for MediaCMS managers and reviewers', + ) def get(self, request, format=None): params = self.request.query_params ordering = params.get("ordering", "").strip() @@ -137,6 +162,12 @@ class CommentList(APIView): serializer = CommentSerializer(page, many=True, context={"request": request}) return paginator.get_paginated_response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Manage'], + operation_summary='Delete Comments', + operation_description='Delete comments for MediaCMS managers and reviewers', + ) def delete(self, request, format=None): comment_ids = request.GET.get("comment_ids") if comment_ids: @@ -156,6 +187,12 @@ class UserList(APIView): permission_classes = (IsMediacmsEditor,) parser_classes = (JSONParser,) + @swagger_auto_schema( + manual_parameters=[], + tags=['Manage'], + operation_summary='Manage Users', + operation_description='Manage users for MediaCMS managers and reviewers', + ) def get(self, request, format=None): params = self.request.query_params ordering = params.get("ordering", "").strip() @@ -187,6 +224,12 @@ class UserList(APIView): serializer = UserSerializer(page, many=True, context={"request": request}) return paginator.get_paginated_response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Manage'], + operation_summary='Delete Users', + operation_description='Delete users for MediaCMS managers', + ) def delete(self, request, format=None): if not is_mediacms_manager(request.user): return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST) diff --git a/files/views.py b/files/views.py index 021c991..6e97455 100644 --- a/files/views.py +++ b/files/views.py @@ -10,6 +10,8 @@ from django.db.models import Q from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.template.defaultfilters import slugify +from drf_yasg import openapi as openapi +from drf_yasg.utils import swagger_auto_schema from rest_framework import permissions, status from rest_framework.exceptions import PermissionDenied from rest_framework.parsers import ( @@ -366,6 +368,12 @@ class MediaList(APIView): permission_classes = (IsAuthorizedToAdd,) parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, format=None): # Show media params = self.request.query_params @@ -405,6 +413,12 @@ class MediaList(APIView): serializer = MediaSerializer(page, many=True, context={"request": request}) return paginator.get_paginated_response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def post(self, request, format=None): # Add new media serializer = MediaSerializer(data=request.data, context={"request": request}) @@ -446,6 +460,12 @@ class MediaDetail(APIView): status=status.HTTP_400_BAD_REQUEST, ) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, friendly_token, format=None): # Get media details password = request.GET.get("password") @@ -471,6 +491,12 @@ class MediaDetail(APIView): ret["related_media"] = related_media return Response(ret) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def post(self, request, friendly_token, format=None): """superuser actions Available only to MediaCMS editors and managers @@ -521,6 +547,12 @@ class MediaDetail(APIView): status=status.HTTP_400_BAD_REQUEST, ) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def put(self, request, friendly_token, format=None): # Update a media object media = self.get_object(friendly_token) @@ -534,6 +566,12 @@ class MediaDetail(APIView): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def delete(self, request, friendly_token, format=None): # Delete a media object media = self.get_object(friendly_token) @@ -565,6 +603,12 @@ class MediaActions(APIView): status=status.HTTP_400_BAD_REQUEST, ) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, friendly_token, format=None): # show date and reason for each time media was reported media = self.get_object(friendly_token) @@ -580,6 +624,12 @@ class MediaActions(APIView): return Response(ret, status=status.HTTP_200_OK) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def post(self, request, friendly_token, format=None): # perform like/dislike/report actions media = self.get_object(friendly_token) @@ -609,6 +659,12 @@ class MediaActions(APIView): else: return Response({"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def delete(self, request, friendly_token, format=None): media = self.get_object(friendly_token) if isinstance(media, Response): @@ -639,6 +695,12 @@ class MediaSearch(APIView): parser_classes = (JSONParser,) + @swagger_auto_schema( + manual_parameters=[], + tags=['Search'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, format=None): params = self.request.query_params query = params.get("q", "").strip().lower() @@ -736,6 +798,12 @@ class PlaylistList(APIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd) parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + @swagger_auto_schema( + manual_parameters=[], + tags=['Playlists'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, format=None): pagination_class = api_settings.DEFAULT_PAGINATION_CLASS paginator = pagination_class() @@ -750,6 +818,12 @@ class PlaylistList(APIView): serializer = PlaylistSerializer(page, many=True, context={"request": request}) return paginator.get_paginated_response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Playlists'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def post(self, request, format=None): serializer = PlaylistSerializer(data=request.data, context={"request": request}) if serializer.is_valid(): @@ -777,6 +851,12 @@ class PlaylistDetail(APIView): status=status.HTTP_400_BAD_REQUEST, ) + @swagger_auto_schema( + manual_parameters=[], + tags=['Playlists'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, friendly_token, format=None): playlist = self.get_playlist(friendly_token) if isinstance(playlist, Response): @@ -793,6 +873,12 @@ class PlaylistDetail(APIView): return Response(ret) + @swagger_auto_schema( + manual_parameters=[], + tags=['Playlists'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def post(self, request, friendly_token, format=None): playlist = self.get_playlist(friendly_token) if isinstance(playlist, Response): @@ -803,6 +889,12 @@ class PlaylistDetail(APIView): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @swagger_auto_schema( + manual_parameters=[], + tags=['Playlists'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def put(self, request, friendly_token, format=None): playlist = self.get_playlist(friendly_token) if isinstance(playlist, Response): @@ -857,6 +949,12 @@ class PlaylistDetail(APIView): status=status.HTTP_400_BAD_REQUEST, ) + @swagger_auto_schema( + manual_parameters=[], + tags=['Playlists'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def delete(self, request, friendly_token, format=None): playlist = self.get_playlist(friendly_token) if isinstance(playlist, Response): @@ -874,6 +972,7 @@ class EncodingDetail(APIView): permission_classes = (permissions.IsAdminUser,) parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + @swagger_auto_schema(auto_schema=None) def post(self, request, encoding_id): ret = {} force = request.data.get("force", False) @@ -999,6 +1098,7 @@ class EncodingDetail(APIView): return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST) return Response({"status": "success"}, status=status.HTTP_201_CREATED) + @swagger_auto_schema(auto_schema=None) def put(self, request, encoding_id, format=None): encoding_file = request.data["file"] encoding = Encoding.objects.filter(id=encoding_id).first() @@ -1016,6 +1116,15 @@ class CommentList(APIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd) parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'), + openapi.Parameter(name='author', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='username'), + ], + tags=['Comments'], + operation_summary='Lists Comments', + operation_description='Paginated listing of all comments', + ) def get(self, request, format=None): pagination_class = api_settings.DEFAULT_PAGINATION_CLASS paginator = pagination_class() @@ -1060,6 +1169,12 @@ class CommentDetail(APIView): status=status.HTTP_400_BAD_REQUEST, ) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, friendly_token): # list comments for a media media = self.get_object(friendly_token) @@ -1072,6 +1187,12 @@ class CommentDetail(APIView): serializer = CommentSerializer(page, many=True, context={"request": request}) return paginator.get_paginated_response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def delete(self, request, friendly_token, uid=None): """Delete a comment Administrators, MediaCMS editors and managers, @@ -1091,6 +1212,12 @@ class CommentDetail(APIView): return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT) + @swagger_auto_schema( + manual_parameters=[], + tags=['Media'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def post(self, request, friendly_token): """Create a comment""" media = self.get_object(friendly_token) @@ -1115,6 +1242,14 @@ class CommentDetail(APIView): class UserActions(APIView): parser_classes = (JSONParser,) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='action', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='action', required=True, enum=VALID_USER_ACTIONS), + ], + tags=['Users'], + operation_summary='List user actions', + operation_description='Lists user actions', + ) def get(self, request, action): media = [] if action in VALID_USER_ACTIONS: @@ -1140,6 +1275,12 @@ class UserActions(APIView): class CategoryList(APIView): """List categories""" + @swagger_auto_schema( + manual_parameters=[], + tags=['Categories'], + operation_summary='Lists Categories', + operation_description='Lists all categories', + ) def get(self, request, format=None): categories = Category.objects.filter().order_by("title") serializer = CategorySerializer(categories, many=True, context={"request": request}) @@ -1150,6 +1291,14 @@ class CategoryList(APIView): class TagList(APIView): """List tags""" + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'), + ], + tags=['Tags'], + operation_summary='Lists Tags', + operation_description='Paginated listing of all tags', + ) def get(self, request, format=None): tags = Tag.objects.filter().order_by("-media_count") pagination_class = api_settings.DEFAULT_PAGINATION_CLASS @@ -1162,6 +1311,12 @@ class TagList(APIView): class EncodeProfileList(APIView): """List encode profiles""" + @swagger_auto_schema( + manual_parameters=[], + tags=['Encoding Profiles'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def get(self, request, format=None): profiles = EncodeProfile.objects.all() serializer = EncodeProfileSerializer(profiles, many=True, context={"request": request}) @@ -1171,6 +1326,8 @@ class EncodeProfileList(APIView): class TasksList(APIView): """List tasks""" + swagger_schema = None + permission_classes = (permissions.IsAdminUser,) def get(self, request, format=None): @@ -1181,6 +1338,8 @@ class TasksList(APIView): class TaskDetail(APIView): """Cancel a task""" + swagger_schema = None + permission_classes = (permissions.IsAdminUser,) def delete(self, request, uid, format=None): diff --git a/requirements.txt b/requirements.txt index 8eeedec..f414021 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,12 +13,17 @@ drf-yasg==1.20.0 Pillow==8.1.1 django-imagekit + markdown django-filter + filetype django-mptt + django-crispy-forms + requests==2.25.0 + django-celery-email m3u8 diff --git a/users/views.py b/users/views.py index a413615..048587f 100644 --- a/users/views.py +++ b/users/views.py @@ -3,6 +3,8 @@ from django.contrib.auth.decorators import login_required from django.core.mail import EmailMessage from django.http import HttpResponseRedirect from django.shortcuts import render +from drf_yasg import openapi as openapi +from drf_yasg.utils import swagger_auto_schema from rest_framework import permissions, status from rest_framework.decorators import api_view from rest_framework.exceptions import PermissionDenied @@ -131,6 +133,13 @@ def edit_channel(request, friendly_token): return render(request, "cms/channel_edit.html", {"form": form}) +@swagger_auto_schema( + methods=['post'], + manual_parameters=[], + tags=['Users'], + operation_summary='Contact user', + operation_description='Contact user through email, if user has set this option', +) @api_view(["POST"]) def contact_user(request, username): if not request.user.is_authenticated: @@ -167,9 +176,18 @@ Sender email: %s\n class UserList(APIView): + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'), + ], + tags=['Users'], + operation_summary='List users', + operation_description='Paginated listing of users', + ) def get(self, request, format=None): pagination_class = api_settings.DEFAULT_PAGINATION_CLASS paginator = pagination_class() @@ -202,6 +220,14 @@ class UserDetail(APIView): except User.DoesNotExist: return Response({"detail": "user does not exist"}, status=status.HTTP_400_BAD_REQUEST) + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='username', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='username', required=True), + ], + tags=['Users'], + operation_summary='List user details', + operation_description='Get user details', + ) def get(self, request, username, format=None): # Get user details user = self.get_user(username) @@ -211,9 +237,24 @@ class UserDetail(APIView): serializer = UserDetailSerializer(user, context={"request": request}) return Response(serializer.data) - def post(self, request, uid, format=None): + @swagger_auto_schema( + manual_parameters=[ + openapi.Parameter(name='username', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='username', required=True), + ], + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + 'description': openapi.Schema(type=openapi.TYPE_STRING, description='description'), + 'name': openapi.Schema(type=openapi.TYPE_STRING, description='name'), + }, + ), + tags=['Users'], + operation_summary='Edit user details', + operation_description='Post user details - authenticated view', + ) + def post(self, request, username, format=None): # USER - user = self.get_user(uid) + user = self.get_user(username) if isinstance(user, Response): return user @@ -228,6 +269,12 @@ class UserDetail(APIView): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @swagger_auto_schema( + manual_parameters=[], + tags=['Users'], + operation_summary='Xto_be_written', + operation_description='to_be_written', + ) def put(self, request, uid, format=None): # ADMIN user = self.get_user(uid) @@ -248,6 +295,12 @@ class UserDetail(APIView): serializer = UserDetailSerializer(user, context={"request": request}) return Response(serializer.data) + @swagger_auto_schema( + manual_parameters=[], + tags=['Users'], + operation_summary='to_be_written', + operation_description='to_be_written', + ) def delete(self, request, username, format=None): # Delete a user user = self.get_user(username)