Applications running on the web are rarely isolated from each other. The internet serves not only static pages but also allows applications to share data using web services. REST (Representational state transfer) as an architectural style for creating web service remains the most implemented architecture by organizations to deployed web services between dynamic applications.
To create web services in python, developers leverage the Django framework’s utilities and tools to speed up the process of implementing business functions as electronic services—Create records, compute numbers, and update persistent data. Nonetheless, writing efficient web service endpoints requires a clear understanding of what Django (Django rest framework) offers to create a fully Restful API. And developers need to know how to use it in different scenarios. Failing to grasp functionalities of the API—Django, in this case, would lead to a lot of inconsistency in terms of data retrieval, maintainability of the applications ultimately, failure to meet business functions. The following figure depicts how Django and the djangorestframework interact to handles requests and responses.
The first thing to retain from figure 1.0 is that Django is a web framework and djangorestframework is an application that extends Django views, URL configuration, models, and other classes to serialize data in different content types before writing or reading data from the database.
Second, the views classes like the generics or view set always take precedence over serializers when handling incoming requests. The serializers packages or classes are closer to the models instead of the templates or the URL dispatcher.
Lastly, when the client issues a request, the URL dispatcher validates a URL against URL configuration. If it is valid, it passes the request to the views classes that handle respective HTTP verbs if one exists. Next, the serializers take control to validate data which goes down to the model for the final validation before hitting the database. Finally, the response goes upward from the models’ classes to the serializers, the views, and the client.
Before Creating A REST API
We create Restful API resources to be consumed by other applications. Thus, before writing resources endpoints, we need to design URLs, define data and the content type that other applications or web services will need to achieve businesses objectives. Bear in mind that we exclude the RDBMS configuration that is not subject to the scope of this blog. However, we’ll look at a simple data model in Django that djangorestframework would require to expose data via a valid HTTP resource endpoint.
Data Model
As mentioned above, the sole purpose of a web service is to communicate data between applications. So, we need to define information (not data) that the restful API will read and write to the database via a URL endpoint. Here is a simple model class that defines properties of the entity called payment—Supposed a business wants to record customer payments.
There is a lot to unpack about this model. But one thing we need to point out is the usage of `timezone.now` instead of using auto_now_add as a parameter on the paid_on field. In this case, we enforce the Datetime string's format for the paid_on's attribute—This will make it easier to query DateTime as a query parameter.
Serialization
Although Django automatically generates the models.py for us during the app creation, it does create the serializers.py that djangorestframework needs to bind data between our views and the model class. So, we must make a serializers.py file that handles the serialization of all incoming and outgoing data.
__author__ = "Josue Kula"
__copyright__ = "Copyright (c) 2021 josuekula.com"
from rest_framework import fields
from rest_framework.serializers import ModelSerializer
from .models import Payment
class CustomerPaymentSerializer(ModelSerializer):
paid_on = fields.DateTimeField(format="%Y-%m-%dT%H:%M:%S",
read_only=True)
class Meta:
model = Payment
fields = ['id', 'user', 'paid_on', 'amount']
def validate(self, attrs):
instance = Payment(**attrs)
instance.clean()
return attrs
As you see, we have a simple serializer class that extends ModelSerializer with a paid_on field which should be read-only and keeps the same DateTime format from the database.
Resquest Handler: Views Class
If you have used hapi.js before or similar javascript Restful frameworks, you might be familiar with the famous handler function that controls all HTTP requests and responses on a given endpoint. In Django, the file views.py defines a class that extends other `djangorestframework` classes to create views.
There are different ways to write views in Django; one can use a simple function or a class-based View. It's worth noting here that we are using the class ListCreateAPIView from the `restframework.generics` package instead of APIView, ViewSet, or ModelViewSet.
In urls.py, we have called an undefined class view. So, let's create the actual class that will handle all incoming requests.
__author__ = "Josue Kula"
__copyright__ = "Copyright (c) 2021 josuekula.com"
from rest_framework.generics import ListCreateAPIView
from rest_framework.response import Response
from rest_framework import status
from django.core.exceptions import FieldError
from .models import Payment
from .serializers import CustomerPaymentSerializer
class CustomerPayments(ListCreateAPIView):
""" Retrieve and create payment data model.
Filter data with valid attributes as query parameters.
:returns an Array of payments, single payment object
or 404 not found message.
"""
serializer_class = CustomerPaymentSerializer
def get_queryset(self):
payments_qs = Payment.objects.all()
filtering_dict = dict([qs for qs in self.request.query_params.items()
if qs[1] != ''])
try:
payments = payments_qs.filter(**filtering_dict)
except FieldError:
return []
return payments
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = CustomerPaymentSerializer(queryset, many=True,
context={'request': request})
if not serializer.data:
return Response({"result": f"No record found."},
status=status.HTTP_404_NOT_FOUND)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
serializer = CustomerPaymentSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The first thing to mention about our view is that it only Implements two main business functions—Information retrieval and creation. That being the case, our exposed endpoint can handle GET (HEAD) and POST without considering another idempotent verb like OPTIONS.
Second, our view class does not require authentication to create or retrieve information from the database.
Lastly, we have the `get_queryset()` that handles Queryset filtering before passing data to the serializer for validation then onto the response object.
Contributions
Djangorestframework has been for many the technology of choice (under python) for creating a restful API as quickly as possible, but requirements sometimes get out of control. Therefore, developers should have a better understanding of REST architecture and object-oriented concepts to deal with complexity.
In this blog, we saw how the ListCreateAPIView from the generics package facilitates writing views to avoid unnecessary repetitions by abstracting the function that handles Queryset from HTTP method handlers—get() or post(). The good news from the generics is that you don’t have to extend the ModelSerializer class on your serializer class.