Django Rest Framework ********************* .. highlight:: python .. note:: To include the *Django REST framework JSON:API* (``djangorestframework-jsonapi``). https://django-rest-framework-json-api.readthedocs.io/ Standards ========= Put the API views (``ViewSet`` etc) in an ``api.py`` file e.g. ``contact/api.py``. URLs to include ``namespace="api"`` e.g:: url( regex=r"^api/0.1/", view=include((router.urls, "api"), namespace="api") ), Usage ===== Requirements: .. code-block:: text # requirements/base.txt djangorestframework .. tip:: Find the version number in :doc:`dev-requirements` .. tip:: For the JSON API, see `JSON API`_ In ``example/base.py`` for an app, ``settings/base.py`` for a project: .. code-block:: python THIRD_PARTY_APPS = ( 'rest_framework', # http://www.django-rest-framework.org/api-guide/authentication#tokenauthentication 'rest_framework.authtoken', # http://www.django-rest-framework.org/api-guide/authentication#tokenauthentication REST_FRAMEWORK = { 'COERCE_DECIMAL_TO_STRING': True, # not sure if this is required or not # 'DATETIME_FORMAT': '%Y%m%dT%H%M%SZ', 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAdminUser', ), 'TEST_REQUEST_DEFAULT_FORMAT': 'json', } Add the following to ``urls.py`` (perhaps in your ``project`` folder):: from rest_framework.authtoken import views url(regex=r'^token/$', view=views.obtain_auth_token, name='api.token.auth', ), .. note:: You can change the ``regex`` to another URL if you want... Create a token for each of the users who will use the API:: from rest_framework.authtoken.models import Token Token.objects.create(user=...) .. tip:: To auto-generate a token for every user, check out TokenAuthentication_ JSON API -------- Standard settings for the JSON API:: REST_FRAMEWORK = { "PAGE_SIZE": 20, "EXCEPTION_HANDLER": "rest_framework_json_api.exceptions.exception_handler", "DEFAULT_PAGINATION_CLASS": "rest_framework_json_api.pagination.JsonApiPageNumberPagination", "DEFAULT_PARSER_CLASSES": ( "rest_framework_json_api.parsers.JSONParser", "rest_framework.parsers.FormParser", "rest_framework.parsers.MultiPartParser", ), "DEFAULT_RENDERER_CLASSES": ( "rest_framework_json_api.renderers.JSONRenderer", "rest_framework_json_api.renderers.BrowsableAPIRenderer", ), "DEFAULT_METADATA_CLASS": "rest_framework_json_api.metadata.JSONAPIMetadata", "DEFAULT_SCHEMA_CLASS": "rest_framework_json_api.schemas.openapi.AutoSchema", "DEFAULT_FILTER_BACKENDS": ( "rest_framework_json_api.filters.QueryParameterValidationFilter", "rest_framework_json_api.filters.OrderingFilter", "rest_framework_json_api.django_filters.DjangoFilterBackend", "rest_framework.filters.SearchFilter", ), "SEARCH_PARAM": "filter[search]", "TEST_REQUEST_RENDERER_CLASSES": ( "rest_framework_json_api.renderers.JSONRenderer", ), "TEST_REQUEST_DEFAULT_FORMAT": "vnd.api+json", } JSON_API_FORMAT_FIELD_NAMES = "dasherize" Pagination ---------- To switch off pagination, add ``pagination_class = None`` to the viewset. MethodNotAllowed ================ To prevent use of a method, add the following to your ``viewsets.ModelViewSet``:: from rest_framework.exceptions import MethodNotAllowed def create(self, request, *args, **kwargs): raise MethodNotAllowed( "POST", detail="Method 'POST' not allowed" ) def perform_destroy(self, instance): raise MethodNotAllowed( "GET", detail="Method 'GET' not allowed" ) To test:: from http import HTTPStatus assert HTTPStatus.METHOD_NOT_ALLOWED == response.status_code error_detail = response.data["detail"] # or # error_detail = response.data["errors"] assert "Method 'POST' not allowed" == str(error_detail) # or # assert "Method "GET" not allowed" == str(error_detail) Testing ======= JSON API - File Upload ---------------------- Add the ``MultiPartRenderer`` to settings:: # settings/base.py REST_FRAMEWORK = { "TEST_REQUEST_RENDERER_CLASSES": ( "rest_framework_json_api.renderers.JSONRenderer", "rest_framework.renderers.MultiPartRenderer", ), } Add ``multipart`` to the ``post`` in the test code:: file_name = Path( settings.BASE_DIR, settings.MEDIA_ROOT, "data", "1-2-3.doc" ) with open(file_name, "rb") as f: data = {"file": f} response = api_client_auth(user).post(url, data, format="multipart") .. tip:: The ``MultiPartRenderer`` is used for ``post`` requests which use the ``multipart`` parameter . Sample ------ Test code using the ``api_client`` fixture from our :doc:`app-api` app:: import pytest from django.urls import reverse from http import HTTPStatus from api.tests.fixture import api_client from login.tests.factories import UserFactory @pytest.mark.django_db def test_something(api_client): response = api_client.get(reverse('docrecord.api.document')) assert HTTPStatus.OK == response.status_code, response.data URL --- For this router:: router.register(r"tasks", ExampleWorkTaskViewSet, basename="task") We can test as follows:: # create returns 'HTTPStatus.CREATED' .post(reverse("api:task-list")) # get (retrieve) .get(reverse("api:task-detail", args=[str(uuid.uuid4())])) # update for JSON - returns 'HTTPStatus.OK' .patch(reverse("api:task-detail", args=[str(uuid.uuid4())]), data) # update for REST API .put(reverse("api:task-detail", args=[str(uuid.uuid4())]), data) # delete - returns 'HTTPStatus.NO_CONTENT' .delete(reverse("api:task-detail", args=[str(uuid.uuid4())])) # list .get(reverse("api:task-list")) .. _TokenAuthentication: http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication