This is the last part of my tutorial for the Django REST framework.

Be sure to catch up with the work we’ve completed in other parts of the series:

Note: You can find the project code we’re working on in this series here.

In this article, I take a closer look at disabling unnecessary fields and expanding related objects.

I want to present this topic in two ways. The first one will follow the flow of the previous sections of this guide. The second approach will ruin that order – and then bring about a new one. But let’s not get ahead of ourselves. 😉 

Selective fields

For this function, we need the drf-dynamic-fields library:

1
$ pip install drf-flex-fields

Next, we replace the current serializer class (a mixin is also available).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# serializers.py
from rest_flex_fields import FlexFieldsModelSerializer
from rest_framework import serializers

from . import models

class FriendSerializer(FlexFieldsModelSerializer):
   owner = serializers.HiddenField(
       default=serializers.CurrentUserDefault()
   )

   class Meta:
       model = models.Friend
       fields = ('id', 'name', 'owner', 'has_overdue')

From now on, we can point to specific fields we need:

http://127.0.0.1:8000/api/v1/friends/?fields=id,name

or list fields that we’d like to omit:

http://127.0.0.1:8000/api/v1/friends/?omit=has_overdue



Adding related objects

The simplest way to add related objects is using the right serializer in the relation field. 

1
2
3
4
5
6
7
8
# serializers.py
class BorrowedSerializer(FlexFieldsModelSerializer):
   what = BelongingSerializer()
   to_who = FriendSerializer()

   class Meta:
       model = models.Borrowed
       fields = ('id', 'what', 'to_who', 'when', 'returned')

But we need to remember about two things here: 

  1. To avoid burdening our database, let’s fill the default queryset with select_related:
1
2
3
4
# views.py
class BorrowedViewset(NestedViewSetMixin, viewsets.ModelViewSet):
   queryset = models.Borrowed.objects.all().select_related('to_who', 'what')
   # ...

2. Relations defined in that way are default only for reading and adding, the saving option will require more work. We can, however, avoid that by using a library that allows expanding fields when needed:

First, we point to the serializers that will be used to expand fields.

1
2
3
4
5
6
7
8
9
class BorrowedSerializer(FlexFieldsModelSerializer):
   expandable_fields = {
       "what": (BelongingSerializer, {"source": "what"}),
       "to_who": (FriendSerializer, {"source": "to_who"})
   }

   class Meta:
       model = models.Borrowed
       fields = ('id', 'what', 'to_who', 'when', 'returned')

Next, we need to work with the viewset. At this point, we should change the base class (a mixin is also available) and add a list of expandable fields.

1
2
3
4
class BorrowedViewset(NestedViewSetMixin, FlexFieldsModelViewSet):
   queryset = models.Borrowed.objects.all().select_related('to_who', 'what')
   permit_list_expands = ["what", "to_who"]
   # ...

NOTE: Make sure to remember about select_related in queryset. It will reduce the burden on the database significantly. You can get similar result with using prefetch_related for the ManyToMany relation.

We can now expand our fields.  

http://127.0.0.1:8000/api/v1/borrowings/?expand=what,to_who



Here’s a different approach

Finally, I wanted to show you another approach that ruins the order we’ve built so far. I’m going to talk about it using one of the most well-developed DRF extensions, DREST (Dynamic REST). You can find the code for this section in a separate branch of our repository called drest and under the part07-drest tag.

The DREST documentation is very comprehensive, so I’m only going to focus on the most important fragments related to our topic.

Installation and configuration:

1
$ pip install dynamic-rest
1
2
3
4
5
# settings.py
INSTALLED_APPS = [
    # ...
    "dynamic_rest",
]

One of the first things we should add after the installation is filling out our browsable API with a list of all the available endpoints:

1
2
3
4
5
6
REST_FRAMEWORK = {
    "DEFAULT_RENDERER_CLASSES": [
        "rest_framework.renderers.JSONRenderer",
        "dynamic_rest.renderers.DynamicBrowsableAPIRenderer",
    ],
}

To take full advantage of DREST, we should switch the currently used ModelSerializer class to DynamicModelSerializer. We’re also going to change the ModelViewSet class to DynamicModelViewSet. 

