Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • 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.

...

  1. 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 our id a UUID4 as recommended in the json:api jsonapi spec.
  2. Create a Course “parent” model and a CourseTerm “child model” that references each Course instance via a ForeignKey.
  3. 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:

  1. Use a HyperlinkedModelSerializer which gives us the HATEOAS links.
  2. For each Model, choose which model fields to serialize.
  3. Define the ResourceRelatedField linkage between the models and the JSON{json:API api} representation. This is where the JSON{json:API api} relationships and related attributes get created.
  4. Add included_serializers which are needed to serialize the included compound document data.

...

Code Block
languagepython
linenumbersfalse
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
languagediff
linenumbersfalse
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',

...

...

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.

...