Metadata-Version: 2.1
Name: django-rest
Version: 0.8.6
Summary: Tiny, lightweight, blazing fast REST library for django
Home-page: https://github.com/alaouimehdi1995/django-rest/
Author: Mehdi Alaoui
Author-email: alaoui.mehdi.1995@gmail.com
License: MIT
Project-URL: Source, https://github.com/alaouimehdi1995/django-rest/
Description: # django-REST
        
        [![PyPI version](https://badge.fury.io/py/django-rest.svg)](https://badge.fury.io/py/django-rest)
        [![PyPI pyversions](https://img.shields.io/pypi/pyversions/django-rest.svg)](https://pypi.python.org/pypi/django-rest/)
        [![PyPI license](https://img.shields.io/pypi/l/django-rest.svg)](https://pypi.python.org/pypi/django-rest/)
        [![PyPI status](https://img.shields.io/pypi/status/django-rest.svg)](https://pypi.python.org/pypi/django-rest/)
        
        [![Build Status](https://travis-ci.org/alaouimehdi1995/django-rest.png?branch=master)](https://travis-ci.org/alaouimehdi1995/django-rest)
        [![codecov](https://codecov.io/gh/alaouimehdi1995/django-rest/branch/master/graph/badge.svg)](https://codecov.io/gh/alaouimehdi1995/django-rest)
        [![Known Vulnerabilities](https://snyk.io/test/github/alaouimehdi1995/django-rest/badge.svg)](https://snyk.io/test/github/alaouimehdi1995/django-rest)
        [![Maintainability](https://api.codeclimate.com/v1/badges/bcfd8d86afef77cf14ab/maintainability)](https://codeclimate.com/github/alaouimehdi1995/django-rest/maintainability)
        
        # Overview
        
        django-REST is a **tiny**, **lightweight**, **easy-to-use** and **incredibly fast** library to implement
        REST views with django. The whole library's focused in **one** decorator that transforms the
        simple views into REST ones, allowing easy customizations (such as permissions, serializers, etc.)
        
        The library itself was highly inspired from the great [django-rest-framework](https://www.django-rest-framework.org/) and [SerPy](https://serpy.readthedocs.io/en/latest/)
        
        # Table of contents
        
        1. [Overview](#overview)
        2. [Table of contents](#table-of-contents)
        3. [Requirements](#requirements)
        4. [Installation](#installation)
        5. [Example](#example)
        6. [Documentation](#documentation)
        
           1. [The @api_view decorator](#1-the-api_view-decorator)
              1. [Decorator argments](#11-decorator-arguments)
              2. [Decorated view's arguments](#12-decorated-views-arguments)
              3. [How to decorate a view](#13-how-to-decorate-a-view)
           2. [View Permissions](#2-view-permissions)
              1. [Introduction](#21-introduction)
              2. [Available Permissions](#22-available-permissions)
              3. [Permissions Operators](#23-permissions-operators)
              4. [Implement your own permission](#24-implement-your-own-permission)
           3. [Deserializers](#3-deserializers)
              1. [Introduction](#31-introduction)
              2. [Impmement a new Deserializer](#32-implement-a-new-deserializer)
              3. [Available Deserializer Fields](#33-available-deserializer-fields)
              4. [Nested Deserializers](#34-nested-deserializers)
              5. [Post-clean methods](#35-post-clean-methods)
              6. [All-pass Deserializer](#36-all-pass-deserializer)
           4. [Serializers](#4-serializers)
              1. [Introduction](#41-introduction)
              2. [Impmement a new Serializer](#42-implement-a-new-serializer)
              3. [Available Serializer Fields](#43-available-serializer-fields)
                 1. [Primitive types](#1-primitive-types)
                 2. [MethodField](#2-methodfield)
                 3. [ConstantField](#3-constantfield)
                 4. [ListField](#4-listfield)
              4. [Nested Serializers](#44-nested-serializers)
              5. [DictSerializer](#45-dictserializer)
           5. [Exceptions](#5-exceptions)
              1. [@api_view exceptions catching](#51-api_view-exceptions-catching)
              2. [Existing API Exceptions](#52-existing-api-exceptions)
              3. [Define your own API Exception](#53-define-your-own-api-exception)
           6. [HTTP](#6-http)
              1. [HTTP Status codes](#62-http-status-codes)
              2. [HTTP Methods](#63-http-methods)
        
        # Requirements
        
        django-REST library requires:
        
        -  Python version 2.7+ or 3.3+
        -  django version 1.10+
        
        # Installation
        
        You can get the package using `pip`, as the following:
        
        ```bash
        pip install django-rest
        ```
        
        # Example
        
        Let's implement a quick public API endpoint that lists existing regular (_i.e._ not staff) users:
        
        First, start a new django project:
        
        ```sh
        pip install django-rest # Will install django if not already installed
        django-admin startproject first_project .
        ./manage.py migrate
        ./manage.py createsuperuser
        # Follow instructions
        ```
        
        Let's get started by implementing the views in `./first_project/urls.py`:
        
        ```python
        from typing import Dict
        
        from django.contrib import admin
        from django.contrib.auth.models import User
        from django.http import JsonResponse
        from django.urls import path
        
        from django_rest.decorators import api_view
        from django_rest.http import status
        from django_rest.serializers import fields as fields, Serializer
        
        
        # The serializer defines the output format of our endpoints
        class UserSerializer(Serializer):
            id = fields.IntegerField()
            username = fields.CharField()
            email = fields.CharField()
            is_staff = fields.BooleanField()
        
        
        @api_view(allowed_methods=["GET"])
        def list_users_view(request, url_params: Dict, query_params: Dict, **kwargs):
            regular_users = User.objects.exclude(is_staff=True)
            return JsonResponse(
                UserSerializer(regular_users, many=True).data,
                status=status.HTTP_200_OK,
                safe=False,
            )
        
        
        urlpatterns = [
            path("admin/", admin.site.urls),
            path("api/users/", list_users_view),
        ]
        ```
        
        That's all! Now run the server:
        
        ```sh
        ./manage.py runserver
        ```
        
        In order to test your endpoints, you can use [PostMan](https://www.postman.com/), [httpie](https://httpie.org/) or [curl](https://curl.haxx.se/).
        I'll be using `httpie` in the example:
        
        ```sh
        http GET http://127.0.0.1:8000/api/users/
        
        HTTP/1.1 200 OK
        Content-Length: 84
        Content-Type: application/json
        Date: Sat, 30 May 2020 02:13:49 GMT
        Server: WSGIServer/0.2 CPython/3.6.9
        X-Content-Type-Options: nosniff
        X-Frame-Options: DENY
        
        []
        
        # After creating a new user from django-admin section (visit: http://127.0.0.1:8000/admin/ using your browser)
        
        http GET http://127.0.0.1:8000/api/users/
        
        HTTP/1.1 200 OK
        Content-Length: 84
        Content-Type: application/json
        Date: Sat, 30 May 2020 02:17:23 GMT
        Server: WSGIServer/0.2 CPython/3.6.9
        X-Content-Type-Options: nosniff
        X-Frame-Options: DENY
        
        [
            {
                "email": "myfirst@user.com",
                "id": 1,
                "is_staff": true,
                "username": "firstuser"
            }
        ]
        
        ```
        
        # Documentation
        
        ## 1. The `@api_view` decorator
        
        ### 1.1 Decorator arguments
        
        As shown in the example section, the `@api_view` could be used with multiple
        (optional) arguments:
        
        ```python
        api_view(
            permission_class: BasePermission = AllowAny,
            allowed_methods: Iterable[str] = ALL_HTTP_METHODS,
            deserializer_class: Union[
                Deserializer, Dict[str, Deserializer]
            ] = AllPassDeserializer,
            allow_forms: bool = False,
        )
        ```
        
        1. **permission_class**
        
           A class that defines who is allowed to access the
           decorated view. If no `permission_class` given, the decorator's default permission is
           `AllowAny` (your view is public).
        
           In case the user isn't allowed to access the view,
           a `403 Forbidden access` response will be returned before even executing the
           view's code. More details in [permissions section](#2-view-permissions).
        
        2. **allowed_methods**:
        
           A `list`/`tuple` of HTTP allowed methods. Allowed methods should
           be in uppercase strings (_ex.`GET`, `POST`, etc._). You can also use some
           predefined sets in `django_rest.http.methods`. If no `allowed_methods`
           given, all HTTP methods will be allowed.
        
           If the user requests the decorated view
           with a non-allowed method, a `405 Method not allowed` response will be
           returned before executing your view's code.
        
        3. **deserializer_class**:
        
           Could be either a sub-class of `Deserializer` (as shown in the
           previous example), or a `dict` that maps HTTP methods that use payload (_i.e._ `POST`, `PUT`
           and `PATCH`) to `Deserializer` sub-classes, as the following:
        
           ```python
           @api_view(deserializer_class=MyDeserializerClass)
           def first_case_view(request, **kwargs):
              # [...]
        
           @api_view(
              deserializer_class={
                 "POST": MyCustomPOSTDeserializer,
                 "PUT": MyCustomPUTDeserializer,
              },
           )
           def second_case_view(request, **kwargs):
              # [...]
           ```
        
           In the first case above, `MyDeserializerClass` will be applied to: `POST`,
           `PUT` and `PATCH` methods. Also, note that in second case, the `deserializer_class` mapping doesn't
           define a deserializer for the `PATCH` HTTP method. In this case,
           the "all-pass" deserializer (_i.e._ passes payload data to the view without any
           validation) will be used. The same deserializer will be applied if no
           `deserializer_class` is given.
        
           If the payload data doesn't respect the format defined in the deserializer,
           a `400 Bad Request` response will be returned.
        
        4. **allow_forms**:
        
           A `bool` that allows/forbids payloads coming from forms (
           `application/x-www-form-urlencoded` and `multipart/form-data` content-types).
        
           A `415 Unsupported Media Type` response will be returned in case the user sends form
           data to a view decorated with `allow_forms=False`. The argument's default value is `False`.
        
        ### 1.2 Decorated view's arguments
        
        As illustrated in the examples above, the `@api_view` decorator alters the
        decorated view's arguments. The decorator gathers, extracts and standardizes
        different arguments, then passes them to your view, in order to facilitate their
        use. Let's explain each argument:
        
        ```python
        @api_view
        def decorated_view(
            request: HttpRequest,
            url_params: Dict[str, Any],
            query_params: Dict[str, str],
            deserialized_data: Optional[Dict[str, Any]],
        ) -> JsonResponse:
           # For class methods, the first argument is `self` (the class instance)
           # [...]
        ```
        
        1. **request**:
        
           django's native request object (`django.http.request.HttpRequest`). Similar
           to every django view's first argument.
           More details on [django's documentation](https://docs.djangoproject.com/en/3.0/ref/request-response/)
        
        2. **url_params**:
        
           A `dict` containing the parameter defined in your view's
           route (django router). For example, let's take a look to `url_params` when requesting the URL `/api/hello/foo/bar/25/` in the following example:
        
           ```python
           # urls.py
           from django.urls import path
           from django_rest.decorators import api_view
        
           @api_view
           def hello_view(request, url_params, query_params, **kwargs):
              #  url_params = {"first_name": "foo", "last_name": "bar", "age": 25}
        
           urlpatterns = [
              path("api/hello/<str:first_name>/<str:last_name>/<int:age>/", hello_view),
           ]
           ```
        
           **Important note:** The parameters are already casted into their target types (_in
           the example above, `url_params['age']` is `int`, while `url_params['first_name']` is `str`_)
        
        3. **query_params**:
        
           A `dict` containing all the query parameters encoded in the request's URL.
           Let's request the previous example's view with the following URL:`/api/hello/foo/bar/25/?lang=fr&display=true`:
        
           ```python
           # views.py
           @api_view
           def hello_view(request, url_params, query_params, **kwargs):
              #  url_params = {"first_name": "foo", "last_name": "bar", "age": 25}
              #  query_params = {"lang": "fr", "display": "true"}
        
           ```
        
           **Important note:** Unlike `url_params`, for query parameters, the values are **ALWAYS** strings (`str`), and
           they should be casted manually.
        
        4. **deserialized_data**:
        
           A `dict` with the data validated by the deserializer. For HTTP
           methods without payload (`GET`, `DELETE`, _etc._), this argument's value is
           `None`.
        
           As explained in the section before, for HTTP methods requiring
           data, if no `deserializer_class`'s been given to the decorator, `deserialized_data`
           will contain the raw payload's data (without any validation).
        
        **Note:** In case you want to ignore a argument (let's say `deserialized_data`
        for a `GET` view), add `**kwargs` argument to your view. Otherwise, you'll have
        a arguments error.
        
        ### 1.3 How to decorate a view
        
        The `@api_view` decorator could be applied differently on the views, depending on your use-case. You can:
        
        1. Decorate a function-based view. For example:
           ```python
           @api_view(allowed_methods=['GET'])
           def hello_view(request, **kwargs):
              return JsonResponse({'message': 'Hello world'}, status=200)
           ```
        2. Decorate a whole class-based view (should be a sub-class of `django.view.View`). For example:
        
           ```python
           @api_view(permission_class=IsStaffUser)
           class HelloView(View):
              def get(self, request, **kwargs):
                 return JsonResponse({"message": "Hello world"}, status=200)
        
              def post(self, request, **kwargs):
                 return JsonResponse({}, status=201)
        
              def other_method(self, arg_1, arg_2):
                 # [...]
           ```
        
           For class-based views, the decorator decorates all view's http methods
           (`get()`, `post()`, `put()`, _etc._) and **ONLY** them. In the example above, all http methods are restricted for staff-users only, but `other_method` method hasn't been altered.
        
        **Note:** Both `@api_view()` and `@api_view` syntaxes are correct in case the decorator is used without arguments.
        
        ## 2. View Permissions
        
        ### 2.1 Introduction
        
        Permissions is what determines whether a request should be granted or denied
        access, for a particular view. The inspection process is done before executing
        the decorated view's code, then, if the request satisfies the
        permission's constraints, the access is granted. If not, a `403 Forbidden access`
        response is returned.
        
        In django-REST, all permissions inherit from `Permission`, and passed as argument to the `@api_view` decorator, as seen in
        the previous examples.
        
        In this section, we will start by introducing the django-REST's provided
        permissions, then how to build more complex permissions by combining the
        existing ones, and finally, how to implement your own custom permission.
        
        ### 2.2 Available Permissions
        
        All the permissions listed below could be imported from `django_rest.permissions`
        
        -  **AllowAny**:
           By choosing this permission, your view will be public (all requests will have granted access). It's the default permission for `@api_view` decorator.
        -  **IsAuthenticated**:
           Allows only authenticated users to access your view. Anonymous users (_i.e._ not authenticated) receive a `403 Forbidden access` response.
        -  **IsStaffUser**:
           The view can be accessed by staff users only. A staff user is a `User` object having `is_staff` attribute set to `True`.
        -  **IsAdminUser**:
           Admins are the only users who can access the decorated view. An admin is a `User` object having `is_superuser` attribute set to `True`.
        -  **IsReadOnly**:
           Only HTTP safe methods (`GET`, `HEAD` and `OPTIONS`) are allowed. For a `POST` request for example, the user receives a `403 Forbidden access`.
        
           This permission is not meant to be used in standalone, because, remember, the `@api_view` decorator has already the `allowed_methods` argument for this purpose, that returns a `405 Method not allowed`.
           It has been implemented only to be combined with other permissions in order to build a more complex ones (the next permission on the list is a good example).
        
        -  **IsAuthenticatedOrReadOnly**:
           This permission allows Authenticated users to use all HTTP methods (`GET`, `POST`, `DELETE`, _etc._), and anonymous users to use safe methods only (`GET`, `HEAD` and `OPTIONS`).
        
        ### 2.3 Permissions Operators
        
        Using logical operators allows you to combine different `Permission` sub-classes, in a simple and powerful way, to obtain more complex and complete permissions.
        
        django-REST provides you 4 logical operators: **AND** (`&`), **OR** (`|`), **XOR** (`^`) and **NOT** (`~`).
        
        Let's demonstrate those operators then their combinations in concrete examples:
        
        1. **AND operator**:
        
        Let's create a new `IsStaffAndReadOnly` permission that grants access to:
        
        -  Staff users, **and** only with reading http methods (`GET`, `HEAD` and `OPTIONS`).
        
        It will be implemented as the following:
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.permissions import IsReadOnly, IsStaffUser
        
        IsStaffAndReadOnly = IsStaffUser & IsReadOnly
        
        @api_view(permission_class=IsStaffAndReadOnly)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        2. **OR operator**:
        
        Let's create a new `IsAdminOrReadOnly` permission granting access to:
        
        -  Admin users with all HTTP methods
        -  Non-admin users with reading http methods (`GET`, `HEAD` and `OPTIONS`) only.
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.permissions import IsAdminUser, IsReadOnly
        
        IsAdminOrReadOnly = IsAdminUser | IsReadOnly
        
        @api_view(permission_class=IsAdminOrReadOnly)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        3. **XOR (_eXclusive OR_) operator**:
        
        For this example, let's implement a permission that grants access to:
        
        -  Users that are staff
        -  Users that are **not** admins
        
        Note that this permission could be implemented differently (and in a more
        correct and readable way). The use of XOR operator here is for demonstration purpose only.
        The correct implementation is shown below in "_5. Combining Operators_" example.
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.permissions import IsAdminUser, IsStaffUser
        
        IsStaffAndNotAdminUser = IsAdminUser ^ IsStaffUser
        
        @api_view(permission_class=IsStaffAndNotAdminUser)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        4. **NOT operator**:
        
        Let's consider a view that should be exposed to anonymous (_i.e._ not
        authenticated) users only. This view's permission will be defined as the following:
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.permissions import IsAuthenticated
        
        AnonymousUserOnly = ~IsAuthenticated
        
        @api_view(permission_class=AnonymousUserOnly)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        5. **Combining Operators**:
        
        Let's re-implement the `IsStaffAndNotAdminUser` used in the XOR example above, by using
        multiple operators. Then, we'll re-use it (`IsStaffAndNotAdminUser`) to implement a new
        `IsStaffAndNotAdminUserOrReadOnly`:
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.permissions import IsAdminUser, IsReadOnly, IsStaffUser
        
        IsStaffAndNotAdminUser = IsStaffUser & (~ IsAdminUser)
        
        IsStaffAndNotAdminUserOrReadOnly = IsStaffAndNotAdminUser | IsReadOnly
        # Or: IsStaffAndNotAdminUserOrReadOnly = (IsStaffUser & (~ IsAdminUser)) | IsReadOnly
        
        @api_view(permission_class=IsStaffAndNotAdminUserOrReadOnly)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        ### 2.4 Implement your own permission
        
        Even if combining standard permissions covers the most usual use-cases, you may have some unusual constrains that cannot be tackled using existing operators only.
        
        django-REST provides you a way to implement a custom permission that fits your needs.
        All you have to do is inherit from `django_rest.permissions.BasePermission`, then implement the `has_permission()` method.
        
        The `has_permission()` takes the `request` object, and the target view object as
        arguments, and should return a `bool` that represents if the access should be
        granted (`True`) or not (`False`).
        
        ```python
        def has_permission(self, request: HttpRequest, view:Union[Callable, View]) -> bool:
        ```
        
        Let's implement a custom permission that grants access to authenticated users
        having `gmail` address only. The "authenticated users" part will be taken care
        of using the existing `IsAuthenticated` permission.
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.permissions import BasePermission, IsAuthenticated
        
        
        class HasGmailAddress(BasePermission):
            def has_permission(self, request, view):
                user_email = request.user.email
                domain_name = user_email.split('@')[1]
                has_gmail_address = (domain_name == 'gmail.com')
                return has_gmail_address
        
        
        @api_view(permission_class=IsAuthenticated & HasGmailAddress)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        **Important Note:**
        
        While using operators, operands order **matters**.
        
        In the example above, in `HasGmailAddress` code, we assumed that the user is
        already authenticated, instead of manually checking it. That's because if the permission `IsAuthenticated` isn't satisfied,
        django-REST returns a `403 Forbidden access` before even evaluating `HasGmailAddress` permission.
        That's why in `HasGmailAddress` code, we assumed the user is authenticated.
        
        If we switched permissions order as the following:
        
        ```python
        @api_view(permission_class=HasGmailAddress & IsAuthenticated)
        def target_view(request, **kwargs):
            # [...]
        ```
        
        We should have added a condition in `HasGmailAddress` to verify if the user is
        authenticated (and therefore, `IsAuthenticated` permission will be useless).
        Otherwise, if an anonymous user requests the view, a `AttributeError: 'NoneType' object has no attribute 'email'` exception will be raised.
        
        ## 3. Deserializers
        
        ### 3.1. Introduction
        
        In django-REST, a deserializer validates input data (request payload and/or form data)
        based on custom fields ans constrains defined in the deserializer class,
        then "translates" data into the target format (Python primitive types), and finally
        executes some post-validation methods (if defined).
        In this chapter, we'll cover how to implement a simple deserializer, what are the
        fields available for use, how to nest deserializers for more complex validation and to post-clean your data,
        and finally, what the `AllPassDeserializer` is.
        
        ### 3.2. Implement a new Deserializer
        
        Defining a new `Deserializer` is quite simple. All you need to do is to inherit
        from `Deserializer` class:
        
        ```python
        from django_rest.deserializers import Deserializer
        
        
        class MyCustomDeserializer(Deserializer):
            pass
        ```
        
        But, a deserializer class has no purpose without its fields. Let's define
        a simple `Deserializer` with 2 fields: a positive integer primary key (`pk`),
        and a `username` (string).
        
        ```python
        from django_rest.deserializers import fields, Deserializer
        
        
        class MyCustomDeserializer(Deserializer):
            pk = fields.IntegerField(min_value=0)  # Implicit required
            username = fields.CharField(required=True)  # Explicit required
        ```
        
        That's how a `Deserializer` is defined. Now, if you want to use the deserializer
        outside the `@api_view`'s `deserializer_class` argument, you have two approaches to proceed:
        
        #### The first approach
        
        1. Instantiate your deserializer class, passing `data` argument to the
           constructor.
        2. Check your data validity, by calling `.is_valid()` method.
        3. Retrieve the validated data with `.data` (or errors with `.errors`)
           attribute.
        
        Here is a simple example:
        
        ```python
        from django_rest.deserializers import fields, Deserializer
        
        
        class MyCustomDeserializer(Deserializer):
            pk = fields.IntegerField(min_value=0)
            username = fields.CharField(required=True)
        
        
        valid_input = {'pk': '3', 'username': 'foobar'}
        invalid_input = {'pk': -3, 'username': 'foobar'}
        
        valid_instance = MyCustomDeserializer(data=valid_input)
        valid_instance.is_valid()  # True
        valid_instance.data  # {'pk': 3, 'username': 'foobar'}
        
        invalid_instance = MyCustomDeserializer(data=invalid_input)
        invalid_instance.is_valid()  # False
        invalid_instance.errors  # {"pk": ["Ensure this value is greater than or equal to 0."]}
        ```
        
        #### The second approach
        
        1. Instantiate the deserializer class without arguments.
        2. Call the `.clean()` method with the data to validate, it should return the
           valid data, or raise a `ValidationError` in case the input data is invalid.
        3. Put the clean call inside a `try/except` clause to catch the validation errors.
        
        Here is a simple example:
        
        ```python
        from django_rest.deserializers import fields, Deserializer, ValidationError
        
        
        class MyCustomDeserializer(Deserializer):
            pk = fields.IntegerField(min_value=0)
            username = fields.CharField(required=True)
        
        
        valid_input = {'pk': '3', 'username': 'foobar'}
        invalid_input = {'pk': -3, 'username': 'foobar'}
        
        deserializer_instance = MyCustomDeserializer()
        
        deserializer.clean(valid_input)  # {'pk': 3, 'username': 'foobar'}
        
        try:
            data = deserializer.clean(invalid_input)
        except ValidationError as errors:
            errors = dict(errors)  # errors = {"pk": ["Ensure this value is greater than or equal to 0."]}
            do_something_with_errors(errors)
        else:
            do_something(data)
        ```
        
        ### 3.3. Available Deserializer Fields
        
        django-REST deserializers use native django forms fields. **Depending on the
        django version you are using**, you may have access (or not) to some fields, and some of
        their attributes. More details on [django's official doc](https://docs.djangoproject.com/en/3.0/ref/forms/fields/).
        
        **Important Note:** You can enjoy **every** feature available in django forms fields, such as
        [custom validators](https://docs.djangoproject.com/en/3.0/ref/validators/) and
        [custom error messages](https://docs.djangoproject.com/en/3.0/ref/forms/fields/#error-messages)
        
        ### 3.4 Nested Deserializers
        
        django-REST offers support for nesting deserializers, in order to build more complex ones, in a flexible way and without losing readability.
        
        By nesting deserializers, errors are nested, and output data is a nested `dict`
        too. The following example illustrates how to nest deserializers:
        
        ```python
        from django.core.validators import MinValueValidator, MaxValueValidator
        from django_rest.deserializers import fields, Deserializer
        
        
        class RaceDriverDeserializer(Deserializer):
            first_name = fields.CharField(required=True)
            last_name = fields.CharField(required=True)
            birth_day = fields.DateField()
        
        
        class RaceCarDeserializer(Deserializer):
            brand = fields.CharField()
            model = fields.CharField()
            production_year = fields.IntegerField(
                required=False, validators=[MinValueValidator(1900), MaxValueValidator(2020)]
            )
            driver = RaceDriverDeserializer(required=True)
        
        
        valid_data = {
            "brand": "Mercedes",
            "model": "C11",
            "production_year": "1990",
            "driver": {
                "first_name": "Michael",
                "last_name": "Schumacher",
                "birth_day": "1969-01-03",
            },
        }
        
        deserializer = RaceCarDeserializer(data=valid_data)
        deserializer.is_valid()  # True
        deserializer.data
        """
        {
           'brand': 'Mercedes',
           'model': 'C11',
           'production_year': 1880,
           'driver': {
              'first_name': 'Michael',
              'last_name': 'Schumacher',
              'birth_day': datetime.date(1969, 1, 3),
           },
        }
        """
        
        invalid_data = {
            "brand": "Mercedes",
            "production_year": "1990",
            "driver": {"birth_day": "1969-01-03",},
        }
        
        deserializer = RaceCarDeserializer(data=invalid_data)
        deserializer.is_valid()  # False
        deserializer.errors
        """
        {
           "model": ["This field is required."],
           "driver": {
              "first_name": ["This field is required."],
              "last_name": ["This field is required."],
           }
        }
        """
        ```
        
        Note that a `Deserializer` is a field too, it can be used the exact same way you use a field (with the [same arguments](https://docs.djangoproject.com/fr/3.0/ref/forms/fields/#core-field-arguments)).
        
        ### 3.5. Post-clean methods
        
        A post-clean method is a deserializer's method, specific to a single `Field` and
        that will be called once the "standard" validation is done by the deserializer,
        allowing you to handle this validated value more easily, then return the value
        that will appear in the output data (that will be given to your view).
        By convention, their name follows the pattern: `post_clean_<FIELD NAME>`.
        
        For example, if your deserializer defines a `foo` field as a `CharField()`, and
        you want that your view receives a custom transformation of that `foo` field (for example, let's
        say: striping border spaces), the post-clean method for that field should be
        named `post_clean_foo()`:
        
        ```python
        from django_rest.deserializers import fields, Deserializer
        
        
        class FooDeserializer(Deserializer):
           foo = fields.CharField(required=True)
        
           def post_clean_foo(self, cleaned_value):
              return cleaned_value.strip()
        ```
        
        **Important Note:** The post-clean methods are called **only** if the field's standard
        validation succeeds. If a `ValidationError` occurs, the post-clean won't be
        done.
        
        ### 3.6 All-pass Deserializer
        
        The `AllPassDeserializer`, is a particular deserializer that allows all payloads to pass to the view, without
        any validation: No type-casts, no post-clean methods, and more importantly,
        never raises a `ValidationError` or returns a `400 BadRequest`.
        
        The `AllPassDeserializer` is the default deserializer used by `@api_view`
        decorator. (You probably won't need it unless you're dealing with a very unusual
        use-case)
        
        ## 4. Serializers
        
        ### 4.1. Introduction
        
        Serializers allow complex data such as querysets and model instances to be
        converted into native Python data-types, so that they could be easily rendered
        into JSON. Serializers do the opposite of Deserializers, and intervene at the
        "return" statement of your view.
        
        ### 4.2. Implement a new Serializer
        
        Similar to how we've implemented a `Deserializer`, in order to implement your
        own serializer, you have to inherit from `Serializer` class, then define the
        fields that you want to include into your serialized data (probably your view's response). Here is a simple
        example:
        
        ```python
        from django.http import JsonResponse
        
        from django_rest.decorators import api_view
        from django_rest.http import status
        from django_rest.serializers import fields, Serializer
        
        from .models import Subscription
        
        
        class SubscriptionSerializer(Serializer):
            id = fields.IntegerField(required=True)
            user_id = fields.IntegerField()
            started_at = fields.CharField(attr_name="created")
            invoices_urls = fields.ListField(fields.CharField(required=True))
        
        @api_view
        def subscription_details_view(request, url_params, **kwargs):
           subscription = Subscription.objects.get(id=url_params["subscription_pk"])
           return JsonResponse(
              SubscriptionSerializer(subscription, many=False).data,
              status=status.HTTP_200_OK,
           )
        
        ```
        
        `Serializer` class accepts 2 arguments:
        
        1. **instance**: The object (or iterable of objects) to be serialized.
        2. **many**: Boolean that tells the `Serializer` if the object is iterable or not. If
           `many=True`, the serialized data will be a `list` of serialized elements of
           the `instance` iterable. Its set by default to `False`.
        
        ### 4.3. Available Serializer Fields
        
        Serializers fields are very limited, because, remember that the data will be converted
        into native Python data-types (that are limited too). Besides primitive fields (`CharField`,
        `IntegerField`, `FloatField`, `BooleanField`), django-REST provides 3 additional
        fields to use within `Serializers`: `ListField`, `ConstantField` and `MethodField` (and the nested
        serializers). Let's dive into existing fields details.
        
        **Note** In order to simplify the wording in this section, "field" word refers
        to the serializer's field, and "attribute" word to an attribute of the object
        to serialize.
        
        #### 1. Primitive types
        
        The primitive types are serializer's fields that cast your data into Python's
        native data-types: `str`, `int`, `float` and `bool`. The `CharField`,
        `IntegerField`, `FloatField` and `BooleanField` accept the **same** arguments:
        
        ```python
        BooleanField(attr_name: str = None, label: str = None, call: bool = False, required: bool = True)
        CharField(attr_name: str = None, label: str = None, call: bool = False, required: bool = True)
        FloatField(attr_name: str = None, label: str = None, call: bool = False, required: bool = True)
        IntegerField(attr_name: str = None, label: str = None, call: bool = False, required: bool = True)
        ```
        
        1. **attr_name**: It refers to the object's attribute that should be binded to the
           current field. The default value is the field name. For example:
        
           ```python
           class Example:
               def __init__(self, foo, bar):
                  self.foo = foo
                  self.bar = bar
        
           class ExampleSerializer(Serializer):
               foo = fields.IntegerField()  # if `attr_name` is omitted, this field will lookup for your object's `.foo` attribute value
               whatever = fields.CharField(attr_name="bar")  # this field will store your object's `.bar` attribute's value
        
           ExampleSerializer(Example(foo=3, bar="test")).data  # {'foo': 3, 'whatever': 'test'}
           ```
        
        2. **label**: It's the name you want to give to your field in the serialized object.
           If omitted, it preserves the field's name. For the same `Example` class defined above, let's use `label` attribute:
        
           ```python
        
           class ExampleSerializer(Serializer):
               foo = fields.IntegerField(label='integerFoo')
               whatever = fields.CharField(attr_name='bar', label='textBar')
        
           ExampleSerializer(Example(foo=3, bar="test")).data  # {'integerFoo': 3, 'textBar': 'test'}
           ```
        
        3. **call**: If set to `True`, the serializer will try to execute (call)
           your attribute. This is useful when the attribute referred-to is a method. Here is a quick example:
        
           ```python
           class Example:
               def __init__(self, foo):
                   self.foo = foo
        
               def _get_text(self):
                   return "Hello"
        
               def bar(self):
                   return self._get_text() + " World!"
        
           class ExampleSerializer(Serializer):
               foo = fields.IntegerField()
               whatever = fields.CharField(attr_name="bar", call=True)  # 'bar' is callable
        
           ExampleSerializer(Example(foo=3)).data  # {'foo': 3, 'whatever': 'Hello World!'}
           ```
        
        4. **required**: When set to `True`, if the serializer fails to
           retrieve the attribute's value, or to convert it into the target type, a `SerializationError` will be raised.
           If the fields isn't required (`required=False`), in case the serializer fails to render the attribute's value,
           the field won't be added to the final result. If we take the same `Example` class from the previous examples:
        
           ```python
           class Example:
               def __init__(self, foo, bar):
                   self.foo = foo
                   self.bar = bar
        
           class ExampleSerializer(Serializer):
               foo = fields.IntegerField()
               bar = fields.IntegerField(required=True)  # Trying to fit a string into `IntegerField`
        
           ExampleSerializer(Example(foo=3, bar="test")).data  # raises a `SerializationError`
        
           class ExampleSerializer(Serializer):
               foo = fields.IntegerField()
               bar = fields.IntegerField(required=False)  # Trying to fit a string into `IntegerField`
        
           ExampleSerializer(Example(foo=3, bar="test")).data  # {'foo': 3}
           ```
        
        #### 2. MethodField
        
        There are some situations in which you'd need a calculated value (from one or multiple attributes),
        without polluting your view, nor your model with a new method.
        In that case, `MethodField` could be very useful. By defining a `MethodField`,
        you have to define a method in your `Serializer`, that receives your object as
        input, and has to return the value to be rendered
        
        **Note** `MethodField` is very similar to a deserializer's post-clean method,
        the only difference is that the post-clean receives the attribute's value,
        while the `MethodField` receives the whole object.
        
        Here is a simple example that illustrates how `MethodField` works:
        
        ```python
        from django_rest.serializers import fields, Serializer
        
        TAX_RATE = 20
        
        class PricingExample:
            def __init__(self, initial_price):
                self.initial_price = initial_price
        
        class PricingSerializer(Serializer):
            initial_price = fields.FloatField()
            final_price = fields.MethodField(method_name="calculate_final_price", required=True)
        
            def calculate_final_price(self, obj: PricingExample):
                tax_price = obj.initial_price * (TAX_RATE / 100)
                return obj.initial_price + tax_price
        
        PricingSerializer(PricingExample(initial_price=200)).data  # {'initial_price': 200.0, 'final_price': 240.0}
        ```
        
        `MethodField` accepts 3 arguments:
        
        ```python
        MethodField(label: str = None, required: bool = True, method_name: str = None)
        ```
        
        1. **label**: The same as [primitive fields](#1-primitive-types) `label`.
        2. **required**: The same as [primitive fields](#1-primitive-types) `label`.
        3. **method_name**: The name of the serializer's method that should be
           called. The default value is `get_<Serializer's field name>` (in the previous
           example, if `method_name` was not given, the method should have been renamed
           `get_final_price(self, obj)`)
        
        **Important note:** The `MethodField`'s method should return native Python
        data-types (`str`, `bool`, `int`, `float`, `None`) or (nested) `list`/`dict` of native types.
        
        #### 3. ConstantField
        
        `ConstantField` allows you to include constant data in your response, without
        having to include that constant in your model. In the previous example,
        `TAX_RATE` was a constant. In case we wanted to include it in the serialized
        data, we should had defined it as `PricingExample` class/instance attribute, or
        created a `MethodField` that returns a constant. Both solutions are quite
        "painful". Using `ConstantField`, the code will look like:
        
        ```python
        from django_rest.serializers import fields, Serializer
        
        TAX_RATE = 20
        
        class PricingExample:
            def __init__(self, initial_price):
                self.initial_price = initial_price
        
        class PricingSerializer(Serializer):
            initial_price = fields.FloatField()
            final_price = fields.MethodField(method_name="calculate_final_price", required=True)
            tax_rate = fields.ConstantField(constant=TAX_RATE)
        
            def calculate_final_price(self, obj: PricingExample):
                tax_price = obj.initial_price * (TAX_RATE / 100)
                return obj.initial_price + tax_price
        
        PricingSerializer(PricingExample(initial_price=200)).data  # {'initial_price': 200.0, 'final_price': 240.0, 'tax_rate': 20}
        ```
        
        `ConstantField` accepts 3 arguments:
        
        ```python
        ConstantField(label: str = None, required: bool = True, constant: Any = None)
        ```
        
        1. **label**: The same as [primitive fields](#1-primitive-types) `label`.
        2. **required**: The same as [primitive fields](#1-primitive-types) `label`.
        3. **constant**: The constant to be included in the serialized object. The
           constant **should be** primitive (i.e. `str`, `bool`, `int`, `float`, `None`
           or combinations -`list`/`dict`- of them), otherwise `SerializationError` will
           be raised (unless `required` is set to `False`, in that case, the field won't figure
           in the rendered object).
        
        #### 4. ListField
        
        `ListField` allows you to serialize iterables of primitives. Let's say your
        object's attribute is a list of integers. With a simple `IntegerField`, you
        won't be able to serialize that field. It could be achieved with `MethodField`,
        but it will be too much written code for a trivial thing. `ListField` does the
        same thing as the `many=True` for `Serializer` class, but the `many` argument
        isn't implemented for `IntegerField`, `BooleanField`, `FloatField` and
        `CharField` for performance purpose.
        The `ListField` accepts a single argument which is the field to be rendered as list.
        
        ```python
        ListField(Union[BooleanField, CharField, FloatField, IntegerField])
        ```
        
        Here is a simple example:
        
        ```python
        from django_rest.serializers import fields, Serializer
        
        
        class Path:
           def __init__(self):
              self.x_coordinates = [1.0, 1.2, 1.5, 1.8, 2.3, 8.6]
              self.y_coordinates = [19.0, 20.9, 30.1, 15.0, 22.3, 5.0]
        
        
        class PathSerializer(Serializer):
           xs = fields.ListField(
              fields.FloatField(label='path_xs', attr_name='x_coordinates', required=True)
           )
           ys = fields.ListField(
              fields.FloatField(label='path_ys', attr_name='y_coordinates', required=True)
           )
        
        PathSerializer(Path()).data  # {'path_xs': [1.0, 1.2, 1.5, 1.8, 2.3, 8.6], 'path_ys': [19.0, 20.9, 30.1, 15.0, 22.3, 5.0]}
        ```
        
        ### 4.4. Nested Serializers
        
        Similarly to `Deserializer`, `Serializer` sub-classes could be nested (i.e.
        using `Serializer` sub-class as a serializer's field). Here is a simple example
        that shows how to nest serializers:
        
        ```python
        from datetime import datetime
        
        from django_rest.serializers import fields, Serializer
        
        
        class Invoice:
            def __init__(self, id, date):
                self.id = id
                self.created_at = date
        
        
        class Subscription:
            def __init__(self):
                self.name = "foo bar subscription"
                self.invoices = [Invoice(id=i, date=datetime.now()) for i in range(10)]
        
        
        class InvoiceSerializer(Serializer):
            id = fields.IntegerField()
            created = fields.CharField(attr_name="created_at")
        
        
        class SubscriptionSerializer(Serializer):
            name = fields.CharField()
            invoices = InvoiceSerializer(many=True)
        
        
        SubscriptionSerializer(instance=Subscription()).data  # {'name': 'foo bar subscription', 'invoices': [{'id': 0, 'created': '2020-06-08 15:26:15.414524'}, ..., {'id': 9, '2020-06-08 15:26:15.93843'}]}
        ```
        
        ### 4.5. DictSerializer
        
        A `DictSerializer` is a sub-class of `Serializer` (it means that it's
        a particular serializer), that, instead of taking an object (class
        instance) as input, it takes a `dict`. The `DictSerializer` transforms a `dict`
        into another `dict`. It accepts the same fields as the classic serializer.
        Here is the previous example, rewritten using `DictSerializer` (to show the
        difference):
        
        ```python
        from datetime import datetime
        
        from django_rest.serializers import fields, DictSerializer
        
        
        subscription = {
            "name": "foo bar subscription",
            "invoices": [
                {"id": 0, "created_at": "2020-06-08 15:26:15.414524"},
                {"id": 9, "created_at": "2020-06-08 15:26:15.93843"},
            ],
        }
        
        
        class InvoiceSerializer(DictSerializer):
            id = fields.IntegerField()
            created = fields.CharField(attr_name="created_at")
        
        
        class SubscriptionSerializer(DictSerializer):
            name = fields.CharField()
            invoices = InvoiceSerializer(many=True)
        
        
        SubscriptionSerializer(instance=subscription).data  # {'name': 'foo bar subscription', 'invoices': [{'id': 0, 'created': '2020-06-08 15:26:15.414524'}, ..., {'id': 9, '2020-06-08 15:26:15.93843'}]}
        ```
        
        ## 5. Exceptions
        
        ### 5.1. `@api_view` exceptions catching
        
        The `@api_view` decorator catches exceptions for you in case you did not, and returns a JSON response with the correct status code.
        If the raised exception is a sub-class of `django_rest.http.exceptions.BaseAPIException`, a custom message and status code will be returned.
        If it's not the case, the returned JSON response will have `"An unknown server error occured."` as message, and `500` as status code.
        
        By raising one of the [existing API exceptions](#52-existing-api-exceptions) (or
        [defining your own](#53-define-your-own-api-exception)), the decorator will
        return the response with the correct message (and status code). This approach
        ensures that:
        
        1. Your responses are standardized in all your decorated views: always the same message and status code for the same situations.
        2. Your view's code is lighter (dropping all the useless `try/except` clauses).
        
        Here is a simple example of a view that receives `url_params`, calls a `find_results()` function, and returns a `404` in case there is no result:
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.http.exceptions import NotFound
        
        @api_view
        def user_custom_view(request, url_params, **kwargs):
            results = find_results(**url_params)
            if results is None or len(results) == 0:
                raise NotFound
        
            # In case the `find_results()` returned non-empty results:
            # [....]
        ```
        
        ### 5.2. Existing API Exceptions
        
        As seen in the previous chapter, django-REST provides you some custom exceptions that you can use (_i.e._ raise) so that your view returns an error response,
        without having to do it manually everytime. Here is the list of the available API exceptions , each with its returned object and status code:
        
        -  **BadRequest**:
        
           Response message: _"Bad request."_ - status code: `400`
        
        -  **NotAuthenticated**:
        
           Response message: _"Unauthorized operation. Maybe forgot the authentication step ?"_ - status code: `401`
        
        -  **PermissionDenied**:
        
           Response message: _"Forbidden operation. Make sure you have the right permissions."_ - status code: `403`
        
        -  **NotFound**:
        
           Response message: _"The requested resource is not found."_ - status code: `404`
        
        -  **MethodNotAllowed**:
        
           Response message: _"HTTP Method not allowed."_ - status code: `405`
        
        -  **UnsupportedMediaType**:
        
           Response message: _"Unsupported Media Type. Check your request's Content-Type."_ - status code: `415`
        
        *  **InternalServerError**:
        
           Response message: _"An unknown server error occured."_ - status code: `500`
        
        *  **ServiceUnavailable**:
        
           Response message: _"The requested service is unavailable."_ - status code: `502`
        
        ### 5.3. Define your own API Exception
        
        In order to define your own API Exception, all you have to do is inheriting from
        `django_rest.http.exceptions.BaseAPIException` (or one of its sub-classes), then override its `STATUS_CODE` and `RESPONSE_MESSAGE` attributes.
        
        Here is a simple example that shows how to define a [conflict](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) exception:
        
        ```python
        from django_rest.decorators import api_view
        from django_rest.http import exceptions, status
        
        class Conflict(exceptions.BaseAPIException):
            STATUS_CODE = status.HTTP_409_CONFLICT
            RESPONSE_MESSAGE = "Requests conflict."
        
        @api_view
        def my_conflicting_view(request, **kwargs):
            # [...]
            if some_condition_is_satisfied:
                raise Conflict  # returns JsonResponse({'error_msg': 'Requests conflict.'}, status=409)
            # [....]
        ```
        
        ## 6. HTTP
        
        django-REST provides some constants/enumerations that allow you to avoid using
        hard-coded values (`str` for HTTP methods, and `int` for status codes), and
        improve your code readability.
        
        ### 6.1. HTTP Status codes
        
        The HTTP status codes can be imported from `django_rest.http.status`:
        
        ```python
        from django_rest.http.status import HTTP_200_OK
        
        response_status = HTTP_200_OK
        
        # OR
        from django_rest.http import status
        
        response_status = status.HTTP_200_OK
        
        ```
        
        Here is the exhaustive list of http status constants provided by django-REST
        (more details about status codes [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)):
        
        -  `HTTP_100_CONTINUE`
        -  `HTTP_101_SWITCHING_PROTOCOLS`
        -  `HTTP_200_OK`
        -  `HTTP_201_CREATED`
        -  `HTTP_202_ACCEPTED`
        -  `HTTP_203_NON_AUTHORITATIVE_INFORMATION`
        -  `HTTP_204_NO_CONTENT`
        -  `HTTP_205_RESET_CONTENT`
        -  `HTTP_206_PARTIAL_CONTENT`
        -  `HTTP_207_MULTI_STATUS`
        -  `HTTP_208_ALREADY_REPORTED`
        -  `HTTP_226_IM_USED`
        -  `HTTP_300_MULTIPLE_CHOICES`
        -  `HTTP_301_MOVED_PERMANENTLY`
        -  `HTTP_302_FOUND`
        -  `HTTP_303_SEE_OTHER`
        -  `HTTP_304_NOT_MODIFIED`
        -  `HTTP_305_USE_PROXY`
        -  `HTTP_306_RESERVED`
        -  `HTTP_307_TEMPORARY_REDIRECT`
        -  `HTTP_308_PERMANENT_REDIRECT`
        -  `HTTP_400_BAD_REQUEST`
        -  `HTTP_401_UNAUTHORIZED`
        -  `HTTP_402_PAYMENT_REQUIRED`
        -  `HTTP_403_FORBIDDEN`
        -  `HTTP_404_NOT_FOUND`
        -  `HTTP_405_METHOD_NOT_ALLOWED`
        -  `HTTP_406_NOT_ACCEPTABLE`
        -  `HTTP_407_PROXY_AUTHENTICATION_REQUIRED`
        -  `HTTP_408_REQUEST_TIMEOUT`
        -  `HTTP_409_CONFLICT`
        -  `HTTP_410_GONE`
        -  `HTTP_411_LENGTH_REQUIRED`
        -  `HTTP_412_PRECONDITION_FAILED`
        -  `HTTP_413_REQUEST_ENTITY_TOO_LARGE`
        -  `HTTP_414_REQUEST_URI_TOO_LONG`
        -  `HTTP_415_UNSUPPORTED_MEDIA_TYPE`
        -  `HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE`
        -  `HTTP_417_EXPECTATION_FAILED`
        -  `HTTP_418_IM_A_TEAPOT`
        -  `HTTP_422_UNPROCESSABLE_ENTITY`
        -  `HTTP_423_LOCKED`
        -  `HTTP_424_FAILED_DEPENDENCY`
        -  `HTTP_426_UPGRADE_REQUIRED`
        -  `HTTP_428_PRECONDITION_REQUIRED`
        -  `HTTP_429_TOO_MANY_REQUESTS`
        -  `HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE`
        -  `HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS`
        -  `HTTP_500_INTERNAL_SERVER_ERROR`
        -  `HTTP_501_NOT_IMPLEMENTED`
        -  `HTTP_502_BAD_GATEWAY`
        -  `HTTP_503_SERVICE_UNAVAILABLE`
        -  `HTTP_504_GATEWAY_TIMEOUT`
        -  `HTTP_505_HTTP_VERSION_NOT_SUPPORTED`
        -  `HTTP_506_VARIANT_ALSO_NEGOTIATES`
        -  `HTTP_507_INSUFFICIENT_STORAGE`
        -  `HTTP_508_LOOP_DETECTED`
        -  `HTTP_509_BANDWIDTH_LIMIT_EXCEEDED`
        -  `HTTP_510_NOT_EXTENDED`
        -  `HTTP_511_NETWORK_AUTHENTICATION_REQUIRED`
        
        Besides, you also have (in the same module `django_rest.http.status`) 5 functions that you can use to verify
        a status code category easily:
        
        -  `is_informational(code: int) -> bool`
        -  `is_success(code: int) -> bool`
        -  `is_redirect(code: int) -> bool`
        -  `is_client_error(code: int) -> bool`
        -  `is_server_error(code: int) -> bool`
        
        ### 6.2. HTTP Methods
        
        All the following HTTP method's related constants can be found in
        `django_rest.http.methods`:
        
        **String constants:**
        
        -  `HEAD`
        -  `GET`
        -  `POST`
        -  `PUT`
        -  `PATCH`
        -  `DELETE`
        -  `OPTIONS`
        -  `TRACE`
        -  `CONNECT`
        
        **Tuple constants:**
        
        -  `SAFE_METHODS` = (`GET`, `HEAD`, `OPTIONS`)
        -  `SUPPORTING_PAYLOAD_METHODS` = (`POST`, `PUT`, `PATCH`)
        -  `ALL_METHODS` = (`HEAD`, `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `TRACE`, `CONNECT`)
        
        <p align="center">&mdash; Made with :hearts: &mdash;</p>
        
Keywords: django REST library fast light serializer
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 1.11
Classifier: Framework :: Django :: 1.4
Classifier: Framework :: Django :: 1.5
Classifier: Framework :: Django :: 1.6
Classifier: Framework :: Django :: 1.7
Classifier: Framework :: Django :: 1.8
Classifier: Framework :: Django :: 1.9
Classifier: Framework :: Django :: 2.0
Classifier: Framework :: Django :: 2.1
Classifier: Framework :: Django :: 2.2
Classifier: Framework :: Django :: 3.0
Classifier: Framework :: Django :: 3.1
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Internet
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4
Description-Content-Type: text/markdown