1
2
3
4
5
6
7
8
9
10
11
12
13
#serializers.py
from rest_framework import serializers
from dynamic_rest.serializers import DynamicModelSerializer
# ...

class FriendSerializer(DynamicModelSerializer):
   # ...

class BelongingSerializer(DynamicModelSerializer):
   # ...

class BorrowedSerializer(DynamicModelSerializer):
   # ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# views.py
import django_filters
from django.core.mail import send_mail
from dynamic_rest.viewsets import DynamicModelViewSet
# ...


class FriendViewset(NestedViewSetMixin, DynamicModelViewSet):
   # ...

class BelongingViewset(DynamicModelViewSet):
   # ...

class BorrowedViewset(NestedViewSetMixin, DynamicModelViewSet):
   # ...

Unfortunately, at the moment of wiring this article the library is incompatible with nested routers (which I covered recently). However, it partially realizes this function automatically, allowing us to view ManyToMany relations. For the sake of structure, let’s get rid of the code responsible for nesting:

1
2
3
4
5
6
7
8
9
10
11
# api.py
from dynamic_rest.routers import DynamicRouter
from rest_framework_extensions.routers import NestedRouterMixin

from core import views as myapp_views


router = DynamicRouter()
friends = router.register(r"friends", myapp_views.FriendViewset)
router.register(r"belongings", myapp_views.BelongingViewset)
router.register(r"borrowings", myapp_views.BorrowedViewset)

We can now begin to view the available functionalities:


Selective fields

Computing the value of specific fields in our serializers can be very costly. That’s why we may want to omit them in certain cases or add them only upon request. DREST allows to realize both of these paths.

Deleting fields we don’t need is easy. All it takes is adding the parameter exclude[] in our query together with the field’s name.  

http://127.0.0.1:8000/api/v1/friends/?exclude[]=has_overdue

It’s worth to pay attention to the change of the returned data default format by the DynamicModelViewSet. Such a structure is in line with the REST best practices.

As for the fields added upon request, we can mark them with the help of the deferred_fields parameter:

1
2
3
4
5
6
7
class FriendSerializer(DynamicModelSerializer):
   owner = serializers.HiddenField(default=serializers.CurrentUserDefault())

   class Meta:
       model = models.Friend
       fields = ("id", "name", "owner", "has_overdue")
       deferred_fields = ("has_overdue",)

That is how we add the field to the response:

http://127.0.0.1:8000/api/v1/friends/?include[]=has_overdue

Another key matter is…


Adding related objects

DREST comes with a handy functionality that additionally optimizes query execution time.

Every relation we mark with the DynamicRelationField field can be expanded and the list of related elements will be returned together with the original result.

For example, the following query will include friends and items in the results:

http://127.0.0.1:8000/api/v1/borrowings?include[]=to_who.*&include[]=what.*

Yes, I do realize that the default name of the borrowed items isn’t that great…

If our application has a more complex structure, we can use that method for including objects on any level of nesting. However, we need to remember about the burden we place on the database as it becomes greater with every level.

This article finishes my series about the Django REST Framework where I touched upon almost every aspect of working on the REST API.

Thirsty for more knowledge? Check out other Django tutorials as well!

If that isn’t enough for you, you have two options. You can either forge your own path and share your experience with others, or turn to GraphQL (but that’s a topic for another series).

But for now… So long and thanks for all the fish!

Dominik Kozaczko
Dominik
Backend Engineer

Dominik has been fascinated with computers throughout his entire life. His two passions are coding and teaching - he is a programmer AND a teacher. He specializes mostly in backend development and training junior devs. He chose to work with Sunscrapers because the company profoundly supports the open-source community. In his free time, Dominik is an avid gamer.

Python

Python best practices: How to contribute to Python open-source projects

One of the things we love about Python is its rich ecosystem that comprises countless modules, libraries, and frameworks – together with all the passionate developers who make it [...]

Python

Python best practices: What every Pythonista should know about Unicode

In my experience as a Python developer, I found that understanding the difference between Unicode and UTF is essential to avoid any confusion about how Python handles Unicode data. [...]

Join our newsletter.

Scroll to bottom

Hi there, we use cookies to provide you with an amazing experience on our site. If you continue without changing the settings, we'll assume that you're happy to receive all cookies on the Sunscrapers website. You can change your cookie settings at any time.

Learn more