Usage
The DJA package implements a custom renderer, parser, exception handler, query filter backends, andpagination. To get started enable the pieces insettings.py
that you want to use.
Many features of theJSON:API format standard have been implemented usingMixin classes inserializers.py
.The easiest way to make use of those features is to import ModelSerializer variantsfromrest_framework_json_api
instead of the usualrest_framework
Configuration
We suggest that you copy the settings block below and modify it if necessary.
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_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'}
Pagination
DJA pagination is based onDRF pagination.
When pagination is enabled, the renderer will return ameta
object withrecord count and alinks
object with the next, previous, first, and last links.
Optional query parameters can also be provided to customize the page size or offset limit.
Configuring the Pagination Style
Pagination style can be set on a particular viewset with thepagination_class
attribute or by default for all viewsetsby settingREST_FRAMEWORK['DEFAULT_PAGINATION_CLASS']
and by settingREST_FRAMEWORK['PAGE_SIZE']
.
You can configure fixed values for the page size or limit – or allow the client to choose the size or limitvia query parameters.
Two pagination classes are available:
JsonApiPageNumberPagination
breaks a response up into pages that start at a given page number with a given size(number of items per page). It can be configured with the following attributes:page_query_param
(defaultpage[number]
)page_size_query_param
(defaultpage[size]
) Set this toNone
if you don’t want to allow the clientto specify the size.page_size
(defaultREST_FRAMEWORK['PAGE_SIZE']
) default number of items per page unless overridden bypage_size_query_param
.max_page_size
(default100
) enforces an upper bound on thepage_size_query_param
.Set it toNone
if you don’t want to enforce an upper bound.
JsonApiLimitOffsetPagination
breaks a response up into pages that start from an item’s offset in the viewset fora given number of items (the limit).It can be configured with the following attributes:offset_query_param
(defaultpage[offset]
).limit_query_param
(defaultpage[limit]
).default_limit
(defaultREST_FRAMEWORK['PAGE_SIZE']
) is the default number of items per page unlessoverridden bylimit_query_param
.max_limit
(default100
) enforces an upper bound on the limit.Set it toNone
if you don’t want to enforce an upper bound.
Examples
These examples show how to configure the parameters to use non-standard names and different limits:
fromrest_framework_json_api.paginationimportJsonApiPageNumberPagination,JsonApiLimitOffsetPaginationclassMyPagePagination(JsonApiPageNumberPagination):page_query_param='page_number'page_size_query_param='page_length'page_size=3max_page_size=1000classMyLimitPagination(JsonApiLimitOffsetPagination):offset_query_param='offset'limit_query_param='limit'default_limit=3max_limit=None
Filter Backends
Following are descriptions of JSON:API-specific filter backends and documentation on suggested usagefor a standard DRF keyword-search filter backend that makes it consistent with JSON:API.
QueryParameterValidationFilter
QueryParameterValidationFilter
validates query parameters to be one of the defined JSON:API query parameters(sort, include, filter, fields, page) and returns a400BadRequest
if a non-matching query parameteris used. This can help the client identify misspelled query parameters, for example.
If you want to change the list of valid query parameters, override the.query_regex
attribute:
# compiled regex that matches the allowed https://jsonapi.org/format/#query-parameters# `sort` and `include` stand alone; `filter`, `fields`, and `page` have []'squery_regex=re.compile(r"^(sort|include)$|^(?P<type>filter|fields|page)(\[[\w\.\-]+\])?$")
For example:
importrefromrest_framework_json_api.filtersimportQueryParameterValidationFilterclassMyQPValidator(QueryParameterValidationFilter):query_regex=re.compile(r"^(sort|include|page|page_size)$|^(?P<type>filter|fields|page)(\[[\w\.\-]+\])?$")
If you don’t care if non-JSON:API query parameters are allowed (and potentially silently ignored),simply don’t use this filter backend.
OrderingFilter
OrderingFilter
implements theJSON:APIsort
and usesDRF’sordering filter.
Per the JSON:API specification, “If the server does not support sorting as specified in the query parametersort
,itMUST return400BadRequest
.” For example, for?sort=abc,foo,def
wherefoo
is a validfield name and the other two are not valid:
{"errors":[{"detail":"invalid sort parameters: abc,def","source":{"pointer":"/data"},"status":"400"}]}
If you want to silently ignore bad sort fields, just userest_framework.filters.OrderingFilter
and setordering_param
tosort
.
DjangoFilterBackend
DjangoFilterBackend
implements a Django ORM-styleJSON:APIfilter
using thedjango-filter package.
This filter is not part of the JSON:API standard per-se, other than the requirementto use thefilter
keyword: It is an optional implementation of a style offiltering in which each filter is an ORM expression as implemented byDjangoFilterBackend
and seems to be in alignment with an interpretation of theJSON:APIrecommendations, including relationshipchaining.
Filters can be:
A resource field equality test:
?filter[qty]=123
Apply otherfield lookup operators:
?filter[name.icontains]=bar
or?filter[name.isnull]=true
Membership in a list of values:
?filter[name.in]=abc,123,zzz(namein['abc','123','zzz'])
Filters can be combined for intersection (AND):
?filter[qty]=123&filter[name.in]=abc,123,zzz&filter[...]
or?filter[authors.id]=1&filter[authors.id]=2
A related resource path can be used:
?filter[inventory.item.partNum]=123456
(whereinventory.item
is the relationship path)
The filter returns a400BadRequest
error for invalid filter query parameters as in this exampleforGEThttp://127.0.0.1:8000/nopage-entries?filter[bad]=1
:
{"errors":[{"detail":"invalid filter[bad]","source":{"pointer":"/data"},"status":"400"}]}
As this feature depends ondjango-filter
you need to run
pipinstalldjangorestframework-jsonapi['django-filter']
SearchFilter
To comply with JSON:API query parameter naming standards, DRF’sSearchFilter shouldbe configured to use afilter[_something_]
query parameter. This can be done by default by adding theSearchFilter toREST_FRAMEWORK['DEFAULT_FILTER_BACKENDS']
and settingREST_FRAMEWORK['SEARCH_PARAM']
oradding the.search_param
attribute to a custom class derived fromSearchFilter
. If you do this and alsouseDjangoFilterBackend
, make sure you set the same values for both classes.
Configuring Filter Backends
You can configure the filter backends either by setting theREST_FRAMEWORK['DEFAULT_FILTER_BACKENDS']
as shownin theexample settings or individually add them as.filter_backends
View attributes:
fromrest_framework_json_apiimportfiltersfromrest_framework_json_apiimportdjango_filtersfromrest_frameworkimportSearchFilterfrommodelsimportMyModelclassMyViewset(ModelViewSet):queryset=MyModel.objects.all()serializer_class=MyModelSerializerfilter_backends=(filters.QueryParameterValidationFilter,filters.OrderingFilter,django_filters.DjangoFilterBackend,SearchFilter)filterset_fields={'id':('exact','lt','gt','gte','lte','in'),'descriptuon':('icontains','iexact','contains'),'tagline':('icontains','iexact','contains'),}search_fields=('id','description','tagline',)
Error objects / Exception handling
For theexception_handler
class, if the optionalJSON_API_UNIFORM_EXCEPTIONS
is set to True,all exceptions will respond with the JSON:APIerror format.
WhenJSON_API_UNIFORM_EXCEPTIONS
is False (the default), non-JSON:API views will respondwith the normal DRF error format.
In case you need a custom error object you can simply raise anrest_framework.serializers.ValidationError
like the following:
raiseserializers.ValidationError({"id":"your-id","detail":"your detail message","source":{"pointer":"/data/attributes/your-pointer",}})
Performance Testing
If you are trying to see if your viewsets are configured properly to optimize performance,it is preferable to useexample.utils.BrowsableAPIRendererWithoutForms
instead of the defaultBrowsableAPIRenderer
to remove queries introduced by the forms themselves.
Serializers
It is recommended to import the base serializer classes from this packagerather than from vanilla DRF. For example,
fromrest_framework_json_apiimportserializersclassMyModelSerializer(serializers.ModelSerializer):# ...
Overwriting the resource object’s id
Per default the primary key propertypk
on the instance is used as the resource identifier.
It is possible to overwrite the resource id by defining anid
field on the serializer like:
classUserSerializer(serializers.ModelSerializer):id=serializers.CharField(source='email')name=serializers.CharField()classMeta:model=User
This also works on generic serializers.
In case you also use a model as a resource related field make sure to overwriteget_resource_id
by creating a customResourceRelatedField
class:
classUserResourceRelatedField(ResourceRelatedField):defget_resource_id(self,value):returnvalue.emailclassGroupSerializer(serializers.ModelSerializer):user=UserResourceRelatedField(queryset=User.objects)name=serializers.CharField()classMeta:model=Group
Setting resource identifier object type
You may manually set resource identifier object type by usingresource_name
property on views, serializers, ormodels. In case of setting theresource_name
property for models you must include the property inside aJSONAPIMeta
class on the model. It is usually automatically set for you as the plural of the view or model name excepton resources that do not subclassrest_framework.viewsets.ModelViewSet
:
Example -resource_name
on View:
classMe(generics.GenericAPIView):""" Current user's identity endpoint. GET /me """resource_name='users'serializer_class=identity_serializers.IdentitySerializerallowed_methods=['GET']permission_classes=(permissions.IsAuthenticated,)
Example -resource_name
on Model:
classMe(models.Model):""" A simple model """name=models.CharField(max_length=100)classJSONAPIMeta:resource_name="users"
If you set theresource_name
on a combination of model, serializer, or viewin the same hierarchy, the name will be resolved as following: view >serializer > model. (Ex: A viewresource_name
will always override aresource_name
specified on a serializer or model). Setting theresource_name
on the view should be used sparingly as serializers and models are shared betweenmultiple endpoints. Setting theresource_name
on views may result in a differenttype
being set depending on which endpoint the resource is fetched from.
Build JSON:API view output manually
If in a view you want to build the output manually, you can setresource_name
toFalse
.
Example:
classUser(ModelViewSet):resource_name=Falsequeryset=User.objects.all()serializer_class=UserSerializerdefretrieve(self,request,*args,**kwargs):instance=self.get_object()data=[{"id":1,"type":"users","attributes":{"fullName":"Test User"}}])
Inflecting object and relation keys
This package includes the ability (off by default) to automatically convertJSON:API field names of requests and responses from the Django REST framework’s preferred underscore to a format of your choice. To hook this up include the following setting in yourproject settings:
JSON_API_FORMAT_FIELD_NAMES='dasherize'
Possible values:
dasherize
camelize (first letter is lowercase)
capitalize (camelize but with first letter uppercase)
underscore
Note: due to the way the inflector worksaddress_1
can camelize toaddress1
on output but it cannot convertaddress1
back toaddress_1
on POST or PATCH. Keepthis in mind when naming fields with numbers in them.
Example - Without format conversion:
{"data":[{"type":"identities","id":"3","attributes":{"username":"john","first_name":"John","last_name":"Coltrane","full_name":"John Coltrane"},}],"meta":{"pagination":{"count":20}}}
Example - With format conversion set todasherize
:
{"data":[{"type":"identities","id":"3","attributes":{"username":"john","first-name":"John","last-name":"Coltrane","full-name":"John Coltrane"},}],"meta":{"pagination":{"count":20}}}
Types
A similar option toJSON_API_FORMAT_FIELD_NAMES
can be set for the types:
JSON_API_FORMAT_TYPES='dasherize'
Example without format conversion:
{"data":[{"type":"blog_identity","id":"3","attributes":{...},"relationships":{"home_town":{"data":[{"type":"home_town","id":3}]}}}]}
When set to dasherize:
{"data":[{"type":"blog-identity","id":"3","attributes":{...},"relationships":{"home_town":{"data":[{"type":"home-town","id":3}]}}}]}
It is also possible to pluralize the types like so:
JSON_API_PLURALIZE_TYPES=True
Example without pluralization:
{"data":[{"type":"identity","id":"3","attributes":{...},"relationships":{"home_towns":{"data":[{"type":"home_town","id":"3"}]}}}]}
When set to pluralize:
{"data":[{"type":"identities","id":"3","attributes":{...},"relationships":{"home_towns":{"data":[{"type":"home_towns","id":"3"}]}}}]}
Related URL segments
Serializer properties in relationship and related resource URLs may be infected using theJSON_API_FORMAT_RELATED_LINKS
setting.
JSON_API_FORMAT_RELATED_LINKS='dasherize'
For example, with a serializer propertycreated_by
and with'dasherize'
formatting:
{"data":{"type":"comments","id":"1","attributes":{"text":"Comments are fun!"},"links":{"self":"/comments/1"},"relationships":{"created_by":{"links":{"self":"/comments/1/relationships/created-by","related":"/comments/1/created-by"}}}},"links":{"self":"/comments/1"}}
The relationship name is formatted by theJSON_API_FORMAT_FIELD_NAMES
setting, but the URL segments are formatted by theJSON_API_FORMAT_RELATED_LINKS
setting.
Related fields
ResourceRelatedField
Because of the additional structure needed to represent relationships in JSON:API, this package provides theResourceRelatedField
for serializers, whichworks similarly toPrimaryKeyRelatedField
. By default,rest_framework_json_api.serializers.ModelSerializer
will use this forrelated fields automatically. It can be instantiated explicitly as in thefollowing example:
fromrest_framework_json_apiimportserializersfromrest_framework_json_api.relationsimportResourceRelatedFieldfrommyapp.modelsimportOrder,LineItem,CustomerclassOrderSerializer(serializers.ModelSerializer):classMeta:model=Orderline_items=ResourceRelatedField(queryset=LineItem.objects,many=True# necessary for M2M fields & reverse FK fields)customer=ResourceRelatedField(queryset=Customer.objects# queryset argument is required)# except when read_only=True
In theJSON:API spec,relationship objects contain links to related objects. To make this workon a serializer we need to tell theResourceRelatedField
about thecorresponding view. Use theHyperlinkedModelSerializer
and instantiatetheResourceRelatedField
with the relevant keyword arguments:
fromrest_framework_json_apiimportserializersfromrest_framework_json_api.relationsimportResourceRelatedFieldfrommyapp.modelsimportOrder,LineItem,CustomerclassOrderSerializer(serializers.HyperlinkedModelSerializer):classMeta:model=Orderline_items=ResourceRelatedField(queryset=LineItem.objects,many=True,related_link_view_name='order-lineitems-list',related_link_url_kwarg='order_pk',self_link_view_name='order_relationships')customer=ResourceRelatedField(queryset=Customer.objects,related_link_view_name='order-customer-detail',related_link_url_kwarg='order_pk',self_link_view_name='order-relationships')
related_link_view_name
is the name of the route for the relatedview.related_link_url_kwarg
is the keyword argument that will be passedto the view that identifies the ‘parent’ object, so that the resultscan be filtered to show only those objects related to the ‘parent’.self_link_view_name
is the name of the route for theRelationshipView
(see below).
In this example,reverse('order-lineitems-list',kwargs={'order_pk':3}
should resolve to something like/orders/3/lineitems
, and that routeshould instantiate a view or viewset forLineItem
objects that acceptsa keyword argumentorder_pk
. Thedrf-nested-routers packageis useful for defining such nested routes in your urlconf.
The corresponding viewset for theline-items-list
route in the above examplemight look like the following. Note that in the typical use case this would bethe same viewset used for the/lineitems
endpoints; when accessed throughthe nested route/orders/<order_pk>/lineitems
the queryset is filtered usingtheorder_pk
keyword argument to include only the lineitems related to thespecified order.
fromrest_frameworkimportviewsetsfrommyapp.modelsimportLineItemfrommyapp.serializersimportLineItemSerializerclassLineItemViewSet(viewsets.ModelViewSet):queryset=LineItem.objectsserializer_class=LineItemSerializerdefget_queryset(self):queryset=super().get_queryset()# if this viewset is accessed via the 'order-lineitems-list' route,# it wll have been passed the `order_pk` kwarg and the queryset# needs to be filtered accordingly; if it was accessed via the# unnested '/lineitems' route, the queryset should include all LineItemsorder_pk=self.kwargs.get('order_pk')iforder_pkisnotNone:queryset=queryset.filter(order__pk=order_pk)returnqueryset
HyperlinkedRelatedField
relations.HyperlinkedRelatedField
has same functionality asResourceRelatedField
but doesnot renderdata
. Use this in case you only need links of relationships and want to lower payloadand increase performance.
SerializerMethodResourceRelatedField
relations.SerializerMethodResourceRelatedField
combines behaviour of DRFSerializerMethodField
andResourceRelatedField
, so it acceptsmethod_name
together withmodel
and links-related arguments.data
is rendered inResourceRelatedField
manner.
fromrest_framework_json_apiimportserializersfromrest_framework_json_api.relationsimportSerializerMethodResourceRelatedFieldfrommyapp.modelsimportOrder,LineItemclassOrderSerializer(serializers.ModelSerializer):classMeta:model=Orderline_items=SerializerMethodResourceRelatedField(model=LineItem,many=True,method_name='get_big_line_items')small_line_items=SerializerMethodResourceRelatedField(model=LineItem,many=True,# default to method_name='get_small_line_items')defget_big_line_items(self,instance):returnLineItem.objects.filter(order=instance).filter(amount__gt=1000)defget_small_line_items(self,instance):returnLineItem.objects.filter(order=instance).filter(amount__lte=1000)
or usingrelated_link_*
withHyperlinkedModelSerializer
classOrderSerializer(serializers.HyperlinkedModelSerializer):classMeta:model=Orderline_items=SerializerMethodResourceRelatedField(model=LineItem,many=True,method_name='get_big_line_items',related_link_view_name='order-lineitems-list',related_link_url_kwarg='order_pk',)defget_big_line_items(self,instance):returnLineItem.objects.filter(order=instance).filter(amount__gt=1000)
Related urls
There is a nice way to handle “related” urls like/orders/3/lineitems/
or/orders/3/customer/
.All you need is just add tourls.py
:
url(r'^orders/(?P<pk>[^/.]+)/$',OrderViewSet.as_view({'get':'retrieve'}),name='order-detail'),url(r'^orders/(?P<pk>[^/.]+)/(?P<related_field>[-\w]+)/$',OrderViewSet.as_view({'get':'retrieve_related'}),name='order-related'),
Make sure that RelatedField declaration hasrelated_link_url_kwarg='pk'
or simply skipped (will be set by default):
line_items=ResourceRelatedField(queryset=LineItem.objects,many=True,related_link_view_name='order-related',related_link_url_kwarg='pk',self_link_view_name='order-relationships')customer=ResourceRelatedField(queryset=Customer.objects,related_link_view_name='order-related',self_link_view_name='order-relationships')
And, the most important part - declare serializer for each related entity:
classOrderSerializer(serializers.HyperlinkedModelSerializer):...related_serializers={'customer':'example.serializers.CustomerSerializer','line_items':'example.serializers.LineItemSerializer'}
Or, if you already haveincluded_serializers
declared and yourrelated_serializers
look the same, just skip it:
classOrderSerializer(serializers.HyperlinkedModelSerializer):...included_serializers={'customer':'example.serializers.CustomerSerializer','line_items':'example.serializers.LineItemSerializer'}
RelationshipView
rest_framework_json_api.views.RelationshipView
is used to buildrelationship views (see theJSON:API spec).Theself
link on a relationship object should point to the correspondingrelationship view.
The relationship view is fairly simple because it only serializesResource Identifier Objectsrather than full resource objects. In most cases the following is sufficient:
fromrest_framework_json_api.viewsimportRelationshipViewfrommyapp.modelsimportOrderclassOrderRelationshipView(RelationshipView):queryset=Order.objects
The urlconf would need to contain a route like the following:
url(regex=r'^orders/(?P<pk>[^/.]+)/relationships/(?P<related_field>[-/w]+)$',view=OrderRelationshipView.as_view(),name='order-relationships')
Therelated_field
kwarg specifies which relationship to use, soif we are interested in the relationship represented by the relatedmodel fieldOrder.line_items
on the Order with pk 3, the url would be/orders/3/relationships/line_items
. OnHyperlinkedModelSerializer
, theResourceRelatedField
will construct the url based on the providedself_link_view_name
keyword argument, which should match thename=
provided in the urlconf, and will use the name of the field for therelated_field
kwarg.Also we can overriderelated_field
in the url. Let’s say we want the url to be:/order/3/relationships/order_items
- all we need to do is just addfield_name_mapping
dict to the class:
field_name_mapping={'order_items':'line_items'}
Working with polymorphic resources
Polymorphic resources allow you to use specialized subclasses without requiringspecial endpoints to expose the specialized versions. For example, if you had aProject
that could be either anArtProject
or aResearchProject
, you canhave both kinds at the same URL.
DJA tests its polymorphic support againstdjango-polymorphic.The polymorphic feature should also work with other popular libraries likedjango-polymodels or django-typed-models.
As this feature depends ondjango-polymorphic
you need to run
pipinstalldjangorestframework-jsonapi['django-polymorphic']
Writing polymorphic resources
A polymorphic endpoint can be set up if associated with a polymorphic serializer.A polymorphic serializer takes care of (de)serializing the correct instances types and can be defined like this:
classProjectSerializer(serializers.PolymorphicModelSerializer):polymorphic_serializers=[ArtProjectSerializer,ResearchProjectSerializer]classMeta:model=models.Project
It must inherit fromserializers.PolymorphicModelSerializer
and define thepolymorphic_serializers
list.This attribute defines the accepted resource types.
Polymorphic relations can also be handled withrelations.PolymorphicResourceRelatedField
like this:
classCompanySerializer(serializers.ModelSerializer):current_project=relations.PolymorphicResourceRelatedField(ProjectSerializer,queryset=models.Project.objects.all())future_projects=relations.PolymorphicResourceRelatedField(ProjectSerializer,queryset=models.Project.objects.all(),many=True)classMeta:model=models.Company
They must be explicitly declared with thepolymorphic_serializer
(first positional argument) correctly defined.It must be a subclass ofserializers.PolymorphicModelSerializer
.
resource_name
defined on the view.Meta
You may add metadata to the rendered json in two different ways:meta_fields
andget_root_meta
.
On anyrest_framework_json_api.serializers.ModelSerializer
you may add ameta_fields
property to theMeta
class. This behaves in the same manner as the defaultfields
property and will causeSerializerMethodFields
or model values to beadded to themeta
object within the samedata
as the serializer.
To add metadata to the top levelmeta
object add:
defget_root_meta(self,resource,many):ifmany:# Dealing with a list requestreturn{'size':len(resource)}else:# Dealing with a detail requestreturn{'foo':'bar'}
to the serializer. It must return a dict and will be merged with the existing top levelmeta
.
To access metadata in incoming requests, theJSONParser
will add the metadata under a top level_meta
key in the parsed data dictionary. For instance, to access meta data from aserializer
object, you may useserializer.initial_data.get("_meta")
. To customize the_meta
key, seehere.
Links
Addingurl
tofields
on a serializer will add aself
link to thelinks
key.
Related links will be created automatically when using the Relationship View.
Included
JSON:API can include additional resources in a single network request.The specification refers to this feature asCompound Documents.Compound Documents can reduce the number of network requestswhich can lead to a better performing web application.To accomplish this,the specification permits a top levelincluded
key.The list of content within this key are the extra resourcesthat are related to the primary resource.
To make a Compound Document,you need to modify yourModelSerializer
.included_serializers
is required to inform DJA of what and how you would liketo include.included_resources
tells DJA what you want to include by default.
For example,suppose you are making an app to go on quests,and you would like to fetch your chosen knightalong with the quest.You could accomplish that with:
classKnightSerializer(serializers.ModelSerializer):classMeta:model=Knightfields=('id','name','strength','dexterity','charisma')classQuestSerializer(serializers.ModelSerializer):included_serializers={'knight':KnightSerializer,}classMeta:model=Questfields=('id','title','reward','knight')classJSONAPIMeta:included_resources=['knight']
Performance improvements
Be aware that using included resources without any form of prefetchingWILL HURT PERFORMANCE as it will introduce m*(n+1) queries.
A viewset helper was therefore designed to automatically preload data when possible. Such is automatically available when subclassingModelViewSet
orReadOnlyModelViewSet
.
It also allows to define customselect_related
andprefetch_related
for each requestedinclude
when needed in special cases:
rest_framework_json_api.views.ModelViewSet
:
fromrest_framework_json_apiimportviews# When MyViewSet is called with ?include=author it will dynamically prefetch author and author.bioclassMyViewSet(views.ModelViewSet):queryset=Book.objects.all()select_for_includes={'author':['author__bio'],}prefetch_for_includes={'__all__':[],'all_authors':[Prefetch('all_authors',queryset=Author.objects.select_related('bio'))],'category.section':['category']}
An additional convenience DJA class exists for read-only views, just as it does in DRF.
fromrest_framework_json_apiimportviewsclassMyReadOnlyViewSet(views.ReadOnlyModelViewSet):# ...
The special keyword__all__
can be used to specify a prefetch which should be done regardless of the include, similar to making the prefetch yourself on the QuerySet.
Using the helper to prefetch, rather than attempting to minimise queries viaselect_related
might give you better performance depending on the characteristics of your data and database.
For example:
If you have a single model, e.g. Book, which has four relations e.g. Author, Publisher, CopyrightHolder, Category.
To display 25 books and related models, you would need to either do:
a) 1 query via selected_related, e.g. SELECT * FROM books LEFT JOIN author LEFT JOIN publisher LEFT JOIN CopyrightHolder LEFT JOIN Category
b) 4 small queries via prefetch_related.
If you have 1M books, 50k authors, 10k categories, 10k copyrightholdersin theselect_related
scenario, you’ve just created a in-memory tablewith 1e18 rows which will likely exhaust any available memory andslow your database to crawl.
Theprefetch_related
case will issue 4 queries, but they will be small and fast queries.
Generating an OpenAPI Specification (OAS) 3.0 schema document
DRF has aOAS schema functionality to generate anOAS 3.0 schema as a YAML or JSON file.
DJA extends DRF’s schema support to generate an OAS schema in the JSON:API format.
Deprecation notice:
REST framework’s built-in support for generating OpenAPI schemas isdeprecated in favor of 3rd party packages that can provide thisfunctionality instead. Therefore we have also deprecated the schema support inDjango REST framework JSON:API. The built-in support will be retired over thenext releases.
As a full-fledged replacement, we recommend thedrf-spectacular-json-api package.
AutoSchema Settings
In order to produce an OAS schema that properly represents the JSON:API structureyou have to either add aschema
attribute to each view class or set theREST_FRAMEWORK['DEFAULT_SCHEMA_CLASS']
to DJA’s version of AutoSchema.
View-based
fromrest_framework_json_api.schemas.openapiimportAutoSchemaclassMyViewset(ModelViewSet):schema=AutoSchema...
Default schema class
REST_FRAMEWORK={# ...'DEFAULT_SCHEMA_CLASS':'rest_framework_json_api.schemas.openapi.AutoSchema',}
Adding additional OAS schema content
You can extend the OAS schema document by subclassingSchemaGenerator
and extendingget_schema
.
Here’s an example that adds OASinfo
andservers
objects.
fromrest_framework_json_api.schemas.openapiimportSchemaGeneratorasJSONAPISchemaGeneratorclassMySchemaGenerator(JSONAPISchemaGenerator):""" Describe my OAS schema info in detail (overriding what DRF put in) and list the servers where it can be found. """defget_schema(self,request,public):schema=super().get_schema(request,public)schema['info']={'version':'1.0','title':'my demo API','description':'A demonstration of [OAS 3.0](https://www.openapis.org)','contact':{'name':'my name'},'license':{'name':'BSD 2 clause','url':'https://github.com/django-json-api/django-rest-framework-json-api/blob/main/LICENSE',}}schema['servers']=[{'url':'http://localhost/v1','description':'local docker'},{'url':'http://localhost:8000/v1','description':'local dev'},{'url':'https://api.example.com/v1','description':'demo server'},{'url':'{serverURL}','description':'provide your server URL','variables':{'serverURL':{'default':'http://localhost:8000/v1'}}}]returnschema
Generate a Static Schema on Command Line
SeeDRF documentation for generateschemaTo generate a static OAS schema document, using thegenerateschema
management command, youmust override DRF’s defaultgenerator_class
with the DJA-specific version:
$ ./manage.py generateschema --generator_class rest_framework_json_api.schemas.openapi.SchemaGenerator
You can then use any number of OAS tools such asswagger-ui-watcherto render the schema:
$ swagger-ui-watcher myschema.yaml
Note: Swagger-ui-watcher will complain that “DELETE operations cannot have a requestBody”but it will still work. Thiserrorin the OAS specification will be fixed whenOAS 3.1.0is published.
(swagger-ui will work silently.)
Generate a Dynamic Schema in a View
SeeDRF documentation for a Dynamic Schema.
fromrest_framework.schemasimportget_schema_viewurlpatterns=[...path('openapi',get_schema_view(title="Example API",description="API for all things …",version="1.0.0",generator_class=MySchemaGenerator,),name='openapi-schema'),path('swagger-ui/',TemplateView.as_view(template_name='swagger-ui.html',extra_context={'schema_url':'openapi-schema'}),name='swagger-ui'),...]
Third Party Packages
About Third Party Packages
Following the example ofDjango REST framework we also support, encourage and strongly favor the creation of Third Party Packages to encapsulate new behavior rather than adding additional functionality directly to Django REST framework JSON:API especially when it involves adding new dependencies.
We aim to make creating third party packages as easy as possible, whilst keeping a simple and well maintained core API. By promoting third party packages we ensure that the responsibility for a package remains with its author. If a package proves suitably popular it can always be considered for inclusion into the DJA core.
Existing Third Party Packages
To submit new content,open an issue orcreate a pull request.
drf-yasg-json-api - Automated generation of Swagger/OpenAPI 2.0 from Django REST framework JSON:API endpoints.
drf-spectacular-json-api - OpenAPI 3 schema generator for Django REST framework JSON:API based on drf-spectacular.