DRF

Django Rest Framework

DRF

Makes it easy to:

  • serialize objects to JSON

  • deseralize from JSON to objects

Class-based Views

DRF provides an APIView class, which subclasses Django's View class.

APIView vs regular View classes:

  • Requests passed to the handler methods will be REST framework's Request instances, not Django's HttpRequestinstances.

  • 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.

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)

Function Based Views

DRF also allows you to work with regular function based views. It provides a set of simple decorators that wrap your function based views to ensure they receive an instance of Request (rather than the usual Django HttpRequest) and allows them to return a Response (instead of a Django HttpResponse), and allow you to configure how the request is processed.

Signature: @api_view(http_method_names=['GET'])

Decorator, which takes a list of HTTP methods that your view should respond to.

Example: simple view that just manually returns some data:

This view will use the default renderers, parsers, authentication classes etc specified in the settings.

By default only GET methods will be accepted. Other methods will respond with "405 Method Not Allowed". To alter this behaviour, specify which methods the view allows, like so:

Viewsets

DRF allows you to combine the logic for a set of related views in a single class, called a ViewSet. In other frameworks called 'Resources' or 'Controllers'.

A type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create().

The method handlers for a ViewSet are only bound to the corresponding actions at the point of finalizing the view, using the .as_view() method.

Typically, rather than explicitly registering the views in a viewset in the urlconf, you'll register the viewset with a router class, that automatically determines the urlconf for you.

Let's define a simple viewset that can be used to list or retrieve all the users in the system.

If we need to, we can bind this viewset into two separate views, like so:

Typically we wouldn't do this, but would instead register the viewset with a router, and allow the urlconf to be automatically generated.

Rather than writing your own viewsets, you'll often want to use the existing base classes that provide a default set of behavior. For example:

2 advantages of using a ViewSet class over using a View class.

  • Repeated logic can be combined into a single class. In the above example, we only need to specify the queryset once, and it'll be used across multiple views.

  • By using routers, we no longer need to deal with wiring up the URL conf ourselves.

Both of these come with a trade-off. Using regular views and URL confs is more explicit and gives you more control. ViewSets are helpful if you want to get up and running quickly, or when you have a large API and you want to enforce a consistent URL configuration throughout.

The default routers included with REST framework will provide routes for a standard set of create/retrieve/update/destroy style actions, as shown below:

During dispatch, the following attributes are available on the ViewSet.

  • basename - the base to use for the URL names that are created.

  • action - the name of the current action (e.g., list, create).

  • detail - boolean indicating if the current action is configured for a list or detail view.

  • suffix - the display suffix for the viewset type - mirrors the detail attribute.

  • name - the display name for the viewset. This argument is mutually exclusive to suffix.

  • description - the display description for the individual view of a viewset.

You may inspect these attributes to adjust behaviour based on the current action. Example, restrict permissions to everything except the list:

Serializers

Definitinon

Allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types.

Also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

Declaration

Serialization

At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into json.

Deserializing objects

1st. Parse a stream into Python native datatypes...

2nd. restore those native datatypes into a dictionary of validated data.

Validation. When deserializing objects, you always need to call is_valid() before attempting to access the validated data, or save an object instance:

Dealing with nested objects

represent more complex objects, where some of the attributes of an object might not be simple datatypes such as strings, dates or integers.

The Serializer class is itself a type of Field, and can be used to represent relationships where one object type is nested inside another.

If a nested representation may optionally accept the None value you should pass the required=False flag to the nested serializer.

Similarly if a nested representation should be a list of items, you should pass the many=True flag to the nested serialized.

When dealing with nested representations that support deserializing the data, any errors with nested objects will be nested under the field name of the nested object.

Similarly, the .validated_data property will include nested data structures.

Writing .create() methods for nested representations

If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.

Example: handling creating a user with a nested profile object:

Writing .update() methods for nested representations

For updates you'll want to think carefully about how to handle updates to relationships. For example if the data for the relationship is None, or not provided, which of the following should occur?

  • Set the relationship to NULL in the database.

  • Delete the associated instance.

  • Ignore the data and leave the instance as it is.

  • Raise a validation error.

Example: an .update() method on our previous UserSerializer class.

Because the behavior of nested creates and updates can be ambiguous, and may require complex dependencies between related models, REST framework 3 requires you to always write these methods explicitly. The default ModelSerializer .create() and .update() methods do not include support for writable nested representations.

There are however, third-party packages available such as DRF Writable Nested that support automatic writable nested representations.

Handling saving related instances in model manager classes

An alternative to saving multiple related instances in the serializer is to write custom model manager classes that handle creating the correct instances.

For example, suppose we wanted to ensure that User instances and Profile instances are always created together as a pair. We might write a custom manager class that looks something like this:

This manager class now more nicely encapsulates that user instances and profile instances are always created at the same time. Our .create() method on the serializer class can now be re-written to use the new manager method.

For more details on this approach see the Django documentation on model managers, and this blogpost on using model and manager classes.

API Views

Django views that will use the previously created class to class to return JSON representation for each HTTP request that our API will handle.

Authentication

Throttling

DRF provedes 3 throttling classes:

  1. AnonRateThrottle: limits the rate of request that an anonymous user can make requests

  2. UserRateThrotle: limits the rate of request that a specific user can make requests

  3. ScopedRateThrottle: limits the rate of requests for specific parts of the API identified

Last updated