Django Rest Framework
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:
# 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
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.
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}
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 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"))