Swagger Implementation using function based views drf
Tags: django, python
Published: 2022-05-07 12:29:08

Hello friends, in this article we will be going over swagger UI implementation with function based views in django rest framework (drf).

I decided to write an article on this because recently I faced different problems when trying to add in basic functionalities of swagger UI into my API, I was using function based views instead of the more popular classed based view approach. 
 
In this article we will be building a basic product API with CRUD applications, using function based views and then adding swagger UI to make our API more presentable.


### Setting up Our Development Environment

We need to create our django project, but we need to setup our development environment.

Firstly create a working directory and in it, create a virtual environment, using:

```
mkdir django-swagger
cd django-swagger
virtualenv env
```
Next we need to activate our virtual environment, use the appropriate code that works for your OS, using any of the code below:

```
Windows --> env\Scripts\activate 
Linux --> source env/bin/activate
```
Now we install our dependencies, using:

```
pip install django djangorestframework
```
Next, we start our django project and then create an app, using:

```
django-admin startproject main .
python manage.py startapp prodcuts
```

### Creating Our Models
We have successfully created our development environment, now we need to create the model(s) we will work with. We only need a simple ``Product`` model with a few fields. Add this to your ``models.py`` file.

```
class Product(models.Model):
    title = models.CharField(max_length=224)
    description = models.CharField(max_length=255)
    
    def __str__(self):
        return self.title
```

## Creating Our Serializers
Now, we run our ``makemigrations`` command so as to populate our database with the ``Product`` table.
Next we create our ``serializers.py`` file in our ``product``, and add the following code.

```
from rest_framework import seriailizers
from .models import Product

class ProductSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Product 
        fields = "__all__"

class UpdateProductSerializer(serializers.ModelSerializer):

    class Meta:
        model = Product 
        fields = ["title", "description"]
    
    def update(self, instance, validated_data):
        instance.title = validated_data.get("title", instance.title)
        instance.description = validated_data.get("description", instance.description)
```

We now have our models and serializers ready. We now need to create ``CRUD`` views in our ``views,py`` file. Add this block of code to your ``views.py`` file.




### Create Product View
```
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status
from .models import Product 
from .serializers import ProductSerializer

@api_view(['POST'])
def create_product(request):
    if request.method == 'POST':
        create_product = ProductSerializer(data=request.data)
        if create_product.is_valid():
            title = create_product.validated_data.get('title')
            description = create_product.validated_data.get('description')
            product = Product.objects.create(title=title, description=description)
            product.save()
            return Response(create_product.data, status=status.HTTP_201_CREATED)
        else:
            return Response(create_product.errors, status=status.HTTP_400_BAD_REQUEST)
    else:
        return Response({"Error": "Invalid request type"}, status=status.HTTP_400_BAD_REQUEST)
```

### Read Product View
```
@api_view(['GET'])
def read_product(request):
    if request.method == 'GET':
        all_products = Product.objects.all()
        serialized_products = ProductSerializer(all_products, many=True)
        return Response(serialized_products.data,    status=status.HTTP_200_OK)
    else:
        return Response(serialized_products.errors, status=status.HTTP_400_BAD_REQUEST)
```

### Update Product View
```
@pi_view(['PATCH'])
def update_product(request, id):
    if request.method == 'PATCH':
        try: 
            product = Product.objects.get(id=id)
            update_seriailizer = UpdateProductSerializer(product, data=request.data, partial=True)
             if update_serializer.is_valid():
                update_serializer.save()
                return Response({"Message": "Product updated"}, status=status.HTTP_200_OK)
             else:
                 return Response(update_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        except Exception as e:
            return Response({"Error": f"Unexpected error {e} occurred.", status=status.HTTP_400_BAD_REQUEST)
    else:
        return Response({"Error": "Invalid request type", status=status.HTTP_400_BAD_REQUEST})
```

### Delete Product View
```
@api_view(['POST'])
def delete_view(request):
    if request.method == 'POST':
        all_products = Product.objects.all()
        all_product.delete()
        return Response({"Message": "All Products have been deleted"}, status=status.HTTP_200_OK)
    else:
        return Response({"Error": "Invalid request type"}, status=status.HTTP_400_BAD_REQUEST)
```

### Product View Urls 
Now, we need to create urls to help us access our endpoints. In our ``product`` directory we need to create a ``urls.py`` file and populate it with this code:

