{"id":2609,"date":"2021-05-25T10:30:00","date_gmt":"2021-05-25T08:30:00","guid":{"rendered":"https:\/\/itell.solutions\/?p=2609"},"modified":"2023-06-16T13:17:07","modified_gmt":"2023-06-16T11:17:07","slug":"skipping-django-serialization-of-rarely-changing-objects","status":"publish","type":"post","link":"https:\/\/itell.solutions\/en\/skipping-django-serialization-of-rarely-changing-objects\/","title":{"rendered":"Skipping Django serialization of rarely changing objects"},"content":{"rendered":"<div data-elementor-type=\"wp-post\" data-elementor-id=\"2609\" class=\"elementor elementor-2609\" data-elementor-post-type=\"post\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-25a98039 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"25a98039\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-367d76d1\" data-id=\"367d76d1\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-13d2cf03 elementor-widget elementor-widget-text-editor\" data-id=\"13d2cf03\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\n<p>For database objects that rarely change but are often requested using a RESTful API, repeated serialization is wasteful. We show you how serialization can be skipped using the HTTP header <code>If-Modified-Since<\/code>.<br \/>As an example, consider a very simplified version of the Django model used by kubo to represent a store:<\/p>\n\n<script src=\"https:\/\/gist.github.com\/roskakori\/0747af1c375dd1c01a51550f8defba53.js?file=models.py\"><\/script>\n\n\n<p>Using the <a href=\"https:\/\/www.django-rest-framework.org\/\" target=\"_blank\" rel=\"noopener\">Django REST framework<\/a>, it is simple to create a serializer and a viewset to retrieve details about all stores available to kubo ordered by name:<\/p>\n\n<script src=\"https:\/\/gist.github.com\/roskakori\/0747af1c375dd1c01a51550f8defba53.js?file=serializers.py\"><\/script>\n\n\n<p>Technically this works fine. However, even though any of the fields of a <code>Store<\/code> rarely change, each call to a <code>\/stores\/123\/<\/code> API route has to serialize the same data over and over again.<\/p>\n\n\n\n<p>But what if the Kubo app remembers when it last queried the Kubo server for a store, and could request to send the full data only if anything changed?<\/p>\n\n\n\n<p>Luckily the HTTP protocol already provides the means to do just that by specifying <code>If-Modified-Since<\/code> header. In case the data changed, the result should be a HTTP 200 (\u201cok\u201d) and the full serialized data. In case nothing changed since the last request, the result should be a HTTP 304 (\u201cnot modified\u201d) without any further data. In this case the app will know that the current data it has already stored locally are still up to date.<\/p>\n\n\n\n<p>To enable such behavior, we first have to remember when a Store was last changed. This can be done by adding a <code>DateTimeField<\/code> to <code>Store<\/code> with <code>auto_now=True<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">last_modified = models.DateTimeField(auto_now=True)<\/pre>\n\n\n\n<p>The <code>auto_now<\/code> means that the value of <code>last_modified<\/code> is automatically updated by Django to the current time each time <code>Store.save()<\/code> is called.<\/p>\n\n\n\n<p>Next we have to tell our viewset to do a full serialization only in case anything changed.<\/p>\n\n\n\n<p>Although Django already provides a decorator <code>@last_modified<\/code> for <a href=\"https:\/\/docs.djangoproject.com\/en\/3.2\/topics\/http\/decorators\/#conditional-view-processing\" target=\"_blank\" rel=\"noopener\">conditional view processing<\/a>, it cannot easily be used in a REST viewset because the viewset has to look at the query parameters to know which objects to retrieve. By then, the decorator has already been passed.<\/p>\n\n\n\n<p>Closer analysis of the <code>ReadOnlyModeViewset<\/code> reveals that in order to obtain a single object, the retrieve() method from the RetrieveModelMixin is called:<\/p>\n\n<script src=\"https:\/\/gist.github.com\/roskakori\/0747af1c375dd1c01a51550f8defba53.js?file=viewsets.py\"><\/script>\n\n<p>So we need to overload this and check for the <code>If-Modified-Since<\/code> header.<\/p>\n\n\n\n<p>Getting the header from the request is as simple as<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">if_modified_since_value = request.headers.get(\"If-Modified-Since\")<\/pre>\n\n\n\n<p>This header uses the date format described in <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc2616.html#page-20\" target=\"_blank\" rel=\"noopener\">RFC2616<\/a>, for example:<\/p>\n\n\n\n<p>Wed, 7 Apr 2021 13:11:23 GMT<\/p>\n\n\n\n<p>For parsing (and formatting) this, Python provides <a href=\"https:\/\/docs.python.org\/3\/library\/email.utils.html#email.utils.parsedate_to_datetime\" target=\"_blank\" rel=\"noopener\"><code>email.utils.parsedate_to_datetime<\/code><\/a> (resp. <code>format_datetime<\/code>).<\/p>\n\n\n\n<p>We also want to handle any date formatting errors as HTTP 400 (\u201cbad request\u201d).<\/p>\n\n\n\n<p>In case no modifications have happened the result is a simple<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">return Response({}, HTTP_304_NOT_MODIFIED)<\/pre>\n\n\n\n<p>as opposed to the previous, more computational intensive<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">serializer = self.get_serializer(instance)\nreturn Response(serializer.data)<\/pre>\n\n\n\n<p>So the improved retrieve() is:<\/p>\n\n<script src=\"https:\/\/gist.github.com\/roskakori\/0747af1c375dd1c01a51550f8defba53.js?file=viewsets_improved.py\"><\/script>\n\n\n<p>Of course, instead of providing this only for the <code>StoreViewSet<\/code> it is sensible to make a more general solution. In practice one would create a reusable class like <code>ModifiedReadOnlyModelViewset<\/code> that extends the standard <code>ReadOnlyModelViewSet<\/code>. Then viewsets for any appropriate models can derive from it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>With a few lines of code, viewsets of the Django REST framework can skip serialization for unmodified objects provided the client specified the HTTP standard header <code>If-Modified-Since<\/code>. This reduces the computational effort needed to compute the result and the size of the payload sent over the network.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Want more?<\/h2>\n\n\n\n<p>For further information visit our Website <a href=\"https:\/\/itell.solutions\/en\/\">itell.solutions<\/a><\/p>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>For database objects that rarely change but are often requested using a RESTful API, repeated serialization is wasteful. We show you how serialization can be skipped using the HTTP header If-Modified-Since.As an example, consider a very simplified version of the Django model used by kubo to represent a store: Using the Django REST framework, it [&hellip;]<\/p>\n","protected":false},"author":5,"featured_media":2611,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[24,25,28,26,27],"class_list":["post-2609","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wissen2go","tag-django","tag-optimierung","tag-optimization","tag-rest","tag-serializer"],"_links":{"self":[{"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/posts\/2609","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/comments?post=2609"}],"version-history":[{"count":18,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/posts\/2609\/revisions"}],"predecessor-version":[{"id":3129,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/posts\/2609\/revisions\/3129"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/media\/2611"}],"wp:attachment":[{"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/media?parent=2609"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/categories?post=2609"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itell.solutions\/en\/wp-json\/wp\/v2\/tags?post=2609"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}