Django Rest Framework 1

Views: 2151
Wrote on April 16, 2020, 6:03 p.m.

RESTful Designing

REST was defined by Roy Fielding, a computer scientist. He presented the REST principles in his PhD dissertation in 2000. REST stands for REpresentational State Transfer, which means when a RESTful API is called, the server will transfer to the client a representation of the state of the requested resource.

A good designed API is always very easy to use and makes the developer’s life very smooth. API is the GUI for developers, if it is confusing or not verbose, then the developer will start finding the alternatives or stop using it. Developers’ experience is the most important metric to measure the quality of the APIs.

1, Terminologies (Client, Resource, Collections, URI and URL)

What is client?

The client is the person or software who uses the API. It can be a developeror a web browser.

What is resource?

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service, a collection of other resources, a non-virtual object (e.g. a person), and so on. REST uses a resource identifier to identify the particular resource involved in an interaction between components.

URI stands for uniform resource identifier URL stands for uniform resource locator. Not all URIs are URLs because a URI could be a name instead of a locator.

URI Rules

  • A trailing forward slash (/) should not be included in URIs.
  • Forward slash separator (/) must be used to indicate a hierarchical relationship.
  • Hyphens (-) should be used to improve the readability of URIs.
  • Underscores (_) should not be used in URIs.
  • Lowercase letters should be preferred in URI paths.
  • File extensions should not be included in URIs.
  • Should the endpoint name be singular or plural?

2, API endpoint

An endpoint is one end of a communication channel. When an API interacts with another system, the touchpoints of this communication are considered endpoints. For APIs, an endpoint can include a URL of a server or service. Each endpoint is the location from which APIs can access the resources they need to carry out their function. The paths should contain the plural form of resources and the HTTP method should define the kind of action to be performed on the resource, the sample API endpoints would be:

GET /companies/3/employees should get the list of all employees from company 3 GET /companies/3/employees/45 should get the details of employee 45, which belongs to company 3 DELETE /companies/3/employees/45 should delete employee 45, which belongs to company 3 POST /companies should create a new company and return the details of the new company created

HTTP Methods

GET - retreive data from a server at the specified resource POST - send data to the API server PUT - create or update a resource HEAD - except without the response body DELETE - delete the resource at the specified URL PATCH - only apply partial modifications to the resource OPTIONS - return data describing what other methods and operations the server supports

HTTP response status codes

HTTP response status codes indicate whether a specific HTTP request has been successfully completed. Responses are grouped in five classes: 1, Informational responses (100–199), 2, Successful responses (200–299), 3, Redirects (300–399), 4, Client errors (400–499), and 5, Server errors (500–599).

Field name casing convention

You can follow any casing convention, but make sure it is consistent across the application. If the request body or response type is JSON then please follow camelCase to maintain the consistency.

Searching, sorting, filtering and pagination

All of these actions are simply the query on one dataset. There will be no new set of APIs to handle these actions. We need to append the query params with the GET method API. Let’s understand with few examples how to implement these actions.

  • Sorting In case, the client wants to get the sorted list of companies, the GET /companies endpoint should accept multiple sort params in the query. E.g GET /companies?sort=rank_asc would sort the companies by its rank in ascending order.

  • Filtering For filtering the dataset, we can pass various options through query params. E.g GET /companies?category=banking&location=india would filter the companies list data with the company category of Banking and where the location is India.

  • Searching When searching the company name in companies list the API endpoint should be GET /companies?search=Digital Mckinsey

  • Pagination When the dataset is too large, we divide the data set into smaller chunks, which helps in improving the performance and is easier to handle the response. Eg. GET /companies?page=23 means get the list of companies on 23rd page.

If adding many query params in GET methods makes the URI too long, the server may respond with 414 URI Too long HTTP status, in those cases params can also be passed in the request body of the POST method.

Versioning

When your APIs are being consumed by the world, upgrading the APIs with some breaking change would also lead to breaking the existing products or services using your APIs.

http://api.yourservice.com/v1/companies/34/employees is a good example, which has the version number of the API in the path. If there is any major breaking update, we can name the new set of APIs as v2 or v1.x.x

CBVs or FBVs

Class-Based Views vs. Function-Based Views

Django has two types of views; function-based views (FBVs), and class-based views (CBVs). Django originally started out with only FBVs, but then added CBVs as a way to templatize functionality so that you didn’t have to write boilerplate (i.e. the same code) code over and over again.

Function Based Views Function is very easy to implement and it’s very useful but the main disadvantage is that on a large Django project, usually a lot of similar functions in the views . If all objects of a Django project usually have CRUD operations so this code is repeated again and again unnecessarily and this was one of the reasons that the class-based views and generic views were created for solving that problem.

def contact(request):
    if request.method == 'POST':
        # Code block for POST request
    else:
        # Code block for GET request (will also match PUT, HEAD, DELETE, etc)

