...
- Django is the core Django framework.
- Django Debug Toolbar adds an in-browser debugger for the Django UI.
- Django REST Framework (DRF) makes it easy to write RESTful APIs.
- Django REST Framework JSON API (DJA) extends DRF to use the JSON{json:API api} format.
- Django OAuth Toolkit (DOT) adds an OAuth 2.0-based security layer.
- Django CORS Middleware is needed for OAuth cross-origin resource sharing.
- Django REST condition allows for boolean composition of DRF view permissions.
- Django Filter for filtering results using the JSON{json:API api}
filter
query parameter. - PyYAML for YAML file utilities.
- tox for automated unit tests, etc.
- tox-pip-extensions makes tox work better with
pip
.
...
- Make a
CommonModel
abstract class that adds some common fields that we want all our models to include. These are things like various dates, modifying users, etc. We also choose to make ourid
a UUID4 as recommended in the json:api jsonapi spec. - Create a
Course
“parent” model and aCourseTerm
“child model” that references eachCourse
instance via aForeignKey
. - Each model has a default parameter to order by. These will come in handy when we get to pagination and want consistent paginated results.
...
Serializers render the Models in the “wire” format, which is JSON, and specifically JSON{json:APIapi}, so we import our serializers from rest_framework_json_api.serializers
. We will:
- Use a
HyperlinkedModelSerializer
which gives us the HATEOAS links. - For each Model, choose which model fields to serialize.
- Define the
ResourceRelatedField
linkage between the models and the JSON{json:API api} representation. This is where the JSON{json:API api}relationships
andrelated
attributes get created. - Add
included_serializers
which are needed to serialize theincluded
compound document data.
...
Code Block | ||||
---|---|---|---|---|
| ||||
from rest_framework_json_api.relations import ResourceRelatedField from rest_framework_json_api.serializers import HyperlinkedModelSerializer from myapp.models import Course, CourseTerm class CourseSerializer(HyperlinkedModelSerializer): """ (de-)serialize the Course. """ class Meta: model = Course fields = ( 'url', 'school_bulletin_prefix_code', 'suffix_two', 'subject_area_code', 'course_number', 'course_identifier', 'course_name', 'course_description', 'effective_start_date', 'effective_end_date', 'last_mod_user_name', 'last_mod_date', 'course_terms') course_terms = ResourceRelatedField( model=CourseTerm, many=True, read_only=False, allow_null=True, required=False, queryset=CourseTerm.objects.all(), self_link_view_name='course-relationships', related_link_view_name='course-related', ) # JSON{json:APIapi} 'included' support (also used for `related_serializers` for DJA 2.6.0) included_serializers = { 'course_terms': 'myapp.serializers.CourseTermSerializer', } # Uncomment this and the course_terms will be included by default, # otherwise '?include=course_terms' must be added to the URL. # class JSONAPIMeta: # included_resources = ['course_terms'] class CourseTermSerializer(HyperlinkedModelSerializer): class Meta: model = CourseTerm fields = ( 'url', 'term_identifier', 'audit_permitted_code', 'exam_credit_flag', 'effective_start_date', 'effective_end_date', 'last_mod_user_name', 'last_mod_date', 'course') course = ResourceRelatedField( model=Course, many=False, # this breaks new 2.6.0 related support. Only works when True. read_only=False, allow_null=True, required=False, queryset=Course.objects.all(), self_link_view_name='course_term-relationships', related_link_view_name='course_term-related', ) # JSON{json:APIapi} 'included' support included_serializers = { 'course': 'myapp.serializers.CourseSerializer', } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
diff --git a/myapp/serializers.py b/myapp/serializers.py index 32604cb..beb962c 100644 --- a/myapp/serializers.py +++ b/myapp/serializers.py @@ -8,11 +8,24 @@ from myapp.models import Course, CourseTerm, Instructor, Person class HyperlinkedModelSerializer(HyperlinkedModelSerializer): """ + Common serializer class for all model serializers. Extends :py:class:`.models.CommonModel` to set `last_mod_user_name` and `...date` from auth.user on a POST/PATCH, not from the client app. + This silently *ignores* anything CREATEd or PATCHed for these fields. """ - #: these are read-only fields - read_only_fields = ('last_mod_user_name', 'last_mod_date') + class Meta: + """ + In order for this Meta inner class to be inherited by the various serializers, + one must explicitly inherit it as in this example:: + + class MySerializer(HyperlinkedModelSerializer): + class Meta(HyperlinkedModelSerializer.Meta): + model = MyModel + """ + #: serialize all model fields unless otherwise overridden + fields = "__all__" + #: mark these fields as read-only + read_only_fields = ('last_mod_user_name', 'last_mod_date') def _last_mod(self, validated_data): """ @@ -40,18 +53,12 @@ class HyperlinkedModelSerializer(HyperlinkedModelSerializer): class CourseSerializer(HyperlinkedModelSerializer): """ - (de-)serialize the Course. + (de-)serialize the Course model. """ - class Meta: + class Meta(HyperlinkedModelSerializer.Meta): model = Course - fields = ( - 'url', - 'school_bulletin_prefix_code', 'suffix_two', 'subject_area_code', - 'course_number', 'course_identifier', 'course_name', 'course_description', - 'effective_start_date', 'effective_end_date', - 'last_mod_user_name', 'last_mod_date', - 'course_terms') + #: a course has zero or more course_term instances course_terms = ResourceRelatedField( model=CourseTerm, many=True, @@ -63,7 +70,8 @@ class CourseSerializer(HyperlinkedModelSerializer): related_link_view_name='course-related', ) - #: json api 'included' support (also used for `related_serializers` for DJA 2.6.0) + #: `JSON:API`{json:api} compound document <https://jsonapi.org/format/#document-compound-documents>`_ + #: (also used for `related_serializers` for DJA 2.6.0) included_serializers = { 'course_terms': 'myapp.serializers.CourseTermSerializer', } @@ -74,16 +82,13 @@ class CourseSerializer(HyperlinkedModelSerializer): class CourseTermSerializer(HyperlinkedModelSerializer): - class Meta: + """ + (de-)serialize the CourseTerm model. + """ + class Meta(HyperlinkedModelSerializer.Meta): model = CourseTerm - fields = ( - 'url', - 'term_identifier', 'audit_permitted_code', - 'exam_credit_flag', - 'effective_start_date', 'effective_end_date', - 'last_mod_user_name', 'last_mod_date', - 'course', 'instructors') + #: a course_term has zero or one parent courses course = ResourceRelatedField( model=Course, many=False, @@ -94,6 +99,7 @@ class CourseTermSerializer(HyperlinkedModelSerializer): self_link_view_name='course_term-relationships', related_link_view_name='course_term-related', ) + #: a course_term can have many instructors instructors = ResourceRelatedField( model=Instructor, many=True, @@ -105,7 +111,8 @@ class CourseTermSerializer(HyperlinkedModelSerializer): related_link_view_name='course_term-related', ) - #: json api 'included' support + #: ``?include=course`` or ``?include=instructors`` + #: `JSON:API`{json:api} compound document <https://jsonapi.org/format/#document-compound-documents>`_ included_serializers = { 'course': 'myapp.serializers.CourseSerializer', 'instructors': 'myapp.serializers.InstructorSerializer', @@ -113,10 +120,13 @@ class CourseTermSerializer(HyperlinkedModelSerializer): class PersonSerializer(HyperlinkedModelSerializer): - class Meta: + """ + (de-)serialize the Person model. + """ + class Meta(HyperlinkedModelSerializer.Meta): model = Person - fields = ('url', 'name', 'instructor') + #: a person is an instructor instructor = ResourceRelatedField( model=Instructor, many=False, @@ -128,16 +138,21 @@ class PersonSerializer(HyperlinkedModelSerializer): related_link_view_name='person-related', ) + #: `JSON:API`{json:api} compound document <https://jsonapi.org/format/#document-compound-documents>`_ included_serializers = { 'instructor': 'myapp.serializers.InstructorSerializer', } class InstructorSerializer(HyperlinkedModelSerializer): - class Meta: + """ + (de-)serialize the Instructor model. + """ + class Meta(HyperlinkedModelSerializer.Meta): model = Instructor - fields = ('person', 'course_terms', 'url') + fields = "__all__" + #: an instructor teaches zero or more course instances course_terms = ResourceRelatedField( model=CourseTerm, many=True, @@ -149,6 +164,7 @@ class InstructorSerializer(HyperlinkedModelSerializer): related_link_view_name='instructor-related', ) + #: an instructor is a person person = ResourceRelatedField( model=Person, many=False, @@ -160,6 +176,7 @@ class InstructorSerializer(HyperlinkedModelSerializer): related_link_view_name='instructor-related' ) + #: `JSON:API`{json:api} compound document <https://jsonapi.org/format/#document-compound-documents>`_ included_serializers = { 'course_terms': 'myapp.serializers.CourseTermSerializer', 'person': 'myapp.serializers.PersonSerializer', |
...
- https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#queryparametervalidationfilter makes sure only valid JSON{json:API api} query parameters are provided. If you leave this out, a client could misspell
sprt
and they’d wonder why the results were not properly sorted. - https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#orderingfilter implements sorting.
- https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#djangofilterbackend implements the
filter
query parameter and requires additional configuration to define what filters are allowed. - https://django-rest-framework.readthedocs.io/en/latest/api-guide/filtering/#searchfilter implements a keyword search across multiple fields.
...
In our example, we configure filterset_fields
with a variety of relational operations. Note that some of these perform related field path searches, for example: course_terms__term_identifier
. This is configured using the standard Django double-underscore notation but can also use JSON{json:API api} dotted notation: course_terms.term_identifier
.
...