Django Rest Framework 1
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'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.
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