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:
APIView
vs regular View
classes:Requests passed to the handler methods will be REST framework's
Request
instances, not Django'sHttpRequest
instances.Handler methods may return REST framework's
Response
, instead of Django'sHttpResponse
. 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
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:
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 thedetail
attribute.name
- the display name for the viewset. This argument is mutually exclusive tosuffix
.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.
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.
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:
AnonRateThrottle: limits the rate of request that an anonymous user can make requests
UserRateThrotle: limits the rate of request that a specific user can make requests
ScopedRateThrottle: limits the rate of requests for specific parts of the API identified
Last updated