Open mahfujul-helios opened 1 month ago
Django REST Framework (DRF) is a powerful toolkit for building Web APIs in Django. It simplifies the process of building APIs by providing a set of tools and functionalities that streamline common tasks such as serialization, authentication, permissions, and more.
Django Serialization is a powerful feature within the Django web framework that facilitates the conversion of complex data types, such as Django model instances or querysets, into more portable formats like JSON, XML, or YAML, and vice versa. This process allows for seamless communication between different parts of a web application or even across different applications, enabling efficient data transfer and interoperability. Through Django's built-in serializers, developers can effortlessly serialize and deserialize data, ensuring smooth integration with frontend interfaces, APIs, and external systems. Moreover, Django Serialization offers customizable serialization options, allowing developers to control the structure and presentation of the serialized data according to their specific requirements. Overall, Django Serialization plays a crucial role in simplifying data handling tasks and promoting flexibility and scalability within Django projects.
Example:
from django.core import serializers
from myapp.models import Book
book = Book.objects.get(pk=1) data = serializers.serialize('json', [book,])
This will generate JSON data that looks like this:
[
{
"model": "myapp.book",
"pk": 1,
"fields": {
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"published_date": "1925-04-10"
}
}
]
Deserializing data in Django
Deserializing data in Django involves converting simple data, such as JSON or XML, back into complex data. This can also be done using Django’s built-in serializers.
For example, let’s say you have JSON data that represents a Book instance:
{
"model": "myapp.book",
"pk": 1,
"fields": {
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"published_date": "1925-04-10"
}
}
You can use a Serializer to convert it back into a Django model:
from django.core import serializers
from myapp.models import Book
json_data = '{"model": "myapp.book", "pk": 1, "fields": {"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "published_date": "1925-04-10"}}'
book = serializers.deserialize('json', json_data, ignorenonexistent=True)
This will create a Book instance with the data from the JSON string.
Custom serialization in Django Django also allows for custom serialization, which means you can define your own serialization rules. This can be useful when you need to serialize data in a way that isn’t supported by Django’s built-in serializers. For example, let’s say you have a Django model called “Person” that has a field called “name“ and a related model called “Address” that has fields for “street”, “city”, and “state”. You want to serialize the Person instance along with their address, but you want the address to be nested inside the Person object.
To do this, you can create a custom serializer:
from rest_framework import serializers
from myapp.models import Person, Address
class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = ('street', 'city', 'state')
class PersonSerializer(serializers.ModelSerializer):
address = AddressSerializer()
class Meta:
model = Person
fields = ('name', 'address')
Here, we define a custom serializer for the Address model, which will be used to serialize the address data. Then, we define a custom serializer for the Person model, which includes the address serializer as a nested field.
When you serialize a Person instance using this serializer, the resulting JSON will look like this:
{
"name": "John Doe",
"address": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA"
}
}
Serialization is a critical component of web development, and Django provides powerful tools for serializing and deserializing data. By using Django’s built-in serializers or creating custom serializers, you can easily convert complex data into a format that can be easily transmitted and stored. However, it’s important to keep in mind the limitations and considerations of serialization to ensure that your Django application is secure and performs well.
Django REST Framework (DRF) views are the backbone of building RESTful APIs within Django applications. Views in DRF are analogous to Django's views but are specialized for handling HTTP methods like GET, POST, PUT, DELETE, etc., and returning serialized data. DRF provides several types of views, including APIView, which offers a high level of customization and control over request handling, and generic views such as ListAPIView and RetrieveAPIView, which provide commonly used patterns for retrieving lists of objects or single object instances. Additionally, DRF's ViewSets and ModelViewSet offer a powerful abstraction for dealing with CRUD (Create, Read, Update, Delete) operations on resources, reducing boilerplate code and promoting code reusability. With DRF views, developers can effortlessly define endpoints, handle requests, and serialize data, making it easier to create robust and scalable RESTful APIs in Django applications.
The essential component of DRF views is the APIView class, which subclasses Django's View class.
APIView class is a base for all the views that you might choose to use in your DRF application.
Whether it be-
viewsets
-they all use the APIView class.
As you can tell from the image below, the options you have at your disposal with regard to DRF views intertwine, extending from one other. You can think of the views as building blocks that form bigger building blocks. With that, you'll probably use some building blocks -- like APIViews, concrete views, and (ReadOnly)ModelViewSets -- more than others -- like mixins and GenericViewSets. This all depends on your specific application's needs, of course.
Class-based views extend the APIView class. With them, you determine how requests will be handled and which policy attributes you're going to use.
For example, let's say you have an Item class for your shopping list API:
class Item(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=100)
done = models.BooleanField()
Here's a view that allows users to delete all the items at once:
from rest_framework.response import Response
from rest_framework.views import APIView
class DeleteAllItems(APIView):
def delete(self, request):
Item.objects.all().delete()
return Response(status=status.HTTP_204_NO_CONTENT)
And here's a view that lists all the items:
from rest_framework.response import Response
from rest_framework.views import APIView
class ListItems(APIView):
def get(self, request):
items = Item.objects.all()
serializer = ItemSerializer(items, many=True)
return Response(serializer.data)
As you can see, the call to the database is done inside the handler functions. They're selected according to the request's HTTP method (e.g., GET -> get, DELETE -> delete).
We'll dive into more about how these views works here shortly.
As you can see, we've set a serializer in the second view. Serializers are responsible for converting complex data (e.g., querysets and model instances) to native Python datatypes that can then be rendered into JSON, XML, or other content types.
You can learn more about DRF serializers in the Effectively Using Django REST Framework Serializers article.
If you want to override the default settings for your class-based views, you can use policy attributes.
Attribute | Usage | Examples |
---|---|---|
renderer_classes |
Determines which media types the response returns. | JSONRenderer , BrowsableAPIRenderer |
parser_classes |
Determines which data parsers for different media types are allowed. | JSONParser , FileUploadParser |
authentication_classes |
Determines which authentication schemas are allowed for identifying the user. | TokenAuthentication , SessionAuthentication |
throttle_classes |
Determines if a request should be authorized based on the rate of requests. | AnonRateThrottle , UserRateThrottle |
permission_classes |
Determines if a request should be authorized based on user credentials. | IsAuthenticated , DjangoModelPermissions |
content_negotiation_class |
Selects one of the multiple possible representations of the resource to return to a client. (unlikely you'll want to set it up) | only custom content negotiation classes |
Be sure to review the Custom Permission Classes in Django REST Framework article to learn more about permission classes.
In the following example, we changed the permissions and how a response is rendered with the permission_classes and renderer_classes policy attributes:
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
class ItemsNotDone(APIView):
permission_classes = [IsAuthenticated] # policy attribute
renderer_classes = [JSONRenderer] # policy attribute
def get(self, request):
user_count = Item.objects.filter(done=False).count()
content = {'not_done': user_count}
return Response(content)
There are two ways to directly implement APIView: With a function or with a class. If you're writing a view in the form of a function, you'll need to use the @api_view decorator.
@api_view is a decorator that converts a function-based view into an APIView subclass (thus providing the Response and Request classes). It takes a list of allowed methods for the view as an argument.
# https://github.com/encode/django-rest-framework/blob/3.12.4/rest_framework/decorators.py#L16
def api_view(http_method_names=None):
http_method_names = ['GET'] if (http_method_names is None) else http_method_names
def decorator(func):
WrappedAPIView = type(
'WrappedAPIView',
(APIView,),
{'__doc__': func.__doc__}
)
# ...
return WrappedAPIView.as_view()
Here's a function-based view that does the same thing as the previously written class-based view, for deleting all items:
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(['DELETE'])
def delete_all_items(request):
Item.objects.all().delete()
return Response(status=status.HTTP_200_OK)
Here, we converted delete_all_items to an APIView subclass with the @api_view decorator. Only the DELETE method is allowed. Other methods will respond with "405 Method Not Allowed".
Overlooking the difference between how a class and a function are written, we have access to the same properties so both code snippets achieve the same result.
If you want to override the default settings for your function-based view, you can use policy decorators. You can use one or multiple of the following:
Those decorators correspond to APIView subclasses. Because the @api_view decorator checks if any of the following decorators are used, they need to be added below the api_view decorator.
If we use the same example that we did for the policy attributes, we can implement the decorators like so to achieve the same results:
from rest_framework.decorators import api_view, permission_classes, renderer_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
@api_view(['GET'])
@permission_classes([IsAuthenticated]) # policy decorator
@renderer_classes([JSONRenderer]) # policy decorator
def items_not_done(request):
user_count = Item.objects.filter(done=False).count()
content = {'not_done': user_count}
return Response(content)
When a request hits a view, the view first initializes a Request object, which is a DRF-enhanced HttpRequest from Django.
When compared to Django's HttpRequest, it has the following advantages:
Content is automatically parsed according to the Content-Type header and is available as request.data.
It supports PUT and PATCH methods (including file uploads). (Django only supports GET and POST methods.)
By temporarily overriding the method on a request, it checks permissions against other HTTP methods.
After creating the Request instance, the view stores the accepted info in the request using the provided (or default) content negotiator and renderers. After that, the view performs authentication and then checks for permissions and any throttling.
Authentication itself doesn't return any errors. It simply determines who the user of the request is. That information is required by the permission and throttle checks. Upon checking permissions, if authentication was not successful, the NotAuthenticated exception is raised. If the request is not permitted, a PermissionDenied exception is raised. Upon checking throttling, if the request is throttled, the Throttled exception is raised and the user is notified of how long they need to wait for the request to be permitted.
Permission checking actually has two parts: check_permissions and check_object_permissions.
check_permissions, which covers general permissions, is called before the view handler is executed. If you're only extending APIView, check_object_permissions doesn't get executed unless you explicitly call it. If you're using Generic Views or ViewSets, check_object_permissions is called for the detail views.
In DRF, permissions, along with authentication and throttling, are used to grant or deny access for different classes of users to different parts of an API.
Authentication and authorization work hand in hand. Authentication is always executed before authorization.
While authentication is the process of checking a user's identity (the user the request came from, the token that it was signed with), authorization is a process of checking if the request user has the necessary permissions for executing the request (are they a super user, are they the creators of the object).
The authorization process in DRF is covered by permissions.
APIView has two methods that check for permissions:
# rest_framework/views.py
class APIView(View):
# other methods
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
def check_object_permissions(self, request, obj):
"""
Check if the request should be permitted for a given object.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_object_permission(request, self, obj):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
When the request comes in, authentication is performed. If the authentication isn't successful, a NotAuthenticated error is raised. After that, the permissions get checked in a loop, and if any of them fail, a PermissionDenied error is raised. Finally, a throttling check is performed against the request.
check_permissions is called before the view handler is executed while check_object_permissions is not executed unless you explicitly call it. For example:
class MessageSingleAPI(APIView):
def get(self, request, pk):
message = get_object_or_404(Message.objects.all(), pk=pk)
self.check_object_permissions(request, message) # explicitly called
serializer = MessageSerializer(message)
return Response(serializer.data)
With ViewSets and Generic Views, check_object_permissions is called after the object is retrieved from the database for all detail views.
# rest_framework/generics.py
class GenericAPIView(views.APIView):
# other methods
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj) # HERE
return obj
Permission is checked for all permissions and if any one of them return False, a PermissionDenied error is raised.
Permissions in DRF are defined as a list of permission classes. You can either create your own or use one of the seven built-in classes. All permission classes, either custom or built-in, extend from the BasePermission class:
class BasePermission(metaclass=BasePermissionMetaclass):
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
return True
As you can see, BasePermission has two methods, has_permission and has_object_permission, that both return True. The permission classes override one or both of the methods to conditionally return True.
Turn back to the check_permissions and check_object_permissions methods from the beginning of the article:
has_permission is used to decide whether a request and a user are allowed to access a specific view
For example:
has_permission possesses knowledge about the request, but not about the object of the request.
As explained at the beginning, has_permission (called by check_permissions) gets executed before the view handler is executed, without explicitly calling it.
has_object_permission is used to decide whether a specific user is allowed to interact with a specific object
For example:
Besides the knowledge of the request, has_object_permission also possesses data about the object of the request. The method executes after the object is retrieved from the database.
Unlike has_permission, has_object_permission isn't always executed by default:
With an APIView, you must explicitly call check_object_permission to execute has_object_permission for all permission classes.
With ViewSets (like ModelViewSet) or Generic Views (like RetrieveAPIView), has_object_permission is executed via check_object_permission inside a get_object method out of the box.
has_object_permission is never executed for list views (regardless of the view you're extending from) or when the request method is POST (since the object doesn't exist yet).
When any has_permission returns False, the has_object_permission doesn't get checked. The request is immediately rejected.
What's the difference between has_permission and has_object_permission in Django REST Framework?
Again, for:
With regard to the built-in DRF permission classes, all of them override has_permission while only DjangoObjectPermissions overrides has_object_permission:
Permission Class | has_permission |
has_object_permission |
---|---|---|
AllowAny | ✓ | ✗ |
IsAuthenticated | ✓ | ✗ |
IsAuthenticatedOrReadOnly | ✓ | ✗ |
IsAdminUser | ✓ | ✗ |
DjangoModelPermissions | ✓ | ✗ |
DjangoModelPermissionsOrAnonReadOnly | ✓ | ✗ |
DjangoObjectPermissions | by extending DjangoModelPermissions | ✓ |
For more on the built-in permission classes, be sure to check out the second article in this series, Built-in Permission Classes in Django REST Framework.
For custom permission classes, you can override one or both of the methods. If you override only one of them you need to be careful, especially if the permissions you're using are complicated or you're combining multiple permissions. Both has_permission and has_object_permission defaults to True, so if you don't set one of them explicitly, the denial of the request is dependent on the one you've explicitly set.
For more on custom permission classes, be sure to check out the second article in this series, Custom Permission Classes in Django REST Framework.
Let's look at a quick example:
from rest_framework import permissions
class AuthorOrReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
return True
return False
def has_object_permission(self, request, view, obj):
if obj.author == request.user:
return True
return False
This permission class allows only the author of the object access to it:
Permission Aspect | List View | Detail View |
---|---|---|
has_permission |
Grants permission to the authenticated user | Grants permission to the authenticated user |
has_object_permission |
Has no impact | Grants permission to the author of the object |
Result | Access granted for the authenticated users | Access granted to the owner of the object if they are authenticated |
Let's look at a permission that won't do what we want, to better understand what's going on:
from rest_framework import permissions
class AuthenticatedOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.is_authenticated:
return True
return False
This permission denies access to the unauthenticated user, but the check is done in has_object_permission instead of has_permission.
Detail view for unauthenticated user:
Even though the auto-generated browsable API shows the delete button, the unauthenticated user can't delete the message.
And the list view for unauthenticated user:
The PageNumberPagination style accepts a single number page number in the request query parameters. To enable this pagination style globally, you can set rest_framework.pagination.PageNumberPagination class to DEFAULT_PAGINATION_CLASS and also set the PAGE_SIZE as desired. You can open the settings.py file and add the below configuration settings.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 2,
}
You can also modify the pagination style by overriding the attributes included in the PageNumberPagination class. Let’s look at the available attributes.
Let’s add a few more robot details to our database. The HTTPie commands are:
http POST :8000/robot/ name=”M-10iD/8L” robot_category=”Articulated Robots” currency=”USD” price=20000 manufacturer=”Fanuc” manufacturing_date=”2020-02-12 00:00:00+00:00″
http POST :8000/robot/ name=”SR-6iA” robot_category=”SCARA Robots” currency=”USD” price=10000 manufacturer=”Fanuc” manufacturing_date=”2020-02-12 00:00:00+00:00″
Now, let’s compose and send an HTTP GET request and analyze the paginated results.
http :8000/robot/
Output:
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 531
Content-Type: application/json
Date: Mon, 01 Feb 2021 05:53:29 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"count": 4,
"next": "http://localhost:8000/robot/?page=2",
"previous": null,
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2019-10-12T00:00:00Z",
"name": "FANUC M-710ic/50",
"price": 37000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/1/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "ABB",
"manufacturing_date": "2020-05-10T00:00:00Z",
"name": "IRB 910SC",
"price": 27000,
"robot_category": "SCARA Robots",
"url": "http://localhost:8000/robot/2/"
}
]
}
You can notice that the response looks different from the previous HTTP GET request. The response has the following keys:
http :8000/robot/?page=2
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 516
Content-Type: application/json
Date: Mon, 01 Feb 2021 05:52:36 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"count": 4,
"next": null,
"previous": "http://localhost:8000/robot/",
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2020-02-12T00:00:00Z",
"name": "M-10iD/8L",
"price": 20000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/4/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2020-02-12T00:00:00Z",
"name": "SR-6iA",
"price": 10000,
"robot_category": "SCARA Robots",
"url": "http://localhost:8000/robot/5/"
}
]
}
In LimitOffsetPagination style, client includes both a “limit” and an “offset” query parameter. The limit indicates the maximum number of items to return, same as that of the page_size. The offset indicates the starting position of the query w.r.t unpaginated items. To enable the LimitOffsetPagination style globally, you can set rest_framework.pagination.LimitOffsetPagination class to DEFAULT_PAGINATION_CLASS. The configuration as follows:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 2,
}
You can skip setting the PAGE_SIZE. If set, then the client can omit the limit query parameter.
If you want to modify the pagination style, you can override the attributes of the LimitOffsetPagination class.
http :8000/robot/
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 541
Content-Type: application/json
Date: Mon, 01 Feb 2021 06:47:42 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"count": 4,
"next": "http://localhost:8000/robot/?limit=2&offset=2",
"previous": null,
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2019-10-12T00:00:00Z",
"name": "FANUC M-710ic/50",
"price": 37000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/1/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "ABB",
"manufacturing_date": "2020-05-10T00:00:00Z",
"name": "IRB 910SC",
"price": 27000,
"robot_category": "SCARA Robots",
"url": "http://localhost:8000/robot/2/"
}
]
}
Let’s try another HTTPie command based on the next field value from the above output. The HTTPie command is
http GET “:8000/robot/?limit=2&offset=2”
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 524
Content-Type: application/json
Date: Mon, 01 Feb 2021 06:52:35 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"count": 4,
"next": null,
"previous": "http://localhost:8000/robot/?limit=2",
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2020-02-12T00:00:00Z",
"name": "M-10iD/8L",
"price": 20000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/4/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2020-02-12T00:00:00Z",
"name": "SR-6iA",
"price": 10000,
"robot_category": "SCARA Robots",
"url": "http://localhost:8000/robot/5/"
}
]
}
http GET “:8000/robot/?limit=1&offset=0”
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 325
Content-Type: application/json
Date: Mon, 01 Feb 2021 10:36:19 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"count": 4,
"next": "http://localhost:8000/robot/?limit=1&offset=1",
"previous": null,
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2019-10-12T00:00:00Z",
"name": "FANUC M-710ic/50",
"price": 37000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/1/"
}
]
}
The CursorPagination provides a cursor indicator to page through the result set. It provides only forward or reverse controls and doesn’t permit the client to navigate to arbitrary positions. The CursorPagination style assumes that there must be a created timestamp field on the model instance and it orders the results by ‘-created’. To enable the CursorPagination style you can mention rest_framework.pagination.CursorPagination class in DEFAULT_PAGINATION_CLASS.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
'PAGE_SIZE': 2,
}
Let’s do a look at the set of attributes that we can modify in CursorPagination class. They are as follows:
Let’s see how to customize the CursorPagination class. Here we will override the ordering attribute. By default, it will order based on the created timestamp. Here, we will use the id field instead of the created field for ordering.
Let’s create a new file named custompagination.py file in the apps (robots) folder and add the below code
from rest_framework.pagination import CursorPagination
class CursorPaginationWithOrdering(CursorPagination):
# order based on id
ordering = 'id'
Here we override the ordering attribute provided by the CursorPagination class. Next, you can mention the customized class in the DEFAULT_PAGINATION_CLASS as shown below.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'robots.custompagination.CursorPaginationWithOrdering',
'PAGE_SIZE': 2,
}
Let’s analyze the output. You can send the below HTTP command.
http :8000/robot/
output:
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 526
Content-Type: application/json
Date: Mon, 01 Feb 2021 11:09:45 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"next": "http://localhost:8000/robot/?cursor=cD0y",
"previous": null,
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2019-10-12T00:00:00Z",
"name": "FANUC M-710ic/50",
"price": 37000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/1/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "ABB",
"manufacturing_date": "2020-05-10T00:00:00Z",
"name": "IRB 910SC",
"price": 27000,
"robot_category": "SCARA Robots",
"url": "http://localhost:8000/robot/2/"
}
]
}
Now, let’s compose an HTTP request based on the next value from the above output (cursor=cD0y). The HTTPie command is:
http :8000/robot/?cursor=cD0y
output:
HTTP/1.1 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 530
Content-Type: application/json
Date: Mon, 01 Feb 2021 11:10:38 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.7.5
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"next": null,
"previous": "http://localhost:8000/robot/?cursor=cj0xJnA9NA%3D%3D",
"results": [
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2020-02-12T00:00:00Z",
"name": "M-10iD/8L",
"price": 20000,
"robot_category": "Articulated Robots",
"url": "http://localhost:8000/robot/4/"
},
{
"currency": "USD",
"currency_name": "US Dollar",
"manufacturer": "Fanuc",
"manufacturing_date": "2020-02-12T00:00:00Z",
"name": "SR-6iA",
"price": 10000,
"robot_category": "SCARA Robots",
"url": "http://localhost:8000/robot/5/"
}
]
}
Django REST Framework
Django REST Framework (DRF) is a powerful toolkit for building Web APIs in Django. It simplifies the process of building APIs by providing a set of tools and functionalities that streamline common tasks such as serialization, authentication, permissions, and more.
Features
Advantages
Conclusion
Django REST Framework is a versatile toolkit for building Web APIs in Django applications. Its rich feature set, coupled with its simplicity and flexibility, makes it the preferred choice for many developers when building RESTful APIs with Django. Whether you're building a small prototype or a large-scale enterprise application, DRF provides the tools and capabilities you need to succeed.