Pros - Simple to implement - Easy to read - Explicit code flow - Straightforward usage of decorators - good for one-off or specialized functionality

Cons - Hard to extend and reuse the code - Handling of HTTP methods via conditional branching

Class Based Views Class-based views provide an alternative way to implement views as Python objects instead of functions. They do not replace function-based views, but have certain differences and advantages when compared to function-based views.

from django.views import View

class ContactView(View):
    def get(self, request):
        # Code block for GET request

    def post(self, request):
        # Code block for POST request

Pros - Code reuseability — In CBV, a view class can be inherited by another view class and modified for a different use case. - DRY — Using CBVs help to reduce code duplication - Code extendability — CBV can be extended to include more functionalities using Mixins - Code structuring — In CBVs A class based view helps you respond to different http request with different class instance methods instead of conditional branching statements inside a single function based view. - Built-in generic class-based views

Cons - Harder to read - Implicit code flow - Use of view decorators require extra import, or method override

class-based views have an as_view() class method which returns a function that can be called when a request arrives for a URL matching the associated pattern. The function creates an instance of the class, calls setup() to initialize its attributes, and then calls its dispatch() method. dispatch looks at the request to determine whether it is a GET, POST, etc, and relays the request to a matching method if one is defined, or raises HttpResponseNotAllowed if not。

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
    self.setup(request, *args, **kwargs)
    if not hasattr(self, 'request'):
        raise AttributeError(
            "%s instance has no 'request' attribute. Did you override "
            "setup() and forget to call super()?" % cls.__name__
        )
    return self.dispatch(request, *args, **kwargs)

Do not ask which one is better, just pick the one fits your needs:)

Django REST Framework

Install

$ pip install djangorestframework

Register rest_framework app in settings.py under INSTALLED_APPS

APIView vs View

REST framework provides an APIView class, which subclasses Django's View class. APIView classes are different from regular View classes in the following ways:

  • Requests passed to the handler methods will be REST framework's Request instances, not Django's HttpRequest instances.
  • Handler methods may return REST framework's Response, instead of Django's HttpResponse. The view will manage content negotiation and setting the correct renderer on the response.
  • Any APIException exceptions will be caught and mediated into appropriate responses.
  • Incoming requests will be authenticated and appropriate permission and/or throttle checks will be run before dispatching the request to the handler method.

Using the APIView class is pretty much the same as using a regular View class, as usual, the incoming request is dispatched to an appropriate handler method such as .get() or .post(). Additionally, a number of attributes may be set on the class that control various aspects of the API policy.

For example:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User

class ListUsers(APIView):
    """
    View to list all users in the system.

    * Requires token authentication.
    * Only admin users are able to access this view.
    """
    authentication_classes = [authentication.TokenAuthentication]
    permission_classes = [permissions.IsAdminUser]

    def get(self, request, format=None):
        """
        Return a list of all users.
        """
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)

Django REST Framework's SessionAuthentication uses Django's session framework for authentication which requires CSRF to be checked. When you don't define any authentication_classes in your view/viewset, DRF uses this authentication classes as the default.

'DEFAULT_AUTHENTICATION_CLASSES'= (
    'rest_framework.authentication.SessionAuthentication',
    'rest_framework.authentication.BasicAuthentication'
),

Since DRF needs to support both session and non-session based authentication to the same views, it enforces CSRF check for only authenticated users. This means that only authenticated requests require CSRF tokens and anonymous requests may be sent without CSRF tokens. Let's play around the source code:

class APIView(View):
  ...
        view = super().as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs
  ...
        return csrf_exempt(view)

def csrf_exempt(view_func):
    def wrapped_view(*args, **kwargs):
        return view_func(*args, **kwargs)
    wrapped_view.csrf_exempt = True
    return wraps(view_func)(wrapped_view)

def dispatch(self, request, *args, **kwargs):
        ...
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

As you can see, APIView exempts the csrf and repacked (encapsulate) the request under dispatch(), the old request becomes _request, the new request is an object instance of the class Request.

def initialize_request(self, request, *args, **kwargs):
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context

def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):
    assert isinstance(request, HttpRequest), (
        'The `request` argument must be an instance of '
        '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
    )

    self._request = request // the old request

Serialization

Serialization in Django

  • .values JsonResponse
  • serializes.serialize (can serialize queryset)
    Doesn't work well with foreign key

Serialization in Django REST framework

Creating a model to work with

from django.db import models

class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()

Creating a Serializer class

from rest_framework import serializers
from snippets.models import Snippet

class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    ...

Working with Serializers

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

snippet = Snippet(code='foo = "bar"\n')
snippet.save()
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}


Reference:
RESTful API Designing guidelines — The best practices
Django : Class Based Views vs Function Based Views
Django REST Framework