.. _reference/serializers: ================ REST Serializers ================ Good application programming style is to strictly separate of *Models*, *Views* and *Controllers*. In typical classic Django jargon, *Views* act as, what outsiders normally denote a controller. *Controllers* can sometimes be found on the server and sometimes on the client. In **django-SHOP** a significant portion of the controller code is written in JavaScript in the form of Angular directives_. Therefore, all data exchange between the *View* and the *Model* must be performed in a serializable format, namely JSON. This allows us to use the same business logic for the server, as well as for the client. It also means, that we could create native mobile apps, which communicate with a web-application, without ever seeing a line of HTML code. Moreover, since **django-SHOP** uses **django-CMS** to organize all available components, a classic Django "View" does not make much sense anymore. Therefore, as we evolve our Model-View-Control pattern into a modern web application, our REST serializers become the new controllers. From a Database Model to the Serializer ======================================= As we already know, all database models from the **django-SHOP** framework are owned by the merchant implementation. Model serializers reflect their content and hence are tightly coupled with them. We therefore must be able to create our own serializers in a way similar to how we extend our database models. This means that we have a set of base serializers, which perform the task required by their basic counterpart models. Thus, if we extend these models, we normally also might want to extend their serializers. Every URL is a REST endpoint ============================ Every URL which is part of part of **django-SHOP**, namely the product's list and detail views, the cart and checkout views, the order list and detail views, they all are REST endpoints. What does that mean? Catalog List View ----------------- Say, we are working with the provided demo shop, then the product's list view is available at http://localhost:8000/de/shop/ . By appending ``?format=json`` to the URL, the raw data making up our product list, is rendered as a JSON object. For humans, this is difficult to read, therefore the Django Restframework offers a version which is more legible: Instead of the above, we invoke the URL as http://localhost:8000/de/shop/?format=api . This renders the list of products as: |rest-catalog-list| .. |rest-catalog-list| image:: /_static/rest-catalog-list.png Overriding the default Product Summary Serializer ................................................. .. code-block:: python :caption: myshop/serializers.py :linenos: from shop.serializers.bases import ProductSerializer from myshop.models.product import MyProduct class ProductSummarySerializer(ProductSerializer): class Meta: model = MyProduct fields = ['id', 'product_name', 'product_url', 'product_type', 'product_model', 'price'] All these fields can be extracted directly from the product model with the exception of the sample image. This is because we yet do not know the final dimensions of the image inside its HTML element such as ````, and we certainly want to resize it using easy-thumbnails_ with Pillow_ before it is delivered. An easy way to solve this problem is to use the ``SerializerMethodField``. Simply extend the above class to: .. code-block:: python :linenos: from rest_framework.serializers import SerializerMethodField class ProductSummarySerializer(ProductSerializer): media = SerializerMethodField() def get_media(self, product): return self.render_html(product, 'media') As you might expect, ``render_html`` assigns a HTML snippet to the field ``media`` in the serialized representation of our product. This method uses a template to render the HTML. The name of this template is constructed using the following rules: #. Look for a folder named according to the project's name, ie. ``settings.SHOP_APP_LABEL`` in lower case. If no such folder can be found, then use the folder named ``shop``. #. Search for a subfolder named ``products``. #. Search for a template named "*label*-*product_type*-*postfix*.html". These three subfieds are determined using the following rule: - *label*: the component of the shop, for instance ``catalog``, ``cart``, ``order``. - *product_type*: the class name in lower case of the product's Django model, for instance ``smartcard``, ``smartphone`` or if no such template can be found, just ``product``. - *postfix*: This is an arbitrary name passed in by the rendering function. As in the example above, this is the string ``media``. .. note:: It might seem *un*-RESTful to render HTML snippets by a serializer and deliver them via JSON to the client. However, we somehow must re-size the images assigned to our product to fit into the layout of our list view. The easiest way to do this in a configurable manner is to use the easy-thumbnails_ library and its templatetag ``{% thumbnail product.sample_image ... %}``. The template to render the media snippet could look like: .. code-block:: django :caption: myshop/products/catalog-smartcard-media.html {% load i18n thumbnail djng_tags %} {% thumbnail product.sample_image 100x100 crop as thumb %} The template of the products list view then may contain a list iteration such as: .. code-block:: django :emphasize-lines: 5 {% for product in data.results %}
{% endfor %} The tag ``{{ product.media }}`` inserts the HTML snippet as prepared by the serializer from above. A serializer may add more than one ``SerializerMethodField``. This can be useful, if the list view shall render different product types using different snippet templates. Catalog Detail View ------------------- By following a URL of a product's detail view, say http://localhost:8000/de/shop/smart-phones/apple-iphone-5?format=api , one may check the legible representation such as: |rest-catalog-detail| .. |rest-catalog-detail| image:: /_static/rest-catalog-detail.png Routing to these endpoints -------------------------- Since we are using CMS pages to display the catalog's list view, we must provide an apphook_ which must be attached to this page. Since these catalog apphooks can vary in many ways they are not part of the shop framework, but must be created and added to the project as the :ref:`reference/create-CatalogListApp`. Catalog List View ................. The urlpattern matching the regular expression ``^$`` routes onto the catalog list view class :class:`shop.views.catalog.CMSPageProductListView` passing in a special serializer class, for example :class:`myshop.serializers.ProductSummarySerializer`. This has been customized to represent our product models in our catalog templates. Since the serialized data now is available as a Python dictionary or as a plain Javascript object, these templates then can be rendered by the Django template engine, as well as by the client using for instance AngularJS. This View class, which inherits from :class:`rest_framework.generics.ListAPIView` accepts a list of filters for restricting the list of items. As we (ab)use CMS pages as categories, we somehow must assign them to our products. Therefore our example project assigns a many-to-many field named ``cms_pages`` to our Product model. Using this field, the merchant can assign each product to one or more CMS pages, using the apphook ``Catalog List``. This special ``filter_backend``, :class:`shop.rest.filters.CMSPagesFilterBackend`, is responsible for restricting selected products on the current catalog list view. Catalog Detail View ................... The urlpattern matching the regular expression ``^(?P