Django Rest Framework
Note
To include the Django REST framework JSON:API
(djangorestframework-jsonapi).
https://django-rest-framework-json-api.readthedocs.io/
Tip
To test the JSON API without authentication, see REST Framework - JSON API
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:
# requirements/base.txt
djangorestframework
Tip
Find the version number in Requirements
Tip
For the JSON API, see JSON API
In example/base.py for an app, settings/base.py for a project:
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
Tip
To test the JSON API without authentication, see REST Framework - JSON API
Standard settings for the JSON API:
# https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
REST_FRAMEWORK = {"EXCEPTION_HANDLER": "api.models.custom_exception_handler"}
# http://www.django-rest-framework.org/api-guide/authentication#tokenauthentication
REST_FRAMEWORK = {
"PAGE_SIZE": 10,
"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",
# If you're performance testing, you will want to use the browseable API
# without forms, as the forms can generate their own queries.
# If performance testing, enable:
# 'example.utils.BrowsableAPIRendererWithoutForms',
# Otherwise, to play around with the browseable API, enable:
"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",
"rest_framework.renderers.MultiPartRenderer",
),
"TEST_REQUEST_DEFAULT_FORMAT": "vnd.api+json",
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
),
}
Tip
We removed JSON_API_FORMAT_FIELD_NAMES = dasherize setting on
8th June 2025 because it keeps the keys consistent when using Vue,
https://chat.kbsoftware.co.uk/kb/pl/z1adahymwjg4fndte56cnju4he
And urls.py should have trailing_slash=False for the DefaultRouter:
router = routers.DefaultRouter(trailing_slash=False)
API Browser
If Django
runserveris0.0.0.0:8000(just the usual)Log in to the app using OIDC
Browse to http://localhost:8000 (not http://127.0.0.1) and it will work!
Ordering / Sort
Add ordering_fields (and ordering for the default order):
from api.api_utils import SoftDeleteViewSet
class SkillViewSet(SoftDeleteViewSet):
ordering = ['name']
ordering_fields = ["name", "location__name"]
Sort using the sort parameter e.g:
parameters = {"filter[parent]": parent.pk, "sort": sort}
url = url_with_querystring(reverse("api:skill-list"), **parameters)
Our standard settings for the JSON API, include the OrderingFilter
The OrderingFilter documentation for the REST Framework uses the
orderingparameter, but the JSON API seems to usesort(which is good)!
Pagination
To switch off pagination, add pagination_class = None to the viewset.
resource_name
To change the resource_name if the model name does not match what you
want to return e.g. we have a Category model which can return a
Location or Department:
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Category
resource_name = "Location"
fields = "name"
Tip
Note the resource_name on this serializer.
To use the LocationSerializer, add included_serializers to your
serializer (where location matches one of your fields):
included_serializers = {"location": LocationSerializer}
Tip
The resource_name may also be set on the model using
JSONAPIMeta
MethodNotAllowed
To prevent use of a method, add the following to your ViewSet:
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("DELETE", detail="Method 'DELETE' not allowed")
def update(self, request, *args, **kwargs):
raise MethodNotAllowed("PATCH", detail="Method 'PATCH' not allowed")
To test:
from http import HTTPStatus
assert HTTPStatus.METHOD_NOT_ALLOWED == response.status_code
data = response.data
assert 1 == len(data)
item = response.data[0]
assert "detail" in item
assert "Method 'PATCH' not allowed" == str(item["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 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"))