app-generator / docs

App Generator - The Official Documentation | AppSeed
https://docs.appseed.us
1 stars 1 forks source link

Django REST Framework #75

Open mahfujul-helios opened 1 month ago

mahfujul-helios commented 1 month ago

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.

mahfujul-helios commented 1 month ago

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

Serialization

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"
        }
    }
]

sera

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.

cs

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"
    }
}

Conclusion

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.

Views

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-

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

Class-based Views

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.

Policy Attributes

If you want to override the default settings for your class-based views, you can use policy attributes.

Attributes and Usage

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)

Function-based Views

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.

Policy Decorators

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)

How Do DRF Views Work?

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:

  1. Content is automatically parsed according to the Content-Type header and is available as request.data.

  2. It supports PUT and PATCH methods (including file uploads). (Django only supports GET and POST methods.)

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

DRF Permissions

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.

View Permissions

APIView has two methods that check for permissions:

  1. check_permissions checks if the request should be permitted based on request data
  2. check_object_permissions checks if the request should be permitted based on the combination of the request and object data
# 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.

Permission classes

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

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

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:

has_permission vs has_object_permission

What's the difference between has_permission and has_object_permission in Django REST Framework?

has

Again, for:

Built-in DRF Permission Classes

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.

Custom Permission Classes

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.

Correct Usage

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:

  1. In has_permission, we refuse permission only to the unauthenticated users. At this point we don't have access to the object so we don't know if the user making the request is the author of the desired object.
  2. If the user is authenticated, after the object is retrieved, has_object_permission is called where we check if the object's author is the same as the user. Results:
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

Incorrect Usage

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:

m1

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:

m2

What's going on?

  1. The list view only checks has_permission. So, since the custom class doesn't have one, it checks has_permission from the BasePermission, which unconditionally returns True.
  2. The detail view first checks the has_permission (again, always True). Then it checks has_object_permission, which denies access to unauthenticated users.

DRF Pagination

PageNumberPagination

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/"
        }
    ]
}

LimitOffsetPagination

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/"
        }
    ]
}

CursorPagination

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/"
        }
    ]
}