```
from django.urls import path 
from . import views 

urlpatterns = [
    path('create', views.create_product, name='create-product'),
    path('read', views.read_product, name='read-all-products'),
    path('update/<int:id>', views.update_product, name='update-products'),
    path('delete', views.delete_view, name='delete-product')
] 
```

Next we need to add our url configuration to the root directory ``urls.py`` file by adding this to the existing ``urlpatterns`` list:

```
path('product/', include('product.urls'))
```
Also make sure to import ``include`` alongside ``path`` from ``django.urls``.


## Swagger Implementation

We're done with building our products API, now, we will see how to use swagger UI with our API we have built. Firstly we need to install a package called ``drf_yasg``, this will help us with the swagger UI implementation. The ``yasg`` stands for ``Yet Another Swagger Generator``.

To install it, let's open our terminal and enter this command:
```
pip install drf_yasg
```
After the installation add it to your installed apps in your, like this:

```
INSTALLED_APPS = [
   'drf_yasg',
]
```
We have added the package to our installed apps, now to see the magic happen we need to add a few routes to our root ``urls.py`` file. 

Add this block of code to your root ``urls.py`` file, right below the ``django.urls`` import:

```
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
   openapi.Info(
      title="Products API",
      default_version='v1',
      description="Description",
      terms_of_service="https://www.google.com/policies/terms/",
      contact=openapi.Contact(email="<your-gmail>@gmail.com"),
      license=openapi.License(name="BSD License"),
   ),
   public=True,
   permission_classes=[permissions.AllowAny],
)
``` 

What this block of code does is to convert our generic django rest framework UI into swagger UI anytime we check our API in the browser. It will be much cleaner  and easier to understand.

We need to add this block of code too, this should go into the ``urlpatterns`` list:

```
    path('', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
    path('json/', schema_view.without_ui(cache_timeout=0), name='schema-json'),
```
This is what will help us view the swagger UI using routes.

Now if we run our server we should see this:

![Swagger Image 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tumnmgd8v3x164b2z361.png)


![Swagger Image 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fn0lzgiw10h1ypwi1spv.png)

What we have done up to this point is to convert our drf UI into swagger UI in the browser, we haven't really implemented. 

If you check you can only see the endpoints our API has, to allow our API send ``POST`` and ``UPDATE`` requests we need to configure that too.
Here is an example:

![Swagger Image 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5k915p5it99s52ztlga.png)

We should be able to send ``POST`` requests for our ``create-product`` endpoint but we can't, we can see that there are no fields for us to add our data for out ``POST`` request, but we can change that.

We need to add this import to our ``views.py`` file:
```
from drf_yasg.utils import swagger_auto_schema
```

``swagger_auto_schema`` is a decorator that helps us add the requests functionalities we need to our function based views. It takes several parameters, but the ones we're interested in are ``method`` and ``request_body``. 

``method`` specifies the type of request that is being sent to our endpoint.

``request_body`` specifies the serializer that the endpoint is going to use to send different requests, mainly used with ``POST``, ``PUT``, ``PATCH``, and ``DELETE`` methods.

To our ``Create Product View`` we need to add this block of code above our ``api_view`` decorator.

```
@swagger_auto_schema(method='POST', request_body=ProductSerializer)
```
After adding this, head on to your browser and see the effect of the change you've made, you should see this now: 


![Swagger Image 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iy9q1c9kkf9uqlmg1jxz.png)


![Swagger Image 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c8a3zoylbp8x7akasjev.png)

Now we can see that there is a ``required data object`` with the fields we specified in our serializer class which we will use to send a ``POST`` request. To send a request, click on the ``Try it out`` button and fill in the data object (if necessary) and hit ``Execute``.

We now need to do the same thing we did to our ``Create Product View`` to our other views. Adding the ``swagger_auto_schema`` decorator above the ``api_view`` decorator of each view with their corresponding methods and request_body serializer.

### Read Product View 
```
@swagger_auto_schema(method='GET')
```

## Update Product View
```
@swagger_auto_schema(method='PATCH', request_body=UpdateProductSerializer)
```

## Delete Product View
```
@swagger_auto_schema(method='POST', request_body=ProductSerializer)
```

Now run your server and see the new changes take effect. 
With this you can now implement swagger UI into drf using function based views.
If you find this article interesting and informative, kindly leave a like and share, and if you still have any questions, leave them in the comment section below. 
Cheers!!