项目作者: m-haziq

项目描述 :
Beginners approach to Django Rest Swagger
高级语言: Python
项目地址: git://github.com/m-haziq/django-rest-swagger-docs.git
创建时间: 2017-10-09T07:17:43Z
项目社区:https://github.com/m-haziq/django-rest-swagger-docs

开源协议:

下载


Comprehensive approach to django-rest-swagger 2 using both FBV and CBV

main-demo

This project provides a beginner’s guide to django rest swagger 2 with
function based views as well as class based views. It provides building the
project from scratch. For those who want to integrate Django Rest Swagger into
an existing project, head straight to: Integrating Django Rest Swagger .

Read this article on @m_haziq/comprehensive-approach-to-django-rest-swagger-2-583e91a4c833">Medium if that is more convenient for you.

Getting Started

Before we start, we need this to be installed:

Project Setup

Start by making the project as in DRF official docs:

  1. mkdir django-rest-swagger-docs
  2. cd django-rest-swagger-docs

Make virtual environment with python3 and activate it:

  1. virtualenv env --python=python3
  2. source ./env/bin/activate

Install Django and DjangoRestFramework:

  1. pip install django
  2. pip install djangorestframework==3.5.3

Note: Installing the correct version of DRF is very important.

Make project named demo in current directory and navigate inside it:

  1. django-admin startproject demo .
  2. cd demo

Make two apps cbv-demo (for class based views) and fbv-demo (for function based view) inside demo project,:

  1. django-admin startapp cbv_demo
  2. django-admin startapp fbv_demo

Get back to main directory:

  1. cd ..

Now sync database and create database user:

  1. python manage.py migrate
  2. python manage.py createsuperuser
Making class based views:

Make a simple model at demo/cbv_demo/models.py with code below:

  1. from django.db import models
  2. class Contact(models.Model):
  3. name = models.CharField(max_length=22)
  4. phone = models.CharField(max_length=22)
  5. address = models.CharField(max_length=44)

Make a simple serializer at demo/cbv_demo/serializers.py with code below:

  1. from rest_framework import serializers
  2. from .models import Contact
  3. class ContactDataSerializer(serializers.ModelSerializer):
  4. class Meta:
  5. model = Contact
  6. fields = '__all__'

Make a simple view at demo/cbv_demo/views.py with code below:

  1. from demo.cbv_demo.serializers import ContactDataSerializer
  2. from rest_framework.response import Response
  3. from rest_framework.permissions import IsAuthenticated
  4. from .models import Contact
  5. from rest_framework import status , generics
  6. class ContactData(generics.GenericAPIView):
  7. serializer_class = ContactDataSerializer
  8. permission_classes = [IsAuthenticated,]
  9. def get(self, request, format=None):
  10. contacts = Contact.objects.all()
  11. serializer = ContactDataSerializer(contacts, many=True)
  12. return Response(serializer.data)
  13. def post(self, request, format=None):
  14. serializer = ContactDataSerializer(data=request.data)
  15. if serializer.is_valid():
  16. serializer.save()
  17. return Response(serializer.data, status=status.HTTP_201_CREATED)
  18. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Add urls of this app to demo/cbv_demo/urls.py as:

  1. from django.conf.urls import url
  2. from .views import ContactData
  3. urlpatterns = [
  4. url(r'^contact', ContactData.as_view(), name='contact'),
  5. ]
Making function based views:

Make a simple model at demo/fbv_demo/models.py with code below:

  1. from django.db import models
  2. class Medical(models.Model):
  3. name = models.CharField(max_length=22)
  4. bloodgroup = models.CharField(max_length=22)
  5. birthmark = models.CharField(max_length=44)

Make a simple view at demo/fbv_demo/views.py with code below:

  1. from rest_framework.decorators import api_view
  2. from rest_framework.response import Response
  3. from .models import Medical
  4. from rest_framework import status
  5. @api_view(['POST'])
  6. def save_medical(request):
  7. name = request.POST.get('name')
  8. bloodgroup = request.POST.get('bloodgroup')
  9. birthmark = request.POST.get('birthmark')
  10. try:
  11. Medical.objects.create(name= name, bloodgroup = bloodgroup, birthmark = birthmark)
  12. return Response("Data Saved!", status=status.HTTP_201_CREATED)
  13. except Exception as ex:
  14. return Response(ex, status=status.HTTP_400_BAD_REQUEST)
  15. @api_view(['GET'])
  16. def get_medical(request):
  17. return Response(Medical.objects.all().values(), status=status.HTTP_200_OK)

