فهرست منبع

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
Markos Gogoulos 4 سال پیش
والد
کامیت
5602422d29
10فایلهای تغییر یافته به همراه347 افزوده شده و 19 حذف شده
  1. 31 0
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 17 0
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 13 0
      CODE_OF_CONDUCT.md
  4. 8 16
      README.md
  5. 2 0
      cms/settings.py
  6. 14 1
      cms/urls.py
  7. 43 0
      files/management_views.py
  8. 159 0
      files/views.py
  9. 5 0
      requirements.txt
  10. 55 2
      users/views.py

+ 31 - 0
.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.

+ 17 - 0
.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.

+ 13 - 0
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

+ 8 - 16
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

+ 2 - 0
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:

+ 14 - 1
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<format>\.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'),
 ]

+ 43 - 0
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)

+ 159 - 0
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):

+ 5 - 0
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
 

+ 55 - 2
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)