Add urls of this app to demo/fbv_demo/urls.py as:

  1. from django.conf.urls import url
  2. from .views import save_medical, get_medical
  3. urlpatterns = [
  4. url(r'^save_medical', save_medical, name='save_contact'),
  5. url(r'^get_medical', get_medical, name='get_contact'),
  6. ]

Add ‘rest_framework’ and both apps to demo/settings.py as:

  1. INSTALLED_APPS = [
  2. ...
  3. 'rest_framework',
  4. 'demo.cbv_demo',
  5. 'demo.fbv_demo',
  6. ]

Now add urls of both apps to demo/urls.py:

  1. from django.conf.urls import url, include
  2. urlpatterns = [
  3. url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
  4. url(r'^cbv/', include('demo.cbv_demo.urls')),
  5. url(r'^fbv/', include('demo.fbv_demo.urls')),
  6. ]

Now you have class based views (GET, POST) in demo/cbv_demo/views.py and
two function based views in demo/fbv_demo/views.py .

In order to run the it apply migrations
to make tables in db against models and runserver to test:

  1. python manage.py makemigrations
  2. python manage.py migrate
  3. python manage.py runserver

Integrating Django Rest Swagger:

In order to integrate django-rest-swagger,
first install it through pip as:

  1. pip install django-rest-swagger==2.1.1

Note: Installing the correct version is very important.(This one works best with djangorestframework==3.5.3)

Add it into the demo/settings.py as:

  1. INSTALLED_APPS = [
  2. ...
  3. 'rest_framework_swagger',
  4. ]
  5. # Parser classes to help swagger, default ll be JSONParser only.
  6. REST_FRAMEWORK = {
  7. # Parser classes priority-wise for Swagger
  8. 'DEFAULT_PARSER_CLASSES': [
  9. 'rest_framework.parsers.FormParser',
  10. 'rest_framework.parsers.MultiPartParser',
  11. 'rest_framework.parsers.JSONParser',
  12. ],
  13. }

Using get_swagger_view the shortcut method:

Make swagger schema view in demo/urls.py and assign it a url as :

  1. from rest_framework_swagger.views import get_swagger_view
  2. schema_view = get_swagger_view(title='Demo Swagger API')
  3. urlpatterns = [
  4. ...
  5. url(r'^swagger/', schema_view),
  6. ]

Your swagger should run at:
http://127.0.0.1:8000/swagger/

Using shortcut method can let us into following issues:

  • No way to document parameters of function based views
  • Less customizablity
  • No way to enforce permission classes
  • No handy way to exclude swagger view from schema

In order to acheive these functionalities, we ll go for its advance usage.

Advance Usage:

For finer control and to make swagger more customizable
we write swagger schema view manually and to document parameters
for function based views, we override SchemaGenerator of rest_framework
which is used by django-rest-swagger (version 2) to generate documentation.

Create a file demo/swagger_schema.py and add following code to it:

  1. from rest_framework.permissions import AllowAny
  2. from rest_framework.response import Response
  3. from rest_framework.views import APIView
  4. from rest_framework_swagger import renderers
  5. from rest_framework.schemas import SchemaGenerator
  6. from urllib.parse import urljoin
  7. import yaml
  8. import coreapi
  9. class CustomSchemaGenerator(SchemaGenerator):
  10. def get_link(self, path, method, view):
  11. fields = self.get_path_fields(path, method, view)
  12. yaml_doc = None
  13. if view and view.__doc__:
  14. try:
  15. yaml_doc = yaml.load(view.__doc__)
  16. except:
  17. yaml_doc = None
  18. #Extract schema information from yaml
  19. if yaml_doc and type(yaml_doc) != str:
  20. _method_desc = yaml_doc.get('description', '')
  21. params = yaml_doc.get('parameters', [])
  22. for i in params:
  23. _name = i.get('name')
  24. _desc = i.get('description')
  25. _required = i.get('required', False)
  26. _type = i.get('type', 'string')
  27. _location = i.get('location', 'form')
  28. field = coreapi.Field(
  29. name=_name,
  30. location=_location,
  31. required=_required,
  32. description=_desc,
  33. type=_type
  34. )
  35. fields.append(field)
  36. else:
  37. _method_desc = view.__doc__ if view and view.__doc__ else ''
  38. fields += self.get_serializer_fields(path, method, view)
  39. fields += self.get_pagination_fields(path, method, view)
  40. fields += self.get_filter_fields(path, method, view)
  41. if fields and any([field.location in ('form', 'body') for field in fields]):
  42. encoding = self.get_encoding(path, method, view)
  43. else:
  44. encoding = None
  45. if self.url and path.startswith('/'):
  46. path = path[1:]
  47. return coreapi.Link(
  48. url=urljoin(self.url, path),
  49. action=method.lower(),
  50. encoding=encoding,
  51. fields=fields,
  52. description=_method_desc
  53. )
  54. class SwaggerSchemaView(APIView):
  55. exclude_from_schema = True
  56. permission_classes = [AllowAny]
  57. renderer_classes = [
  58. renderers.OpenAPIRenderer,
  59. renderers.SwaggerUIRenderer
  60. ]
  61. def get(self, request):
  62. generator = CustomSchemaGenerator()
  63. schema = generator.get_schema(request=request)
  64. return Response(schema)

exclude_from_schema = True removes the swagger view from schema.

CustomSchemaGenerator
class overrides the default DRF SchemaGenerator so that it first
checks if the view has .__doc__ inside it, if available it uses this YAML
to make parameter fields, otherwise it looks for serializers.
So in this way we acheive the functionality of Django Rest Swagger (version 1)
which supported YAML docstrings as well.

SwaggerSchemaView
is the view made for swagger, which calls CustomSchemaGenerator
to create the schema instead of default SchemaGenerator.

Now, You ll need to install yaml as:

  1. pip install PyYAML

Next change demo/urls.py to point swagger url to this SwaggerSchemaView as:

  1. from .swagger_schema import SwaggerSchemaView
  2. urlpatterns = [
  3. ...
  4. url(r'^swagger/', SwaggerSchemaView.as_view()),
  5. ]
Defining parameters in FBV using YAML:

Now add __doc__ to the function based API by adding YAML
into it, add following YAML to save_medical in demo/fbv_demo/views.py as:

  1. @api_view(['POST'])
  2. def save_medical(request):
  3. # ----- YAML below for Swagger -----
  4. """
  5. description: This API deletes/uninstalls a device.
  6. parameters:
  7. - name: name
  8. type: string
  9. required: true
  10. location: form
  11. - name: bloodgroup
  12. type: string
  13. required: true
  14. location: form
  15. - name: birthmark
  16. type: string
  17. required: true
  18. location: form
  19. """
  20. ...
  21. ...

Now CustomSchemaGenerator will be able to read this YAML
and create input parameters against it accordingly.

Go to swagger url :
http://127.0.0.1:8000/swagger/

You will be able to see input parameters fields for
save_medical POST API as well.

Customize Swagger UI:

Swagger UI can be customized to some extent,
which is sufficient for a normal user, Follow these simple steps:

  • Make the file as demo/templates/rest_framework_swagger/index.html
    and add following code to it:
  1. {% extends "rest_framework_swagger/base.html" %}
  2. {# TO Customizer Swagger UI #}
  3. {% block header %}
  4. <div id='header' style="background-color: #000000;">
  5. <div class="swagger-ui-wrap">
  6. {% load staticfiles %}
  7. <a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="{% static 'rest_framework_swagger/images/logo_small.png' %}" /><span class="logo__title">swagger</span></a>
  8. <form id='api_selector'>
  9. <input id="input_baseUrl" name="baseUrl" type="hidden"/>
  10. {% if USE_SESSION_AUTH %}
  11. {% csrf_token %}
  12. {% if request.user.is_authenticated %}
  13. <div class="input">
  14. {% block user_context_message %}
  15. {# Override this block to customize #}
  16. Hello, {{ request.user }}
  17. {% endblock %}
  18. </div>
  19. {% endif %}
  20. {% block extra_nav %}
  21. {# Override this block to add more buttons, content to nav bar. #}
  22. {% endblock %}
  23. {% endif %}
  24. {% if USE_SESSION_AUTH %}
  25. {% if request.user.is_authenticated %}
  26. <div class='input'><a id="auth" class="header__btn" href="{{ LOGOUT_URL }}?next={{ request.path }}" data-sw-translate>Django Logout</a></div>
  27. {% else %}
  28. <div class='input'><a id="auth" class="header__btn" href="{{ LOGIN_URL }}?next={{ request.path }}" data-sw-translate>Django Login</a></div>
  29. {% endif %}
  30. {% endif %}
  31. <div id='auth_container'></div>
  32. </form>
  33. </div>
  34. </div>
  35. {% endblock %}

This will override the tag {% block header %} , for the sample we are
just changing the colour of header, rest remains the same.

  • Set template directory in demo/settings.py as:
  1. TEMPLATES = [
  2. {
  3. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  4. 'DIRS': ['demo/templates/'],
  5. ...
  6. }
  7. ]

You ll be able to see custom BLACK header at http://127.0.0.1:8000/swagger/.

Some add-ons that may help:

Django Rest Swagger provide much customization, most of which is explained indepth
in its Official Doc .

Few mostly used customization is explained here for ease. Add SWAGGER_SETTINGS in
demo/settings.py to provide custom settings:

Swagger Login methods:

You can login to swagger by using button Authorize in the header of swagger UI.

In order to use django superuser username and password to login use:

  1. SWAGGER_SETTINGS = {
  2. 'SECURITY_DEFINITIONS': {
  3. 'basic': {
  4. 'type': 'basic'
  5. }
  6. },
  7. }

In order to use authentication token to login to swagger use:

  1. SWAGGER_SETTINGS = {
  2. 'SECURITY_DEFINITIONS': {
  3. "api_key": {
  4. "type": "apiKey",
  5. "name": "Authorization",
  6. "in": "header"
  7. },
  8. },
  9. }

You can use oauth2 for security. Details: security definitions .

DJANGO LOGIN Button:

You can also use Django Login button to login. It uses django admin panel
for authentication. In order to make it run correctly you must set LOGIN_URL
and LOGOUT_URL in swagger settings. For example if you have rest_framework’s
login mechanism, you can set:

  1. SWAGGER_SETTINGS = {
  2. 'USE_SESSION_AUTH': True,
  3. 'LOGIN_URL': 'rest_framework:login',
  4. 'LOGOUT_URL': 'rest_framework:logout',
  5. }

If you want to disable this button just set USE_SESSION_AUTH to False.

VALIDATION_URL:

A schema json is generated against APIs which you may like to get validated by swagger.io.
Its value can be set to any local deployment of validator. By default it is set to:
https://online.swagger.io/validator/. Set it to None to not validate
your schema.

  1. SWAGGER_SETTINGS = {
  2. ...
  3. 'VALIDATOR_URL': None,
  4. }

Additionally you can also validate your schema manually by copying your schema json to
https://editor.swagger.io/.

Customize format of parameters defined in DOCSTRING:

You can customize YAML parameters, the most important is location
that helps you with passing the data to API in different forms.

  • location: form , to access data as request.POST['key']

  • location: body , to access data as request.body , you may have to
    add JSON_EDITOR: False in SWAGGER_SETTINGS in demo/settings.py.

  • location: query , to access data as request.GET['key']

Other location options include location: post and location: path

Common issues and their solution:

Since the release of version 2 of django-rest-swagger, various issues arise
because this version is fundamentally different from previous versions,
i.e. DOCSTRING has been deprecated by DRF and now swagger
uses DRF ‘s SchemaGenerator to generate schema.

Here are some of the reported issues and their possible solution:

  • The schema generator did not return a schema Document: All the APIs
    of your project may be authentication protected, try removing authentication
    from few of them.

  • Input parameters for class based views do not appear: You might be using
    APIView for making class based views. Try using generics.GenericAPIView to
    make APIs. All the views must have serializer or Docstring __doc__.

  • Input parameters for function based views do not appear: You might be using
    incompatible versions, try using djangorestframework==3.5.3 and django-rest-swagger==2.1.1.
    In order to use other versions you may need to modify get_link inside demo/swagger_schema.py.

  • TypeError: ‘type’ object is not iterable: Try adding your authentication in this
    format @permission_classes((IsAuthenticated, )).

  • Invalid block tag: ‘static’, expected ‘endblock’. Did you forget to register or load this tag? : Try
    adding {% load staticfiles %} to the overridden block of swagger UI e.g. add in demo/templates/rest_framework_swagger/index.html as:

  1. {% extends "rest_framework_swagger/base.html" %}
  2. {% block header %}
  3. <div id='header' style="background-color: #000000;">
  4. <div class="swagger-ui-wrap">
  5. {% load staticfiles %}
  6. ...
  7. ...
  8. </div>
  9. </div>
  10. {% endblock %}
  • swagger-error schemaValidationMessages message:”Can’t read from file http://0.0.0.0:8000/swagger/?format=openapi : This error is rendered
    because you schema validation is enabled in swagger settings but your server is not accessible from external network(which swagger.io
    tries to access for validation purpose). Try running your server with python manage.py runserver 0.0.0.0:8000 --insecure and in your settings.py
    add ALLOWED_HOSTS = ['*'] . You wont get this error while accessing server from external network now.

    Success in validation would show: swagger-success

  • swagger-error schemaValidationMessages message: “instance type () does not match any allowed primitive : This error means that your schema json does not validated
    by online.swagger.io/validator. You can disable validation by setting "VALIDATION_URL" : None in swagger settings or to fix the issue you can validate
    your schema better manually by pasting on https://editor.swagger.io/.

    Success in validation would show: swagger-success

End Note:

This repository is intended to help those who are facing issues
using django-rest-swagger 2. If you like this effort, please like and share this with others.
If you have any suggestions, comment here or approach me on linkedin , @m_haziq/">medium, twitter
or send me an email to discuss in person.
I would love to hear from you.