You are on page 1of 180

djangoSHOP

Release 1.0.2

Jul 02, 2019


Contents

1 Software Architecture 1

2 Unique Features of django-SHOP 5

3 Upgrading 9

4 Tutorial 11

5 Reference 29

6 How To’s 133

7 Django/Python compatibility table 139

8 Development and Community 141

9 To be written 165

10 License 171

Python Module Index 173

Index 175

i
ii
CHAPTER 1

Software Architecture

The django-SHOP framework is, as its name implies, a framework and not a software which runs out of the box.
Instead, an e-commerce site built upon django-SHOP, always consists of this framework, a bunch of other Django
apps and the merchant’s own implementation. While this may seem more complicate than a ready-to-use solution,
it gives the programmer enormous advantages during the implementation:
Not everything can be “explained” to a software system using graphical user interfaces. After reaching a certain point
of complexity, it normally is easier to pour those requirements into executable code, rather than to expect yet another
set of configuration buttons.
When evaluating django-SHOP with other e-commerce solutions, I therefore suggest to do the following litmus test:
Consider a product which shall be sold world-wide. Depending on the country’s origin of the request, use the native
language and the local currency. Due to export restrictions, some products can not be sold everywhere. Moreover, in
some countries the value added tax is part of the product’s price, and must be stated separately on the invoice, while
in other countries, products are advertised using net prices, and tax is added later on the invoice.
Instead of looking for software which can handle such a complex requirement, rethink about writing your own plugins,
able to handle this. With the django, REST and django-SHOP frameworks, this normally is possible in a few dozen
lines of clearly legible Python code. Compare this to solutions, which claim to handle such complex requirements.
They normally are shipped containing huge amounts of features, which very few merchants ever require, but which
bloat the overall system complexity, making such a piece of software expensive to maintain.

1.1 Design Decisions

1.1.1 Single Source of Truth

A fundamental aspect of good software design is to follow the principle of “Don’t repeat yourself”, often denoted as
DRY. In django-SHOP we aim for a single source of truth, wherever possible.
For instance have a look at the shop.models.address.BaseShippingAddress. Whenever we add, change
or remove a field, the ORM mapper of Django gets notified and with ./manage.py makemigrations followed
by ./manage.py migrate our database scheme is updated. But even the input fields of our address form adopt

1
djangoSHOP, Release 1.0.2

to all changes in our address model. Even the client side form field validation adopts to every change in our address
model. As we can see, here our single source of truth is the address model.

1.1.2 Feature Completeness

A merchant who wants to implement a unique feature for his e-commerce site, must never have to touch the code
of the framework. Aiming for ubiquity means, that no matter how challenging a feature is, it must be possible to be
implemented into the merchant’s own implementation, rather than by patching the framework itself.
Otherwise this framework contains a bug - not just a missing feature! I’m sure some merchants will come up with
really weird ideas, I never have thought of. If the django-SHOP framework inhibits to add a feature, then feel
free to create a bug report. The claim “feature completeness” for a framework is the analogue to the term “Turing
completeness” for programming languages.
Consider that on many sites, a merchant’s requirement is patched into existing code. This means that every time a
new version of the e-commerce software is released, that patch must be repeatedly adopted. This can become rather
dangerous when security flaws in that software must be closed immediately. DjangoSHOP instead is designed, so
that the merchant’s implementation and third party plugins have to subclass its models and to override its templates
accordingly.

1.1.3 Minimalism

In a nutshell, django-SHOP offers this set of basic functionalities, to keep the framework simple and stupid (KISS)
without reinventing the wheel:
• A catalog to display product lists and detail views.
• Some methods to add products to the cart.
• A way to remove items from the cart or change their quantities.
• A set of classes to modify the cart’s totals.
• A collection of forms, where customers can add personal, shipping and payment information.
• A way to perform the purchase: this converts the cart into an order.
• A list view where customers can lookup their previously performed orders
• A backend tool which helps to track the state of orders.
All functionality required to build a real e-commerce site, sits on top of this. Computing taxes for instance, can vary a
lot among different legislations and thus is not part of the framework. The same applies for vouchers, rebates, delivery
costs, etc.
These are the parts, which must be fine tuned by the merchant. They can be rather complicate to implement and are
best implemented by separate plugins.

1.1.4 Separation of Concern

Compared to other e-commerce solutions, the django-SHOP framework has a rather small footprint in terms of
code lines, database tables and classes. This does not mean, that its functionality is somehow limited. Instead, the
merchant’s own implementation can become rather large. This is because django-SHOP implies dependencies to
many third party Django apps.
Having layered systems gives us programmers many advantages:
• We don’t have to reinvent the wheel for every required feature.

2 Chapter 1. Software Architecture


djangoSHOP, Release 1.0.2

• Since those dependencies are used in other applications, they normally are tested quite well.
• No danger to create circular dependencies, as found often in big libraries and stand alone applications.
• Better overview for newcomers, which part of the system is responsible for what.
• Easier to replace one component against another one.
Fortunately Django gives us all the tools to stitch those dependencies together. If for instance we would use one of the
many PHP-based e-commerce system, we’d have to stay inside their modest collection for third party apps, or reinvent
the wheel. This often is a limiting factor compared to the huge ecosystems arround Django.

1.1.5 Inversion of Control

Wherever possible, django-SHOP tries to delegate the responsibility for taking decision to the merchant’s implemen-
tation of the site. Let explain this by a small example: When the customer adds a product to the cart, django-SHOP
consults the implementation of the product to determine whether the given item is already part of the cart or not. This
allows the merchant’s implementation to fine tune its product variants.

1.2 Core System

Generally, the shop system can be seen in three different phases:

1.2.1 The shopping phase

From a customers perspective, this is where we look around at different products, presumably in different categories.
We denote this as the catalog list- and catalog detail views. Here we browse, search and filter for products. In one of
the list views, we edit the quantity of the products to be added to our shopping cart.
Each time a product is added, the cart is updated which in turn run the so named “Cart Modifiers”. Cart modifiers sum
up the line totals, add taxes, rebates and shipping costs to compute the final total. The Cart Modifiers are also during
the checkout phase (see below), since the chosen shipping method and destination, as well as the payment method
may modify the final total.

1.2.2 The checkout process

Here the customer must be able to refine the cart’ content: Change the quantity of an item, or remove that item
completely from the cart.
During the checkout process, the customer must enter his addresses and payment informations. These settings may
also influence the cart’s total.
The final step during checkout is the purchase operation. This is where the cart’s content is converted into an order
object and emptied afterwards.

1.2.3 The fulfillment phase

It is now the merchants’s turn to take further steps. Depending on the order status, certain actions must be performed
immediately or the order must be kept in the current state until some external events happen. This could be a payment
receivement, or that an ordered item arrived in stock. While setting up a django-SHOP project, the allowed status
transitions for the fulfillment phase can be plugged together, giving the merchant the possibility to programmatically
define his order workflows.

1.2. Core System 3


djangoSHOP, Release 1.0.2

1.3 Plugins

Django SHOP defines 5 types of different plugins:


1. Product models
2. Cart modifiers
3. Payment backends
4. Shipping backends
5. Order workflow modules
They may be added as a third party django-SHOP plugin, or integrated into the merchant’s implementation.

4 Chapter 1. Software Architecture


CHAPTER 2

Unique Features of django-SHOP

2.1 django-SHOP requires to describe your products instead of pre-


scribing prefabricated models

Products can vary wildly, and modeling them is not always trivial. Some products are salable in pieces, while others
are continues. Trying to define a set of product models, capable for describing all such scenarios is impossible –
describe your product by customizing the model and not vice versa.

2.1.1 E-commerce solutions, claiming to be plug-and-play, normally use one of


these (anti-)patterns

Either, they offer a field for every possible variation, or they use the Entity-Attribute-Value pattern to add meta-data
for each of your models. This at a first glance seems to be easy. But both approaches are unwieldy and have serious
drawbacks. They both apply a different “physical schema” – the way data is stored, rather than a “logical schema”
– the way users and applications require that data. As soon as you have to combine your e-commerce solution with
some Enterprise-Resource-Planning software, additional back-and-forward conversion routines have to be added.

2.1.2 In django-SHOP, the physical representation of a product corresponds to its


logical

django-SHOP’s approach to this problem is to have minimal set of models. These abstract models are stubs provided
to subclass the physical models. Hence the logical representation of the product conforms to their physical one.
Moreover, it is even possible to represent various types of products by subclassing polymorphically from an abstract
base model. Thanks to the Django framework, modeling the logical representation for a set of products, together with
an administration backend, becomes almost effortless.

5
djangoSHOP, Release 1.0.2

2.2 Django-SHOP is multilingual

Products offered in various regions, normally require attributes in different natural languages. For such a set of
products, these attributes can be easily modelled using translatable fields. This lets you seamlessly built a multilingual
e-commerce site.

2.3 Django-SHOP supports multiple currencies

Django-SHOP is shipped with a set of currency types, bringing their own money arithmetic. This adds an additional
layer of security, because one can not accidentally sum up different currencies. These money types always know how
to represent themselves in different local environments, prefixing their amount with the correct currency symbol. They
also offer the special amount “no price” (represented by -), which behaves like zero but is handy for gratuitous items.

2.4 Django-SHOP directly plugs into django-CMS

Product detail pages may use all templatetags from django-CMS, such as the {% placeholder ... %}, the {%
static_placeholder ... %}, or other CMS tags.
Django-SHOP does not presuppose categories to organize product list views. Instead django-CMS pages can be
specialized to handle product lists via a CMS app. This allows the merchant to organize products into categories,
using the existing page hierarchy from the CMS. It also allows to offer single products from a CMS page, without
requiring any category.

2.5 Django-SHOP is based on REST

• Django-SHOP uses the Django REST framework and hence does not require any Django View
• Every view is based on REST interfaces.
• Infinite scrolling and paginated listings use the same template.
• Views for cart, checkout etc. can be inserted into exiting pages.
• This means that one can navigate through products, add them to the cart, modify the cart, register himself as
new customer (or proceed as guest), add his shipping information, pay via Stripe and view his past orders. Other
Payment Service Providers can be added in a pluggable manner.
Every page in the shop: product-list, product-detail, cart, checkout-page, orders-list, order-detail etc. is part of the
CMS and can be edited through the plugin editor. The communication between the client and these pages is done
exclusively through REST. This has the nice side-effect, that the merchants shop implementation does not require any
Django-View.
Django-SHOP is shipped with individual components for each task. These plugins then can be placed into any CMS
placeholder using the plugin editor. Each of these plugins is shipped with their own overridable template, which can
also be used as a stand-alone template outside of a CMS placeholder. Templates for bigger tasks, such as the Cart-View
are granular, so that the HTML can be overridden partially.
Authentication is done through auth-rest, which allows to authenticate against a bunch of social networks, such as
Google+, Facebook, GitHub, etc in a pluggable manner.
Moreover, the checkout process is based on a configurable finite state machine, which means that a merchant can adopt
the shops workflow to the way he is used to work offline.

6 Chapter 2. Unique Features of django-SHOP


djangoSHOP, Release 1.0.2

Client code is built using Bootstrap-3.3 and AngularJS-1.3. jQuery is required only for the backends administration
interface. All browser components have been implemented as AngularJS directives, so that they can be reused between
projects. For instance, my current merchant implementation does not have a single line of customized JavaScript.
This makes it very easy, even for non-programmers, to implement a shop. A merchant only has to adopt his product
models, optionally the cart and order models, and override the templates.

2.5. Django-SHOP is based on REST 7


djangoSHOP, Release 1.0.2

8 Chapter 2. Unique Features of django-SHOP


CHAPTER 3

Upgrading

If you are upgrading from an earlier version, please be sure to read the Release Notes.

9
djangoSHOP, Release 1.0.2

10 Chapter 3. Upgrading
CHAPTER 4

Tutorial

This tutorial shows how to setup a working e-commerce site with django-SHOP using the given dependencies. The
code required to setup this demo can be found in the example/myshop folder.

4.1 Django-SHOP Tutorial

This tutorial is aimed at people new to django SHOP but already familiar with Django. If you aren’t yet, reading their
excellent Django Tutorial is highly recommended.
Since django-SHOP relies on many features offered by django-CMS and Django REST Framework, you should
familiarize yourself with these apps.

4.1.1 Introduction

Django-SHOP is an e-commerce framework rather than a turn-key solution. This means that the merchant is in charge
of the project and that django-SHOP acts as one of the third party dependencies making up the whole project. We
name this the merchant implementation.
The merchant implementation contains everything which makes up its fully customizable project, such as:
• The main configuration file, settings.py.
• The URL-routing entry point, usually urls.py.
• Optionally, but highly reccomended: Django models to describe the products sold by the merchant.
• If required, extended models for the Cart and Order.
• An administration interface to manage entities from all those models.
• Special Cart modifiers to calculate discounts or additional costs.
• Order workflows to handle all the steps how an order is processed.
• Apphooks for integrating Django-Views into django-CMS.

11
djangoSHOP, Release 1.0.2

• Custom filters to restrict the rendered set of products according to their properties.
• Form definitions, if they differ from the built-in defaults.
• HTML snippets and their cascading style sheets, if they differ from the built-in defaults.
This approach allows a merchant to implement every desired extra feature, without having to modify any code in the
django-SHOP framework itself. This however requires to add some custom code to the merchant implementation
itself. Since we don’t want to do this from scratch, we can use a prepared cookiecutter template to bootstrap our first
project. Please follow their instructions for setting up a running demo.
This cookiecutter template is shipped with 3 distinct product models, which are named commodity, smartcard and
polymorphic. Depending on their need for internationalization, they are subdivided into a variant for a single lan-
guage and one with support for translated product properties. Which one of them to use, depends on the merchant
requirements. When answering the questions, asked by the cookiecutter wizard, consider to:
• use commodity, if you want to fill a free-form page with components from the CMS. It does not require any
adaption of the product model. It is useful for shops with a handful of different products. The Commodity
Product Model and The Internationalized Commodity Product Model
• use smartcard, if you have many products, which all share the same properties. It is useful for shops with one
distinct product type. Here the product model usually must be renamed, and further adopted, by adding and
removing fields. The Smart Card Product Model and An Internationalized Smart Card Model
• use polymorphic, if you have many product types, with different properties for each type. Here we have to define
a smallest common denominator for all products, and further create a product model for each distinc product
type. The Polymorphic Product Model and The Internationalized Polymorphic Product Model

4.1.2 Installation

Before installing the files from the project, ensure that your operating system contains these applications:
• NodeJS including npm.
• Python including pip.
Install some additional Python applications, globally or for the current user:
Then change into a directory, usually used for your projects and invoke:
You will be asked a few question. If unsure, just use the defaults. This creates a directory named my-shop, or
whatever you have choosen. This generated directory is the base for adopting this project into your merchant imple-
mentation. For simplicity, in this tutorial, it is refered as my-shop. Change into this directory and install the missing
dependencies:
This demo shop must initialize its database and be filled with content for demonstration purpose. Each of these
steps can be performed individually, but for simplicity we use a Django managment command which wraps all these
command into a single one:
Finally we start the project, using Django’s built-in development server:
Point a browser onto http://localhost:8000/ and check if everything is working. To access the backend at http:
//localhost:8000/admin/ , log in using username admin with password secret.

Note: The first time, django-SHOP renders a page, images must be thumbnailed and cropped. This is an expensive
operation which runs only once. Therefore please be patient, when loading a page for the first time.

12 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

4.1.3 Overview

What you see here is a content managment system consisting of many pages. By accessing the Django administration
backend at Home › django CMS › Pages, one gets an overview of the page-tree structure. One thing which immedi-
ately stands out is, that all pages required to build the shop, are actually pages, served by django-CMS. This means
that the complete sitemap (URL structure) of a shop, can be reconfigured easily to the merchants needs.

4.1.4 Adding pages to the CMS

If we want to add pages to the CMS which have not been installed with the demo, we must sign in as a Django staff
user. If our demo has been loaded through one of the prepared fixtures, use user admin with password secret. After
signing in, a small arrow appears on the top right in our browser. Clicking on that arrow expands the Django-CMS
toolbar.

Click on the menu item named example.com and select Pages . . . . This opens the Django-CMS Page Tree. In
django-SHOP, every page, can be rendered by the CMS. Therefore, unless we need a special landing page, we can
start immediately with the Catalog’s List View of our products.
Click on New Page to create a new Page. As its Title choose whatever seems appropriate. Then change into the
Advanced Settings at the bottom of the page. In this editor window, locate the field Template and choose the default.
Change into Structure mode and locate the placeholder named Main Content, add a Container-plugin, followed by
a Row-, followed by one or more Column-plugins. Choose the appropriate width for each column, so that for any
given breakpoint, the widths units sum up to 12. Below that column, add whatever is approriate for that page. This is
how in django-CMS we add components to our page placeholders.
The default template provided with the demo contains other placeholders. One shall be used to render the breadcrumb.
By default, if no Breadcrumb-plugin has been selected, it shows the path to the current page. By clicking on the
ancestors, one can navigate backwards in the page-tree hierarchy.

4.1.5 Next Chapter

In the next chapter of this tutorial, we will see how to organize the Catalog Views

4.2 Catalog Views

In django-SHOP, every URL, which points to a page visible by the customer, is managed by the CMS. This means
that we are completely free, in how we organize the structure of our page-tree. There are however a few things to
consider, when building a working e-commerce site.
The catalog page(s) is where we present the products, we want to sell. In a shop, we can add as many catalog pages to
the CMS, but there should be at least one, even if the shop only sells one product exclusively.
When editing the CMS page used for the products list view, open Advanced Settings and choose Products List from
the select box labeled Application.
Then choose a template with at least one placeholder. Click onto View on site to change into front-end editing mode.
Locate the main placeholder of the template, and from section Bootstrap, add a Container-plugin, followed by a
Row-, followed by a Column-plugin. Below that column add a Catalog List Views-plugin from section Shop. Then
publish the page, it should not display any products yet.

4.2. Catalog Views 13


djangoSHOP, Release 1.0.2

Django-SHOP does not distinguish between categories and a catalog pages. If our shop needs a hierarchy of different
categories, we organize them using many catalog pages nested into each other. Each product can be assigned to as
many catalog pages as we want.

4.2.1 Assign products to their category

In Django’s administration backend, find the list view for products. Depending on the name of a given product type,
this can be Home › My Shop › Commodities, Home › My Shop › Products, or similar. Choose an item of that list to
open the product’s detail editor. Locate the many-to-many select box labeled Categories > Cms pages. Select one or
more CMS pages where the given product shall appear on.
On reloading the catalog page, the assigned products shall now be visible in their list view. Assure that they have been
set to be active, otherwise they won’t show up.
If we nest categories, products assigned to children will be also be visible on their parents pages.

Configure Pagination

The serializer used to create the list of products for the catalog’s view, usually only renders a subset, adding links
pointing to other URLs for fetching neighboring subsets of that list. We also name this “pagination”. The component
rendering the catalog’s list view offers three different types of pagination:
• Adding a paginator, where the customer can choose the neighboring page manually.
• Adding a simple paginator button, where by clicking one can extend the existing list of products.
• An automatic paginator, which triggers the extension of catalog’s list, whenever the customer scrolls to the end
of the page. We name this infinite scroll.

Note: If manual pagination is selected, django-SHOP tries to prevent widows – these are single items spawning over
the last row. Say that the grid of the list can show 53 items, then the 16nth item is hidden. If however we want to
render 44 items, then it is visible. A side-effect of this feature is, that the 16nth item is rendered again on the following
page.

4.2.2 Product Detail Views

In django-SHOP’s frontend, each product can be rendered using their own detail view. However, we neither have to,
nor can’t create a CMS page for each product. This is, because we have to store the properties of a product, such as a
unit price, product code, availability, etc. inside a Django model. It furthermore would be tedious to create one CMS
page per product, to render its detail view. Therefore, products have to be assigned to their categories, rather than
being children of thereof.
This approach allows us to use CMS pages tagged as Catalog List, as product categories. Furthermore, we can assign
a product to more than one such category.
As with regular Django views, the product detail view is rendered by adding the product to the context, and using a
Django template to render HTML. If the product has custom properties, they shall be referred by that template.
In the merchant implementation, each product type can provide their own template referring exactly the properties of
that model. On rendering, django-SHOP converts the classname of a product to lowercase. Say, we want to render
the detail view of an instance of our class SmartCard. Then we look for a template named
1. myshop/catalog/smartcard-detail.html
2. if not found, then myshop/catalog/product-detail.html

14 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

3. if not found, then shop/catalog/product-detail.html


Inside this template we refer the properties as usual, for instance

<ul class="list-group">
<li class="list-group-item">
<div class="w-50">Product Code:</div>
<strong>{{ product.product_code }}</strong>
</li>
<li ...
</ul>

Django-CMS offers a useful templatetag to access the product backend editor, while navigating on the product’s detail
view. The following HTML snippet renders the product title

{% load cms_tags %}
<h1>{% render_model product "product_name" %}</h1>

with the possibility, that authenticated staff users may double click onto the title. In case the CMS is in edit mode, the
product’s backend editor pops up and, allowing front-end editong by its users.

4.2.3 Product Model Serializers

We already learned how to write model classes and model managers, so what are serializers for?
In django-SHOP the response views do not distinguish whether the product’s information shall be rendered as HTML
or transferred via JSON. This gives us the ability to use the same business logic for web browsers rendering static
HTML, single page web applications communicating via AJAX or native shopping applications for your mobile de-
vices. This btw. is one of the great benefits when working with RESTful API’s and thanks to the djangorestframework
we don’t even have to write any Django Views anymore.
Let’s recap the shop’s catalog list view. There we need some functionality to render a list of all products and we need
a detail view to render each product type. The django-SHOP framework supplies two such serializers:

Serialize the Products for the List View

For each product we want to display in a list view, we need a serializer which converts the content of the most important
fields of a product. Normally these are the Id, the product name, the URL (onto the detail view), the product type, the
price, a caption (short description) and some media field to render a sample image.
For this purpose, the django-SHOP framework provides a default serializer, shop.serializers.default.
product_summary.ProductSummarySerializer, which handles the most common use cases. If required,
it can be replaced by a customized implementation. Such a serializer can be configured using a settings variable.
During development, it can be useful to examine what data this serializer delivers. In django-SHOP the easiest way
to achieve this, is to append ?format=api to the URL on the catalog’s list view. This will show the context data to
render the catalog, but without embedding it into HTML.

Serializer for the Product’s Detail View

The serializer for the Product’s Detail View is very similar to its counterpart, the just described
ProductSummarySerializer. By default, the django-SHOP framework uses the serializer shop.
serializers.bases.ProductSerializer. This serializer converts all properties of the product model into a
serialized representation. Of course, this serializer can also be replaced by a custom implementation. Such a serializer
can be configured by adopting the Detail View class, and is explained in the programmers reference.

4.2. Catalog Views 15


djangoSHOP, Release 1.0.2

During development, it can be useful to examine what data this serializer delivers. The easiest way to achieve this,
is to append ?format=api to the URL on the product’s detail view. This will show the context data to render the
product detail view, but without embedding it into HTML.

The AddToCartSerializer

Rather than using the detail serializer, the business logic for adding a product to the cart has been moved into a
specialized serializer. This is because in django-SHOP products can either be added to the cart from within the detail
view1 , or from their catalog list view. We also need a way to add more than one product variant to the cart from each
products detail page.
For this purpose django-SHOP is shipped with an AddToCartSerializer. It can be overridden for special
product requirements, but for a standard applications, the default implementation should just work out of the box.
During development, it can be useful to examine what data this serializer delivers. The easiest way to achieve this,
is to append /add-to-cart?format=api to the URL on the product’s detail view. This will show the interface
with which the add-to-cart form communicates.
Ensure that the context for rendering a product contains the key product referring to the product object – this is the
default behavior. Then add

{% include "shop/catalog/product-add2cart.html" %}

to an appropriate location in the template which renders the product detail view.
The now included add-to-cart template contains a form with some input fields and a few AngularJS directives, which
communicate with the endpoint connected to the AddToCartSerializer. It updates the subtotal whenever the
customer changes the quantity and optionally displays a nice popup window, whenever an item is added to the cart.
Of course, that template can be extended with arbitrary HTML.
These Angular JS directives require some JavaScript code which is located in the file shop/js/catalog.js; it is
referenced automatically when using the above template include statement.

4.2.4 Understanding the Routing

Behind the scenes, django-CMS allows us to attach Django Views to any existing CMS page using a so called
apphook. This means, that accessing a CMS page or any child ot it, can implicitely invoke a Django View. To
achieve this, in the CMS page’s Advanced Settings, that apphook must be selected from the drop-down menu named
“Application”.
In our implementation, such an apphook can be implemented as:

from django.conf.urls import url


from shop.views.catalog import AddToCartView, ProductListView, ProductRetrieveView
from shop.cms_apphooks import CatalogListCMSApp

class CatalogListApp(CatalogListCMSApp):
def get_urls(self, page=None, language=None, **kwargs):
return [
url(r'^$', ProductListView.as_view()),
url(r'^(?P<slug>[\w-]+)/?$', ProductRetrieveView.as_view()),
url(r'^(?P<slug>[\w-]+)/add-to-cart', AddToCartView.as_view()),
]

apphook_pool.register(CatalogListApp)

1 Specially in business-to-business sites, this usually is done in the list views.

16 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

All what this apphook does, is to set special routes to either, the catalog’s list view, here shop.views.catalog.
ProductListView, or to the product’s detail view, here shop.views.catalog.ProductRetrieveView,
or to the add-to-cart view, here shop.views.catalog.AddToCartView.
Such an apphook allows us to extend an existing CMS page with classic Django Views routed onto sub-URLs of our
page. Here we create additional routes, on top of the existing CMS page. These three views also serve another purpose:
They enrich the rendering context by a Python dictionary named product, it contains the serialized representation
to render the corresponding templates.

4.2.5 Next Chapters

One of the unique features of django-SHOP, is the possibility to choose and/or override its product models. De-
pending on the kind of product model selected through the cookiecutter template, proceed with one of the following
chapters from one of these tutorials:
• The Commodity Product Model
• The Smart Card Product Model
• The Polymorphic Product Model

4.3 The Commodity Product Model

The demo provided by cookiecutter-django-shop using the product model “commodity”, shows how to setup a shop,
with a single generic product, named Commodity. The product model shop.models.defauls.commodity.
Commodity is part of the django-SHOP framework. It is intended for shops where the merchant does not want
to create a customized product model, but rather prefers to create the product’s detail views using common CMS
functionality. Here for demonstration purpose we try to sell a house, hence it is practical that we can layout our CMS
page the way we want to and we can add whatever Django-CMS plugins are available.
A Commodity model contains only the following properties:
• The name of the product.
• The product code.
• The slug (a short label used as the last bit in the URLs).
• The product’s unit price.
• One sample image to be shown in the catalog’s list view.
• A caption to be shown in the catalog’s list view.
The detail view for each product shall however be styled individually using a django-CMS placeholder together with
the plugin system provided, for instance by djangocms-cascade. This gives the merchant all the flexibility to style
each product’s detail page individually and without having to create a special HTML template. It is thus best suited
for types of products with a high degree of customization. Hence in the demo, a house was used to show a product
detail page, filled with standard components from the CMS. Into this placeholder we then can add as many text fields
as we want. Additionally we can use image galleries, carousels, different backgrounds, tab sets, etc.
The commodity demo contains just one product, a splendid villa. In such a situation, we usually don’t want to render
the catalogs list view with one item, but instead want to get redirected onto our lonely product. This can be achieved
by reconfiguring the catalogs list view, and is explained in the reference sections.
Using the Commodity product model only makes sense, if the merchant does not require special product properties
and normally is only suitable for shops with up to a dozen articles. Otherwise, creating a reusable HTML template is
probably less effort, than filling the placeholder for each product’s detail page individually.

4.3. The Commodity Product Model 17


djangoSHOP, Release 1.0.2

4.3.1 The Base Template

Even though we can show the complete information about a (Commodity-) product using standard components, pro-
vided by django-CMS and/or djangocms-cascade, we still have to provide a base template with a placeholder. Since
the django-SHOP framework doesn’t want to know anything about the skeleton of a page, this base template must be
contributed by the merchant implementation.
On the assumption that the product’s detail view renders a product of type “Commodity”, django-SHOP looks for a
template named
1. myshop/catalog/commodity-detail.html
2. if not found, then myshop/catalog/product-detail.html
3. if not found, then shop/catalog/product-detail.html
Note that all names are lowercased, while searching for the matching template. Such a base template must contain the
templatetag

{% load cms_tags %}
...

‘’ {% render_placeholder product.placeholder %}
Here the placeholder is a special field cms.models.fields.PlaceholderField in our Django model
Commodity. It is the equivalent to the placeholder otherwise used in regular django-CMS page templates. This
placeholder field can be added to all Django models for any other product type and is useful, in case the merchant
wants to add some optional and/or unstructured information to its product model. This for instance, can be specially
convenient to add a video, a downloadable datasheet, or other useful information about the product.

4.3.2 The Internationalized Commodity Product Model

If support for multiple languages is enabled, some of the properties can be translated into different natural languages.
In the demo for the product model “commodity”, these properties then become translatable:
• The name of the product.
• The slug.
• A caption to be shown in the catalog’s list view.
Using this internationalized version, requires to configure I18N = True in the settings.py of the project.
Additionally, the thrird party app django-parler must be installed. By doing so, the product model from above shop.
models.defauls.commodity.Commodity, is replaced by an internationalized version.
All other product properties, such as unit price and product code are shared across all languages.

4.3.3 Add Commodity to Cart

One plugin which should always be present on a product’s detail page, is the Add Product to Cart, as found in section
Shop. Otherwise a customer wouldn’t be able to purchase that product. In the provided demo, we sell one house, hence
the usual quantity doesn’t make sense. By using a slightly modified template, the quantity fields is hidden.
It often makes sense to override the “add-to-cart” template for special product models. If for instance a product has
variantions, this is where we would add additional choice fields so that the customer can select different properties,
such as size, color, etc.

18 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

4.3.4 Next Chapter

In the next chapter of this tutorial, we will see how to organize the Cart and Checkout

4.4 The Smart Card Product Model

The demo provided by cookiecutter-django-shop using the product model “smartcard”, shows how to setup a shop,
with a single product type. In our example we use a Smart Card for it. Here the Django model is managed by the
merchant implementation.
Smart Cards have many different attributes such as their card type, the manufacturer, storage capacity and the max-
imum transfer speed. Here it’s the merchant’s responsibility to create the database model according to the physical
properties of the product. The model class to describe a Smart Card therefore is not part of the shop’s framework, but
rather in the merchant’s implementation as found in our example.
Creating a customized product model, requires only a few lines of declarative Python code. Here is a simplified
example:
from django.db import models
from shop.models.product import BaseProduct, BaseProductManager, CMSPageReferenceMixin
from shop.money.fields import MoneyField

class SmartCard(CMSPageReferenceMixin, BaseProduct):


product_name = models.CharField(
max_length=255,
verbose_name="Product Name",
)

slug = models.SlugField(verbose_name="Slug")

caption = models.TextField(
"Caption",
help_text="Short description used in the catalog's list view.",
)

description = models.TextField(
"Description",
help_text="Long description used in the product's detail view.",
)

order = models.PositiveIntegerField(
"Sort by",
db_index=True,
)

cms_pages = models.ManyToManyField(
'cms.Page',
through=ProductPage,
help_text="Choose list view this product shall appear on.",
)

images = models.ManyToManyField(
'filer.Image',
through=ProductImage,
)

(continues on next page)

4.4. The Smart Card Product Model 19


djangoSHOP, Release 1.0.2

(continued from previous page)


unit_price = MoneyField(
"Unit price",
decimal_places=3,
help_text="Net price for this product",
)

card_type = models.CharField(
"Card Type",
choices=[(t, t) for t in ('SD', 'SDXC', 'SDHC', 'SDHC II')],
max_length=9,
)

product_code = models.CharField(
"Product code",
max_length=255,
unique=True,
)

storage = models.PositiveIntegerField(
"Storage Capacity",
help_text="Storage capacity in GB",
)

class Meta:
verbose_name = "Smart Card"
verbose_name_plural = "Smart Cards"
ordering = ['order']

lookup_fields = ['product_code__startswith', 'product_name__icontains']

objects = BaseProductManager()

def get_price(self, request):


return self.unit_price

def __str__(self):
return self.product_name

@property
def sample_image(self):
return self.images.first()

Let’s examine this product model. Our SmartCard inherits from the abstract shop.models.product.
BaseProduct, which is the base class for any product. It only contains a minimal amount of fields, because
django-SHOP doesn’t make any assumptions about the product’s properties. Additionally this class inherits from the
mixin shop.models.product.CMSPageReferenceMixin, which adds some functionality to handle CMS
pages as product categories.
In this class declaration, we use one field for each physical property of our Smart Cards, such as card type, storage,
transfer speed, etc. Using one field per property allows us to build much simpler interfaces, rather than e-commerce
solutions, which use a one-size-fits-all approach, attempting to represent all product’s properties. Otherwise, this
product model class behaves exactly like any other Django model.
In addition to the properties, the example above contains these extra fields:
• slug: This is the URL part after the category part.
• order: This is an integer field to remember the sorting order of products.

20 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

• cms_pages: A list of CMS pages, this product shall appear on.


• images: A list of images of this product.
The list in lookup_fields is used by the Select2-widget, when searching for a product. This is often required,
while setting internal links onto products.
In django-SHOP, the field unit_price is optional. Instead, each product class must provide a method
get_price(), which shall return the unit price for the catalog’s list view. This is because products may have
variations with different price tags, or prices for different groups of customers. Therefore the unit price must be
computed per request, rather than being hard coded into a database column.

4.5 An Internationalized Smart Card Model

If in the demo provided by cookiecutter-django-shop, support for multiple languages (I18N) is enabled, the product
model for our Smart Card changes slightly.
First ensure that django-parler is installed and 'parler' is listed in the project’s INSTALLED_APPS. Then import
some extra classes into the project’s models.py and adopt the product class. Only the relevant changes to our model
class are shown here:
...
from parler.managers import TranslatableManager, TranslatableQuerySet
from polymorphic.query import PolymorphicQuerySet
...

class ProductQuerySet(TranslatableQuerySet, PolymorphicQuerySet):


pass

class ProductManager(BaseProductManager, TranslatableManager):


queryset_class = ProductQuerySet

def get_queryset(self):
qs = self.queryset_class(self.model, using=self._db)
return qs.prefetch_related('translations')

class SmartCard(CMSPageReferenceMixin, TranslatableModelMixin, BaseProduct):


...
caption = TranslatedField()
description = TranslatedField()
...

class SmartCardTranslation(TranslatedFieldsModel):
master = models.ForeignKey(
SmartCard,
related_name='translations',
null=True,
)

caption = models.TextField(
"Caption",
help_text="Short description used in the catalog's list view.",
)

description = models.TextField(
"Description",
help_text="Long description used in the product's detail view.",
(continues on next page)

4.5. An Internationalized Smart Card Model 21


djangoSHOP, Release 1.0.2

(continued from previous page)


)

class Meta:
unique_together = [('language_code', 'master')]

For this model we decided to translate the fields caption and description. The product name of a Smart Card
is international anyways and doesn’t have to be translated into different langauges. Hence we neither use a translatable
field for the product name, nor its slug. On the other hand, if it makes sense to translate the product name, then we’d
simply move these fields into the related class SmartCardTranslation. This gives us all the flexibility we need
to model our products according to their physical properties, and prevents that the administrator of the site has to enter
redundant data through the administration backend, while creating or editing an instance.

4.6 Add Product Model to Django Admin

In order to make our Smart Card editable, we have to register it in the Django administration backend:

from django.contrib import admin


from adminsortable2.admin import SortableAdminMixin
from shop.admin.product import CMSPageAsCategoryMixin, ProductImageInline,
˓→InvalidateProductCacheMixin

from myshop.models import SmartCard

@admin.register(SmartCard)
class SmartCardAdmin(InvalidateProductCacheMixin, SortableAdminMixin,
˓→CMSPageAsCategoryMixin, admin.ModelAdmin):

fields = ['product_name', 'slug', 'product_code', 'unit_price', 'active', 'caption


˓→', 'description',

'storage', 'card_type']
inlines = [ProductImageInline]
prepopulated_fields = {'slug': ['product_name']}
list_display = ['product_name', 'product_code', 'unit_price', 'active']

This is a typical implementation of a Django ModelAdmin. This class uses a few additions however:
• shop.admin.product.InvalidateProductCacheMixin: After saving a product instance, all
caches are going to be cleared.
• adminsortable2.admin.SortableAdminMixin: Is used to add sorting capabilities to the backend
list view.
• shop.admin.product.CMSPageAsCategoryMixin: Is used to assign a product to one ore more CMS
pages, tagged as Categories.
• shop.admin.product.ProductImageInline: Is used to assign a one ore more images to a product
and sort them accordingly.

4.6.1 With I18N support

If multilingual support is required, then we also must add a possibility to make some fields translatable:

from parler.admin import TranslatableAdmin


...
(continues on next page)

22 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

(continued from previous page)


class SmartCardAdmin(InvalidateProductCacheMixin, SortableAdminMixin,
˓→TranslatableAdmin, CMSPageAsCategoryMixin, admin.ModelAdmin):

...

For detail, please refer to the documentation provided by django-parler.

4.7 Next Chapter

In the next chapter of this tutorial, we will see how to organize the Cart and Checkout

4.8 The Polymorphic Product Model

The demo provided by cookiecutter-django-shop_ using the product model “polymorphic”, shows how to setup a
shop, with different product types. This is where polymorphism enters the scene. In our example we use a combination
from the simpler demos “commodity” and “smartcard”.
Since in this example, we have to specialize our product out of a common base, the properties for each product type
are shared accross two models. In our demo, the base model is declared by the class myshop.models.Product.
Here we store the properties common to all product types, such as the product’s name, a caption, etc.
The model classes for Smart Card, Smart Phone and a variation of Commodity then inherits from this base product
class. These models additionally declare their model fields, which are required to describe the physical properties of
each product type. Since they vary, we also have to create special templates for the detail views of each of them. Smart
Phones for instance allow product variations, therefore we must adopt the template for adding the product to the cart.

4.8.1 The Internationalized Polymorphic Product Model

The i18n_polymorphic demo is a variation of the above example, with a few attributes translated into multi-
ple languages, namely caption and description. This sample implementation does not use translated slugs,
although it would be possible.

4.8.2 Next Chapter

In the next chapter of this tutorial, we will see how to organize the Cart and Checkout

4.9 Cart and Checkout

In django-SHOP, the cart and checkout view follow the same idea as all other pages – they are managed by the CMS.
Change into the Django admin backend and look for the CMS page tree. A good position for adding a page is the root
level, but then assure that in Advanced Setting the checkbox Soft root is set.
The checkout can be combined with the cart on the same page or moved on a separate page. Its best position normally
is just below the Cart page.

4.7. Next Chapter 23


djangoSHOP, Release 1.0.2

The Checkout pages presumably are the most complicated page to setup. Therefore no generic receipt can be presented
here. Instead some CMS plugins will be listed here. They can be useful to compose a complete checkout page. In
the reference section it is shown in detail how to create a Cart and Checkout view, but for this tutorial the best way to
proceed is to have a look in the prepared demo project for the Cart and Checkout pages.
A list of plugins specific to django-SHOP can be found in the reference section. They include a cart editor, a static
cart renderer, forms to enter the customers names, addresses, payment- and shipping methods, credit card numbers
and some more.
Other useful plugins can be found in the Django application djangocms-cascade.

4.9.1 Scaffolding

Depending on who is allowed to buy products, keep in mind that visitors must declare themselves whether they want to
buy as guest or as registered user. This means that we first must distinguish between visitor and recognized customer.
The simplest way to do this is to use the Segmentation if- and else-plugin. A recognized customer shall be able to
proceed directly to the purchasing page. A visitor first must declare himself, this could be handled with a collections
of plugins, such as:

24 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

in structure mode. This collection of plugins then will be rendered as:

Please note that the Authentication plugins Login & Reset, Register User and Continue as guest must reload the
current page. This is because during these steps a new session-id is assigned, which requires a full page reload.
After reloading the page, the customer is considered as “recognized”. Since there are a few forms to be filled, this
example uses a Process Bar plugin, which emulates a few sub-pages, which then can be filled out by the customer
step-by-step.

4.9. Cart and Checkout 25


djangoSHOP, Release 1.0.2

A fragment of this collection of plugins then will be rendered as:

26 Chapter 4. Tutorial
djangoSHOP, Release 1.0.2

4.9. Cart and Checkout 27


djangoSHOP, Release 1.0.2

28 Chapter 4. Tutorial
CHAPTER 5

Reference

Reference to classes and concepts used in django-SHOP

5.1 Customer Model

Most web applications distinguish logged in users explicitly from the anonymous site visitor, which is regarded as a
non-existing user, and hence does not reference a session- or database entity. The Django framework, in this respect,
is no exception.
This pattern is fine for web-sites, which run a Content Management System or a Blog, where only an elected group of
staff users shall be permitted to access. This approach also works for web-services, such as social networks or Intranet
applications, where visitors have to authenticate right on from the beginning of their session.
But when running an e-commerce site, this use-pattern has serious drawbacks. Normally, a visitor starts to look for
interesting products, hopefully adding a few of them to their cart. Then on the way to checkout, they decide whether
to create a user account, use an existing one or continue as guest. Here’s where things get complicated.
First of all, for non-authenticated site visitors, the cart does not belong to anybody. But each cart must be associated
with its current site visitor, hence the generic anonymous user object is not appropriate for this purpose. Unfortunately
the Django framework does not offer an explicit but anonymous user object based on the assigned Session-Id.
Secondly, at the latest when the cart is converted into an order, but the visitor wants to continue as guest (thus remaining
anonymous), that order object must refer to an user object in the database. These kind of users would be regarded as
fakes: Unable to log in, reset their password, etc. The only information which must be stored for such a faked user, is
their email address otherwise they couldn’t be informed, whenever the state of their order changes.
Django does not explicitly allow such user objects in its database models. But by using the boolean flag is_active,
we can fool an application to interpret such a guest visitor as a faked anonymous user.
However, since such an approach is unportable across all Django based applications, django-SHOP introduces a new
database model – the Customer model, which extends the existing User model.

29
djangoSHOP, Release 1.0.2

5.1.1 Properties of the Customer Model

The Customer model has a 1:1 relation to the existing User model, which means that for each customer, there
always exists one and only one user object. This approach allows us to do a few things:
The built-in User model can be swapped out and replaced against another implementation. Such an alternative imple-
mentation has a small limitation. It must inherit from django.contrib.auth.models.AbstractBaseUser
and from django.contrib.auth.models.PermissionMixin. It also must define all the fields which are
available in the default model as found in django.contrib.auth.models.User.
By setting the flag is_active = False, we can create guests inside Django’s User model. Guests can not sign
in, they can not reset their password, and hence can be considered as “materialized” anonymous users.
Having guests with an entry in the database, gives us another advantage: By using the session key of the site visitor as
the user object’s username, it is possible to establish a link between a User object in the database with an otherwise
anonymous visitor. This further allows the Cart and the Order models always refer to the User model, since
they don’t have to care about whether a certain user authenticated himself or not. It also keeps the workflow simple,
whenever an anonymous user decides to register and authenticate himself in the future.

5.1.2 Adding the Customer model to our application

As almost all models in django-SHOP, the Customer model itself, uses the Deferred Model Pattern. This means that
the Django project is responsible for materializing that model and additionally allows the merchant to add arbitrary
fields to his Customer model. Sound choices are a phone number, birth date, a boolean to signal whether the
customer shall receive newsletters, his rebate status, etc.
The simplest way is to materialize the given Customer class as found in our default and convenience models:

from shop.models.defaults.customer import Customer

or, if we need extra fields, then instead of the above, we create a customized Customer model:

from shop.models.customer import BaseCustomer

class Customer(BaseCustomer):
birth_date = models.DateField("Date of Birth")
# other customer related fields

Configure the Middleware

A Customer object is created automatically with each visitor accessing the site. Whenever Django’s internal Au-
thenticationMiddleware adds an AnonymousUser to the request object, then the django-SHOP’s CustomerMid-
dleware adds a VisitingCustomer to the request object as well. Neither the AnonymousUser nor the
VisitingCustomer are stored inside the database.
Whenever the AuthenticationMiddleware adds an instantiated User to the request object, then the django-SHOP’s
CustomerMiddleware adds an instantiated Customer to the request object as well. If no associated Customer
exists yet, the CustomerMiddleware creates one.
Therefore add the CustomerMiddleware after the AuthenticationMiddleware in the project’s settings.py:

MIDDLEWARE_CLASSES = (
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'shop.middleware.CustomerMiddleware',
(continues on next page)

30 Chapter 5. Reference
djangoSHOP, Release 1.0.2

(continued from previous page)


...
)

Configure the Context Processors

Additionally, some templates may need to access the customer object through the RequestContext. Therefore,
add this context processor to the settings.py of the project.

TEMPLATE_CONTEXT_PROCESSORS = (
...
'shop.context_processors.customer',
...
)

Implementation Details

The Customer model has a non-nullable one-to-one relation to the User model. Therefore each customer is associ-
ated with exactly one user. For instance, accessing the hashed password can be achieved through customer.user.
password. Some common fields and methods from the User model, such as first_name, last_name, email,
is_anonymous() and is_authenticated() are accessible directly, when working with a Customer object.
Saving an instance of type Customer also invokes the save() method from the associated User model.
The other direction – accessing the Customer model from a User – does not always work. Accessing an attribute
that way fails if the corresponding customer object is missing, ie. if there is no reverse relation from a Customer
pointing onto the given User object.

>>> from django.contrib.auth import get_user_model


>>> user = get_user_model().create(username='bobo')
>>> print user.customer.salutation
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "django/db/models/fields/related.py", line 206, in __get__
self.related.get_accessor_name()))
DoesNotExist: User has no customer.

This can happen for User objects added manually or by other Django applications.
During database queries, django-SHOP always performs and INNER JOIN between the customer and the user table.
Therefore it performs better to query the User via the Customer object, rather than vice versa.

Anonymous Users and Visiting Customers

Most requests to our site will be of anonymous nature. They will not send a cookie containing a session-Id to the client,
and the server will not allocate a session bucket. The middleware adds a VisitingCustomer object associated
with an AnonymousUser object to the request. These two objects are not stored inside the database.
Whenever such an anonymous user/visiting customer adds his first item to the cart, django-SHOP instantiates a user
object in the database and associates it with a customer object. Such a customer is considered as “unregistered” and
invoking customer.is_authenticated() will return False; here its associated User model is inactive and
has an unusable password.

5.1. Customer Model 31


djangoSHOP, Release 1.0.2

Guests and Registered Customers

On the way to the checkout, a customer must declare himself, whether to continue as guest, to sign in using an
existing account or to register himself with a new account. In the former case (customer wishes to proceed as
guest), the User` object remains as it is: Inactive and with an unusable password.
In the second case, the visitor signs in using Django's default authentication
backends. Here the cart's content is merged with the already existing cart of
that user object. In the latter case (customer registers himself), the user
object is recycled and becomes an active Django ``User object, with a password and an
email address.

Obviate Criticism

Some may argue that adding unregistered and guest customers to the user table is an anti-pattern or hack. So, what are
the alternatives?
We could keep the cart of anonymous customers in the session store. This was the procedure used until django-SHOP
version 0.2. It however required to keep two different models of the cart, one session based and one relational. Not
very practical, specially if the cart model should be overridable by the merchant’s own implementation.
We could associate each cart models with a session id. This would require an additional field which would be NULL
for authenticated customers. While possible in theory, it would require a lot of code which distinguishes between
anonymous and authenticated customers. Since the aim of this software is to remain simple, this idea was dismissed.
We could keep the primary key of each cart in the session associated with an anonymous user/customer. But this would
it make very hard to find expired carts, because we would have to iterate over all carts and for each cart we would have
to iterate over all sessions to check if the primary keys matches. Remember, there is no such thing as an OUTER JOIN
between sessions and database tables.
We could create a customer object which is independent of the user. Hence instead of having a
OneToOneField(AUTH_USER_MODEL) in model Customer, we’d have this 1:1 relation with a nullable for-
eign key. This would require an additional field to store the session id in the customer model. It also would require
an additional email field, if we wanted guest customers to remain anonymous users – what they actually are, since
they can’t sign in. Apart from field duplication, this approach would also require some code to distinguish between
unrecognized, guest and registered customers. In addition to that, the administration backend would require two
distinguished views, one for the customer model and one for the user model.

5.1.3 Authenticating against the Email Address

Nowadays it is quite common, to use the email address for authenticating, rather than an explicit account identifier.
This in Django is not possible without replacing the built-in User model. Since for an e-commerce site this authen-
tication variant is rather important, django-SHOP is shipped with an optional drop-in replacement for the built-in
User model.
This User model is almost identical to the existing User model as found in django.contrib.auth.models.
py. The difference is that it uses the field email rather than username for looking up the credentials. To activate
this alternative User model, add that alternative authentication app to the project’s settings.py:

INSTALLED_APPS = (
'django.contrib.auth',
'email_auth',
...
)

AUTH_USER_MODEL = 'email_auth.User'

32 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Note: This alternative User model uses the same database table as the Django authentication would, namely
auth_user. It is even field-compatible with the built-in model and hence can be added later to an existing Django
project.

Caveat when using this alternative User model

The savvy reader may have noticed that in email_auth.models.User, the email field is not declared as unique.
This by the way causes Django to complain during startup with:

WARNINGS:
email_auth.User: (auth.W004) 'User.email' is named as the 'USERNAME_FIELD', but it is
˓→not unique.

HINT: Ensure that your authentication backend(s) can handle non-unique usernames.

This warning can be silenced by adding SILENCED_SYSTEM_CHECKS = ['auth.W004'] to the project’s


settings.py.
The reason for this is twofold:
First, Django’s default User model has no unique constraint on the email field, so email_auth remains more
compatible.
Second, the uniqueness is only required for users which actually can sign in. Guest users on the other hand can not
sign in, but they may return someday. By having a unique email field, the Django application email_auth would
lock them out and guests would be allowed to buy only once, but not a second time – something we certainly do not
want!
Therefore django-SHOP offers two configurable options:
• Customers can declare themselves as guests, each time they buy something. This is the default setting, but
causes to have non-unique email addresses in the database.
• Customer can declare themselves as guests the first time they buys something. If someday they return to the site
a buy a second time, they will be recognized as returning customer and must use a form to reset their password.
This configuration is activated by setting SHOP_GUEST_IS_ACTIVE_USER = True. It further allows us,
to set a unique constraint on the email field.

Note: The email field from Django’s built-in User model has a max-length of 75 characters. This is enough for
most use-cases but violates RFC-5321, which requires 254 characters. The alternative implementation uses the correct
max-length.

Administration of Users and Customers

By keeping the Customer and the User model tight together, it is possible to reuse the Django’s administration
backend for both of them. All we have to do is to import and register the customer backend inside the project’s
admin.py:

from django.contrib import admin


from shop.admin.customer import CustomerProxy, CustomerAdmin

admin.site.register(CustomerProxy, CustomerAdmin)

5.1. Customer Model 33


djangoSHOP, Release 1.0.2

This administration backend recycles the built-in django.contrib.auth.admin.UserAdmin, and enriches it


by adding the Customer model as a StackedInlineAdmin on top of the detail page. By doing so, we can edit the
customer and user fields on the same page.

5.1.4 Summary for Customer to User mapping

This table summarizes to possible mappings between a Django User model1 and the Shop’s Customer model:

Shop’s Cus- Django’s User Model Active Session


tomer Model
VisitingCustomerAnonymousUser object No
object
Unrecognized Inactive User object with unusable password Yes, but not logged in
Customer
Customer Inactive User with valid email address and unusable pass- Yes, but not logged in
recognized as word
guest2
Customer Active User with valid email address and unusable, but re- Yes, but not logged in
recognized as setable password
guest3
Registered Active User with valid email address, known password, op- Yes, logged in using Django’s
Customer tional salutation, first- and last names, and more authentication backend

Manage Customers

Django-SHOP is shipped with a special management command which informs the merchant about the state of cus-
tomers. In the project’s folder, invoke on the command line:

./manage.py shop_customers
Customers in this shop: total=20482, anonymous=17418, expired=10111, active=1068,
˓→guests=1997, registered=1067, staff=5.

Read these numbers as:


• Anonymous customers are those which added at least one item to the cart, but never proceeded to checkout.
• Expired customers are the subset of the anonymous customers, whose session already expired.
• The difference between guest and registered customers is explained in the above table.

Delete expired customers

By invoking on the command line:

./manage.py shop_customers --delete-expired

This removes all anonymous/unregistered customers and their associated user entities from the database, whose session
expired. This command may be used to reduce the database storage requirements.
1 or any alternative User model, as set by AUTH_USER_MODEL.
2 if setting SHOP_GUEST_IS_ACTIVE_USER = False (the default).
3 if setting SHOP_GUEST_IS_ACTIVE_USER = True.

34 Chapter 5. Reference
djangoSHOP, Release 1.0.2

5.2 Deferred Model Pattern

Until django-SHOP version 0.2, there were abstract and concrete and models: BaseProduct and Product,
BaseCart and Cart, BaseCartItem and CartItem, BaseOrder and Order and finally, BaseOrderItem
and OrderItem.
The concrete models were stored in shop.models, whereas abstract models were stored in shop.
models_bases. This was quite confusing and made it difficult to find the right model definition whenever one
had to access the definition of one of the models. Additionally, if someone wanted to subclass a model, he had
to use a configuration directive, say PRODUCT_MODEL, ORDER_MODEL, ORDER_MODEL_ITEM from the projects
settings.py.
This made configuration quite complicate and causes other drawbacks:
• Unless all models have been overridden, the native ones appeared in the administration backend below the cate-
gory Shop, while the customized ones appeared under the given project’s name. To merchants, this inconsistency
in the backend was quite difficult to explain.
• In the past, mixing subclassed with native models caused many issues with circular dependencies.
Therefore in django-SHOP, since version 0.9 all concrete models, Product, Order, OrderItem, Cart,
CartItem have been removed. These model definitions now all are abstract and named BaseProduct,
BaseOrder, BaseOrderItem, etc. They all have been moved into the folder shop/models/, because that’s the
location a programmer expects them.

5.2.1 Materializing Models

Materializing such an abstract base model, means to create a concrete model with an associated database table. This
model creation is performed in the concrete project implementing the shop; it must be done for each base model in the
shop software.
For instance, materialize the cart by using this code snippet inside our own shop’s models/shopmodels.py files:

from shop.models import cart

class Cart(cart.BaseCart):
my_extra_field = ...

class Meta:
app_label = 'my_shop'

class CartItem(cart.BaseCartItem):
other_field = ...

class Meta:
app_label = 'my_shop'

Of course, we can add as many extra model fields to this concrete cart model, as we wish. All shop models, now
are managed through our project instance. This means that the models Cart, Order, etc. are now managed by the
common database migrations tools, such as ./manage.py makemigration my_shop and ./manage.py
migrate my_shop. This also means that these models, in the Django admin backend, are visible under my_shop.

Use the default Models

Often we don’t need extra fields, hence the abstract shop base model is enough. Then, materializing the models can
be done using some convenience classes as found in shop/models/defaults. We can simply import them into

5.2. Deferred Model Pattern 35


djangoSHOP, Release 1.0.2

models.py or models/__init__.py in our own shop project:

from shop.models.defaults.cart import Cart # nopyflakes


from shop.models.defaults.cart_item import CartItem # nopyflakes

Note: The comment nopyflakes has been added to suppress warnings, since these classes arern’t used anywhere
in models.py.

All the configuration settings from django-SHOP <0.9: PRODUCT_MODEL, ORDER_MODEL,


ORDER_MODEL_ITEM, etc. are not required anymore and can safely be removed from our settings.py.

5.2.2 Accessing the deferred models

Since models in django-SHOP are yet unknown during instantiation, one has to access their materialized instance
using the lazy object pattern. For instance in order to access the Cart, use:

from shop.models.cart import CartModel

def my_view(request):
cart = CartModel.objects.get_from_request(request)
cart.items.all() # contains the queryset for all items in the cart

Here CartModel is a lazy object resolved during runtime and pointing on the materialized, or, to say it in other
words, real Cart model.

5.2.3 Technical Internals

Mapping of Foreign Keys

One might argue, that this can’t work, since foreign keys must refer to a real model, not to abstract ones! Therefore
one can not add a field ForeignKey, OneToOneField or ManyToManyField which refers an abstract model
in the django-SHOP project. But relations are fundamental for a properly working software. Imagine a CartItem
without a foreign relation to Cart.
Fortunately there is a neat trick to solve this problem. By deferring the mapping onto a real model, instead of using a
real ForeignKey, one can use a special “lazy” field, declaring a relation with an abstract model. Now, whenever the
models are “materialized”, then these abstract relations are converted into real foreign keys. The only drawback for
this solution is, that one may derive from an abstract model only once, but for django-SHOP that’s a non-issue and
doesn’t differ from the current situation, where one can subclass BaseCart only once anyway.
Therefore, when using this deferred model pattern, instead of using models.ForeignKey, models.
OneToOneField or models.ManyToManyField, use the special fields deferred.ForeignKey,
deferred.OneToOneField and deferred.ManyToManyField. When Django materializes the model,
these deferred fields are resolved into real foreign keys.

Accessing the materialized model

While programming with abstract model classes, sometimes they must access their model manager or their concrete
model definition. A query such as BaseCartItem.objects.filter(cart=cart) therefore can not func-
tion and will throw an exception. To facilitate this, the deferred model’s metaclasses adds an additional member
_materialized_model to their base class, while building the model class. This model class then can be accessed
through lazy evaluation, using CartModel, CartItemModel, OrderModel, OrderItemModel, etc.

36 Chapter 5. Reference
djangoSHOP, Release 1.0.2

5.3 Money Types

In earlier versions of django-SHOP, amounts relating to money were kept inside a Decimal type and stored in the
database model using a DecimalField. In shop installations with only one available currency, this wasn’t a major
issue, because the currency symbol could be hard-coded anywhere on the site.
However, for sites offering pricing information in more than one currency, this caused major problems. When we
needed to perform calculations with amounts that have an associated currency, it is very common to make mistakes by
mixing different currencies. It also is common to perform incorrect conversions that generate wrong results. Python
doesn’t allow developers to associate a specific decimal value with a unit.
Starting with version 0.9, django-SHOP is shipped with a special factory class:

5.3.1 MoneyMaker

This class can not be instantiated, but is a factory for building a money type with an associated currency. Internally
it uses the well established Decimal type to keep track of the amount. Additionally, it restricts operations on the
current Money type. For instance, we can’t sum up Dollars with Euros. We also can’t multiply two currencies with
each other.

Not a Number

In special occurrences we’d rather want to specify “no amount” rather than an amount of 0.00 (zero). This can be
useful for free samples, or when an item currently is not available. The Decimal type denotes a kind of special value a
NaN – for “Not a Number”. Our Money type also knows about this special value, and when rendered, C - or $ -``
is printed out.
Declaring a Money object without a value, say m = Money() creates such a special value. The big difference as
for the Decimal type is that when adding or subtracting a NaN to a valid value, it is considered zero, rather than
changing the result of this operation to NaN as well.
It also allows us to multiply a Money amount with None. The result of this operation is NaN.

Create a Money type

>>> from shop.money import MoneyMaker


>>> Money = MoneyMaker()
>>> print(Money('1.99'))
C 1.99

>>> print(Money('1.55') + Money('8'))


C 9.55

>>> print(Money)
<class 'shop.money.money_maker.MoneyInEUR'>

>>> Yen = MoneyMaker('JPY')


>>> print(Yen('1234.5678'))
¥ 1,235

>>> print(Money('100') + Yen('1000'))


ValueError: Can not add/substract money in different currencies.

5.3. Money Types 37


djangoSHOP, Release 1.0.2

How does this work?


By calling MoneyMaker() a type accepting amounts in the default currency is created. The default currency can
be changed in settings.py with SHOP_DEFAULT_CURRENCY = 'USD', using one of the official ISO-4217
currency codes.
Alternatively, we can create our own Money type, for instance Yen.

Formatting Money

When the amount of a money type is printed or forced to text using str(price), it is prefixed by the currency
symbol. This is fine, when working with only a few currencies. However, some symbols are ambiguous, for instance
Canadian, Australian and US Dollars, which all use the “$” symbol.
With the setting SHOP_MONEY_FORMAT we can style how money is going to be printed out. This setting defaults to
{symbol} {amount}. The following format strings are allowed:
• {symbol}: The short symbol for a currency, for instance $, £, C, ¥, etc.
• {code}: The international currency code, for instance USD, GBP, EUR, JPY, etc.
• {currency}: The spoken currency description, for instance “US Dollar”, “Pound Sterling”, etc.
• {amount}: The amount, unlocalized.
Thus, if we prefer to print 9.98 US Dollar, then we should set {amount} {currency} as the formatting
string.

5.3.2 Localizing Money

Depending on our current locale setting, amounts are printed using a localized format.

5.3.3 Money Database Fields

Money can be stored in the database, keeping the currency information together with the field type. Internally, the
database uses the Decimal type, but such a field knows its currency and will return an amount as MoneyIn... type.
This prevents implicit, but accidental currency conversions.
In our database model, declare a field as:

class Product(models.Model):
...
unit_price = MoneyField(currency='GBP')

This field stores its amounts as British Pounds and returns them typed as MoneyInGBP. If the currency argument
is omitted, then the default currency is used.

5.3.4 Money Representation in JSON

An additional REST SerializerField has been added to convert amounts into JSON strings. When writing REST
serializers, use:

38 Chapter 5. Reference
djangoSHOP, Release 1.0.2

from rest_framework import serializers


from shop.money.rest import MoneyField

class SomeSerializer(serializers.ModelSerializer):
price = MoneyField()

The default REST behavior serializes Decimal types as floats. This is fine if we want to do some computations in the
browser using JavaScript. However, then the currency information is lost and must be re-added somehow to the output
strings. It also is a bad idea to do commercial calculations using floats, yet JavaScript does not offer any Decimal-like
type. It therefore is recommended to always do the finance arithmetic on the server and transfer amount information
using JSON strings.

5.4 Product Models

Products can vary wildly, and modeling them is not always trivial. Some products are salable in pieces, while others
are continues. Trying to define a set of product models, capable for describing all such scenarios is impossible –

5.4.1 Describe Products by customizing the Model

DjangoSHOP requires to describe products instead of prescribing prefabricated models.


All in all, the merchant always knows best how to describe his products!

E-commerce solutions, claiming to be plug-and-play, usually use one of these (anti-)patterns

Either, they offer a field for every possible variation, or they use the Entity Attribute Value (EAV) pattern to add meta-
data for each of our models. This at a first glance seems to be easy. But both approaches are unwieldy and have serious
drawbacks. They both apply a different “physical schema” – the way data is stored, rather than a “logical schema” –
the way users and applications require that data. As soon as we have to combine our e-commerce solution with some
Enterprise Resource Planning (ERP) software, additional back-and-forward conversion routines have to be added.

In django-SHOP, the physical representation of a product always maps to its logical

Django-SHOP’s approach to this problem is to have a minimal set of models. These abstract models are stubs
providing to subclass the physical models. Hence the logical representation of the product conforms to their physical
one. Moreover, it is even possible to represent various types of products by subclassing polymorphically from an
abstract base model. Thanks to Django’s Object Relational Mapper, modeling the logical representation for a set of
products, together with an administration backend, becomes almost effortless.
Therefore the base class to model a product is a stub which contains only these three fields:
The timestamps for created_at and updated_at; these are self-explanatory.
A boolean field active, used to signalize the products availability.
The attentive reader may wonder, why there not even fields for the most basic requirements of each sellable article,
there is no product name, no price field and no product code.
The reason for this is, that django-SHOP does not impose any fields, which might require a different implementation
for the merchants use case. However, for a sellable commodity some information is fundamental and required. But its
up to him how to implement these fields:

5.4. Product Models 39


djangoSHOP, Release 1.0.2

The product’s name must be implemented as a model field or as a property method, but both must be declared as
product_name. Use a method implementation for composed and translatable names, otherwise use a database
model field with that name.
The product’s price must be implemented as a method declared as get_price(request) which accepts the re-
quest object. This gives the merchant the ability to vary the price and/or its currency depending on the geographic
location, the customers login status, the browsers user-agent, or whatever else.
An optional, but highly recommended field is the products item number, declared as product_code. It shall return
a unique and language independent identifier for each product, to be identifiable. In most cases the product code is
implemented by the product model itself, but in some circumstances it may be implemented by the product’s variant.
The product model SmartPhone, as referenced in the demo code, is one such example.
The example section of django-SHOP contains a few models which can be copied and adopted to the specific needs
of the merchants products. Let’s have a look at a few use-cases:

5.4.2 Case study: Smart-Phones

There are many smart-phone models with different equipment. All the features are the same, except for the built-in
storage. How shall we describe such a model?
In that model, the product’s name shall not be translatable, not even on a multi-lingual site, since smart-phones have
international names used everywhere. Smart-phones models have dimensions, an operating system, a display type and
other features.
But smart-phone have different equipment, namely the built-in storage, and depending on that, they have different
prices and a unique product code. Therefore our product models consists of two classes, the generic smart phone
model and the concrete flavor of that model.
Therefore we would model our smart-phones using a database model similar to the following one:
from shop.models.product import BaseProductManager, BaseProduct
from shop.money import Money

class SmartPhoneModel(BaseProduct):
product_name = models.CharField(
_("Product Name"),
max_length=255,
)

slug = models.SlugField(_("Slug"))

description = HTMLField(
help_text=_("Detailed description."),
)

manufacturer = models.ForeignKey(
Manufacturer,
verbose_name=_("Manufacturer"),
)

screen_size = models.DecimalField(
_("Screen size"),
max_digits=4,
decimal_places=2,
)
# other fields to map the specification sheet

(continues on next page)

40 Chapter 5. Reference
djangoSHOP, Release 1.0.2

(continued from previous page)


objects = BaseProductManager()

lookup_fields = ('product_name__icontains',)

def get_price(request):
aggregate = self.variants.aggregate(models.Min('unit_price'))
return Money(aggregate['unit_price__min'])

class SmartPhoneVariant(models.Model):
product_model = models.ForeignKey(
SmartPhoneModel,
related_name='variants',
)

product_code = models.CharField(
_("Product code"),
max_length=255,
unique=True,
)

unit_price = MoneyField(_("Unit price"))

storage = models.PositiveIntegerField(_("Internal Storage"))

Lets go into the details of these classes. The model fields are self-explanatory. Something to note here is, that each
product requires a field product_name. This alternatively can also be implemented as a translatable field using
django-parler.
Another mandatory attribute for each product is the ProductManager class. It must inherit from
BaseProductManager, and adds some methods to generate some special querysets.
Finally, the attribute lookup_fields contains a list or tuple of lookup fields. These are required by the administra-
tion backend, and used when the site editor has to search for certain products. Since the framework does not impose
which fields are used to distinguish between products, we must give some hints.
Each product also requires a method implemented as get_price(request). This must return the unit price using
one of the available Money Types.

Add multilingual support

Adding multilingual support to an existing product is quite easy and straight forward. To achieve this django-SHOP
uses the app django-parler which provides Django model translations without nasty hacks. All we have to do, is to
replace the ProductManager with one capable of handling translations:
class ProductQuerySet(TranslatableQuerySet, PolymorphicQuerySet):
pass

class ProductManager(BaseProductManager, TranslatableManager):


queryset_class = ProductQuerySet

The next step is to locate the model fields, which shall be available in different languages. In our use-case thats only
the product’s description:
class SmartPhoneModel(BaseProduct, TranslatableModel):
# other field remain unchanged
description = TranslatedField()
(continues on next page)

5.4. Product Models 41


djangoSHOP, Release 1.0.2

(continued from previous page)

class ProductTranslation(TranslatedFieldsModel):
master = models.ForeignKey(
SmartPhoneModel,
related_name='translations',
null=True,
)

description = HTMLField(
help_text=_("Some more detailed description."),
)

class Meta:
unique_together = [('language_code', 'master')]

This simple change now allows us to offer the shop’s assortment in different natural languages.

Add Polymorphic Support

If besides smart phones we also want to sell cables, pipes or smart cards, we must split our product models into
a common- and a specialized part. That said, we must separate the information every product requires from the
information specific to a certain product type. Say, in addition to smart phones, we also want to sell smart cards. First
we declare a generic Product model, which is a common base class of both, SmartPhone and SmartCard:

class Product(BaseProduct, TranslatableModel):


product_name = models.CharField(
_("Product Name"),
max_length=255,
)

slug = models.SlugField(
_("Slug"),
unique=True,
)

description = TranslatedField()

objects = ProductManager()
lookup_fields = ['product_name__icontains']

Next we only add the product specific attributes to the class models derived from Product:

class SmartPhoneModel(Product):
manufacturer = models.ForeignKey(
Manufacturer,
verbose_name=_("Manufacturer"),
)

screen_size = models.DecimalField(
_("Screen size"),
max_digits=4,
decimal_places=2,
)

battery_type = models.PositiveSmallIntegerField(
(continues on next page)

42 Chapter 5. Reference
djangoSHOP, Release 1.0.2

(continued from previous page)


_("Battery type"),
choices=BATTERY_TYPES,
)

battery_capacity = models.PositiveIntegerField(
help_text=_("Battery capacity in mAh"),
)

ram_storage = models.PositiveIntegerField(
help_text=_("RAM storage in MB"),
)
# and many more attributes as found on the data sheet

class SmartPhone(models.Model):
product_model = models.ForeignKey(SmartPhoneModel)
product_code = models.CharField(
_("Product code"),
max_length=255,
unique=True,
)

unit_price = MoneyField(_("Unit price"))

storage = models.PositiveIntegerField(_("Internal Storage"))

class SmartCard(Product):
product_code = models.CharField(
_("Product code"),
max_length=255,
unique=True,
)

storage = models.PositiveIntegerField(help_text=_("Storage capacity in GB"))

unit_price = MoneyField(_("Unit price"))

CARD_TYPE = [2 * ('{}{}'.format(s, t),)


for t in ('SD', 'SDXC', 'SDHC', 'SDHC II') for s in ('', 'micro ')]
card_type = models.CharField(
_("Card Type"),
choices=CARD_TYPE,
max_length=15,
)

SPEED = [(str(s), "{} MB/s".format(s))


for s in (4, 20, 30, 40, 48, 80, 95, 280)]
speed = models.CharField(
_("Transfer Speed"),
choices=SPEED,
max_length=8,
)

If MyShop would sell the iPhone5 with 16GB and 32GB storage as independent products, then we could unify the
classes SmartPhoneModel and SmartPhone and move the attributes product_code and unit_price into
the class Product. This would simplify some programming aspects, but would require the merchant to add a lot of
information twice. Therefore we remain with the model layout presented here.

5.4. Product Models 43


djangoSHOP, Release 1.0.2

5.4.3 Caveat using a ManyToManyField with existing models

Sometimes we may need to use a ManyToManyField for models which are handled by other apps in our project.
This for example could be an attribute files referring the model filer.FilerFileField from the library
django-filer. Here Django would try to create a mapping table, where the foreign key to our product model can not be
resolved properly, because while bootstrapping the application, our Product model is still considered to be deferred.
Therefore, we have to create our own mapping model and refer to it using the through parameter, as shown in this
example:

from six import with_metaclass


from django.db import models
from filer.fields.file import FilerFileField
from shop.models import deferred
from shop.models.product import BaseProductManager, BaseProduct

class ProductFile(with_metaclass(deferred.ForeignKeyBuilder, models.Model)):


file = FilerFileField()
product = deferred.ForeignKey(BaseProduct)

class Product(BaseProduct):
# other fields
files = models.ManyToManyField('filer.File', through=ProductFile)

objects = ProductManager()

Note: Do not use this example for creating a many-to-many field to FilerImageField. Instead use shop.
models.related.BaseProductImage which is a base class for this kind of mapping. Just import and materi-
alize it, in your own project.

5.5 Catalog

The catalog presumably is that part, where customers of our e-commerce site spend the most time. Often it even makes
sense, to start the Catalog List View on the main landing page.
In this documentation we presume that categories of products are built up using specially tagged CMS pages in
combination with a django-CMS apphook. This works perfectly well for most implementation, but some sites may
require categories implemented independently of the CMS.
Using an external django-SHOP plugin for managing categories is a very conceivable solution, and we will see
separate implementations for this feature request. Using such an external category plugin can make sense, if this e-
commerce site requires hundreds of hierarchical levels and/or these categories require a set of attributes which are not
available in CMS pages. If you are going to use externally implemented categories, please refer to their documentation,
since here we proceed using CMS pages as categories.
A nice aspect of django-SHOP is, that it doesn’t require the programmer to write any special Django Views in order
to render the catalog. Instead all merchant dependent business logic goes into a serializer, which in this documentation
is referred as ProductSerializer.

5.5.1 Catalog List View

In this documentation, the catalog list view is implemented as a django-CMS page. Depending on whether the e-
commerce aspect of that site is the most prominent part or just a niche of the CMS, select an appropriate location in

44 Chapter 5. Reference
djangoSHOP, Release 1.0.2

the page tree and create a new page. This will become the root of our catalog list.
But first we must Create the CatalogListApp.

Create the CatalogListApp

To retrieve a list of product models, the Catalog List View requires a django-CMS apphook. This CatalogListApp
must be added into a file named cms_apps.py and located in the root folder of the merchant’s project:

Listing 1: myshop/cms_apps.py
from cms.apphook_pool import apphook_pool
from shop.cms_apphooks import CatalogListCMSApp

class CatalogListApp(CatalogListCMSApp):
def get_urls(self, page=None, language=None, **kwargs):
return ['myshop.urls.products']

apphook_pool.register(CatalogListApp)

as all apphooks, it requires a file defining its urlpatterns:

Listing 2: myshop/urls/products.py
from django.conf.urls import url
from shop.views.catalog import ProductListView

urlpatterns = [
url(r'^$', ProductListView.as_view()),
# other patterns
]

By default the ProductListView renders the catalog list of products assigned to the current CMS page. In this
example, only model attributes for fields declared in the default ProductSummarySerializer are available in
the render context for the used CMS template, as well as for a representation in JSON suitable for client side rendered
list views. This allows us to reuse this Django View (ProductListView) whenever the catalog list switches into
infinite scroll mode, where it only requires the product’s summary, composed of plain old JavaScript objects.

Customized Product Serializer

In case we need Additional Product Serializer Fields, let’s add them to this class using the serializer fields from the
Django RESTFramework library. This can be useful for product serializers which shall provide more information on
our catalog list view.
For this customized serializer, we normally only require a few attributes from our model, therefore we can write it as:

from shop.serializers.bases import ProductSerializer

class CustomizedProductSerializer(ProductSerializer):
class Meta:
model = Product
fields = [all-the-fields-required-for-the-list-view]

Additionally, we have to rewrite the URL pattern from above as:

5.5. Catalog 45
djangoSHOP, Release 1.0.2

from django.conf.urls import url


from shop.views.catalog import ProductListView
from myshop.serializers import CustomizedProductSerializer

urlpatterns = [
url(ProductListView.as_view(
serializer_class=CustomizedProductSerializer,
)),
# other patterns
]

Here the CustomizedProductSerializer is used to create a more specialized representation of our product
model.

Add the Catalog to the CMS

In the page list editor of django-CMS, create a new page at an appropriate location of the page tree. As the page title
and slug we should use something describing our product catalog in a way, both meaningful to the customers as well
as to search engines.
Next, we change into Advanced Settings mode.
As a template we use one with a big placeholder, since it must display our list of products.
As Application, select “Catalog List”. This selects the apphook we created in the previous section.
Then we save the page, change into Structure mode and locate the placeholder named Main Content. Add a Con-
tainer plugin, followed by a Row and then a Column plugin. As the child of this column choose the Catalog List View
plugin from section Shop.
Finally we publish the page. If we have assigned products to that CMS page, they should be rendered now.

5.5.2 Catalog Detail View

The product’s detail pages are the only ones we typically do not control with django-CMS placeholders. This is
because we often have thousands of products and creating a CMS page for each of them, would be kind of overkill. It
only makes sense for shops selling up to a dozen of different products.
Therefore, the template used to render the products’s detail view is selected automatically by the
ProductRetrieveView1 following these rules:
• look for a template named <myshop>/catalog/<product-model-name>-detail.html23 , other-
wise
• look for a template named <myshop>/catalog/product-detail.html2 , otherwise
• use the template shop/catalog/product-detail.html.

Use CMS Placeholders on Detail View

If we require CMS functionality for each product’s detail page, its quite simple to achieve. To the class imple-
menting our product model, add a django-CMS Placeholder field named placeholder. Then add the templatetag
{% render_placeholder product.placeholder %} to the template implementing the detail view of our
1 This is the View class responsible for rendering the product’s detail view.
2 <myshop> is the app label of the project in lowercase.
3 <product-model-name> is the class name of the product model in lowercase.

46 Chapter 5. Reference
djangoSHOP, Release 1.0.2

product. This placeholder then shall be used to add arbitrary content to the product’s detail page. This for instance
can be an additional text paragraphs, some images, a carousel or whatever is available from the django-CMS plugin
system.

Route requests on Detail View

The ProductsListApp, which we previously have registered into django-CMS, is able to route requests on all of
its sub-URLs. This is done by expanding the current list of urlpatterns:

Listing 3: myshop/urls/products.py
from django.conf.urls import url
from shop.views.catalog import ProductRetrieveView

urlpatterns = [
# previous patterns
url(r'^(?P<slug>[\w-]+)$', ProductRetrieveView.as_view()),
# more patterns
]

If we need additional business logic regarding our product, we can create a customized serializer class, named for
instance CustomizedProductDetailSerializer. This class then may access the various attributes of our
product model, recombine them and/or merge them into a serializable representation, as described in Customized
Product Serializer.
Additionally, we have to rewrite the URL pattern from above as:

from myshop.serializers import CustomizedProductDetailSerializer

urlpatterns = [
# previous patterns
url(r'^(?P<slug>[\w-]+)$', ProductRetrieveView.as_view(
serializer_class=CustomizedProductDetailSerializer,
)),
# more patterns
]

Additional Product Serializer Fields

Sometimes such a serializer field shall return a HTML snippet; this for instance is required for image source (<img
src="..." />) tags, which must thumbnailed by the server when rendered using the appropriate templatetags from
the easythumbnail library. For these use cases add a field of type media = SerializerMethodField() with
an appropriate method get_media() to our serializer class. This method then may forward the given product to a
the built-in renderer:

class ProductDetailSerializer(BaseProductDetailSerializer):
# other attributes

def get_media(self, product):


return self.render_html(product, 'media')

This HTML renderer method looks up for a template following these rules:

5.5. Catalog 47
djangoSHOP, Release 1.0.2

• look for a template named <myshop>/product/catalog-<product-model-name>-<second-argument>.


html456 , otherwise
• look for a template named <myshop>/product/catalog-product-<second-argument>.
html46 , otherwise
• use the template shop/product/catalog-product-<second-argument>.html6 .

Emulate Categories

Since we want to use CMS pages to emulate categories, the product model must declare a relationship between the
CMS pages and itself. This usually is done by adding a Many-to-Many field named cms_pages to our Product
model.
As we work with deferred models, we can not use the mapping table, which normally is generated automatically for
Many-to-Many fields by the Django framework. Instead, this mapping table must be created manually and referenced
using the though parameter, when declaring the field:

from shop.models.product import BaseProductManager, BaseProduct


from shop.models.related import BaseProductPage

class ProductPage(BaseProductPage):
"""Materialize many-to-many relation with CMS pages"""

class Product(BaseProduct):
# other model fields
cms_pages = models.ManyToManyField(
'cms.Page',
through=ProductPage
)

objects = ProductManager()

In this example the class ProductPage is responsible for storing the mapping information between our Product
objects and the CMS pages.

Admin Integration

To simplify the declaration of the admin backend used to manage our Product model, django-SHOP is shipped with
a special mixin class, which shall be added to the product’s admin class:

from django.contrib import admin


from shop.admin.product import CMSPageAsCategoryMixin
from myshop.models import Product

@admin.register(Product)
class ProductAdmin(CMSPageAsCategoryMixin, admin.ModelAdmin):
fields = [
'product_name', 'slug', 'product_code',
'unit_price', 'active', 'description'
]
# other admin declarations

4 <myshop> is the app label of the project in lowercase.


5 <product-model-name> is the class name of the product model in lowercase.
6 <field-name> is the attribute name of the just declared field in lowercase.

48 Chapter 5. Reference
djangoSHOP, Release 1.0.2

This then adds a horizontal filter widget to the product models. Here the merchant must select each CMS page, where
the currently edited product shall appear on.
If we are using the method render_html() to render HTML snippets, these are cached by django-SHOP, if
caching is configured and enabled for that project. Caching these snippets is highly recommended and gives a notice-
able performance boost, specially while rendering catalog list views.
Since we would have to wait until they expire naturally by reaching their expire time, django-SHOP offers a mixin
class to be added to the Product admin class, to expire all HTML snippets of a product altogether, whenever a prod-
uct in saved in the backend. Simply add shop.admin.product.InvalidateProductCacheMixin to the
ProductAdmin class described above.

Note: Due to the way keys are handled in many caching systems, the InvalidateProductCacheMixin only
makes sense if used in combination with the redis_cache backend.

5.5.3 DEPRECATED DOCS

5.5.4 Connect the Serializers with the View classes

Now that we declared the serializers for the product’s list- and detail view, the final step is to access them through a
CMS page. Remember, since we’ve chosen to use CMS pages as categories, we had to set a special django-CMS
apphook:

Listing 4: myshop/cms_apps.py
1 from cms.app_base import CMSApp
2 from cms.apphook_pool import apphook_pool
3

4 class ProductsListApp(CMSApp):
5 name = _("Products List")
6 urls = ['myshop.urls.products']
7

8 apphook_pool.register(ProductsListApp)

This apphook points onto a list of boilerplate code containing these urlpattern:

Listing 5: myshop/urls/products.py
1 from django.conf.urls import url
2 from rest_framework.settings import api_settings
3 from shop.rest.filters import CMSPagesFilterBackend
4 from shop.rest.serializers import AddToCartSerializer
5 from shop.views.catalog import (CMSPageProductListView,
6 ProductRetrieveView, AddToCartView)
7

8 urlpatterns = [
9 url(r'^$', CMSPageProductListView.as_view(
10 serializer_class=ProductSummarySerializer,
11 )),
12 url(r'^(?P<slug>[\w-]+)$', ProductRetrieveView.as_view(
13 serializer_class=ProductDetailSerializer
14 )),
15 url(r'^(?P<slug>[\w-]+)/add-to-cart', AddToCartView.as_view()),
16 ]

5.5. Catalog 49
djangoSHOP, Release 1.0.2

These URL patterns connect the product serializers with the catalog views in order to assign them an endpoint. Addi-
tional note: The filter class CMSPagesFilterBackend is used to restrict products to specific CMS pages, hence it
can be regarded as the product categoriser.

5.6 Full Text Search

How should a customer find the product he desires in a more or less unstructured collection of countless products?
Hierarchical navigation often doesn’t work and takes too much time. Thanks to the way we use the Internet today,
most site visitors expect one central search field in, or nearby the main navigation bar of a site.

5.6.1 Search Engine API

In Django the most popular API for full-text search is Haystack. While other indexing backends, such as Solr and
Whoosh might work as well, the best results have been achieved with Elasticsearch. Therefore this documentation
focuses exclusively on Elasticsearch. And since in django-SHOP every programming interface uses REST, search is
no exception here. Fortunately there is a project named drf-haystack, which “restifies” our search results, if we use a
special serializer class.
In this document we assume that the merchant only wants to index his products, but not any arbitrary content, such
as for example the terms and condition, as found outside django-SHOP, but inside django-CMS. The latter would
however be perfectly feasible.

Configuration

Install the Elasticsearch binary. Currently Haystack only supports versions smaller than 2. Then start the service in
daemon mode:

./path/to/elasticsearch-version/bin/elasticsearch -d

Check if the server answers on HTTP requests. Pointing a browser onto port http://localhost:9200/ should return
something similar to this:

$ curl http://localhost:9200/
{
"status" : 200,
"name" : "Ape-X",
"cluster_name" : "elasticsearch",
"version" : {
...
},
}

In settings.py, check that 'haystack' has been added to INSTALLED_APPS and connects the application
server with the Elasticsearch database:

HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://localhost:9200/',
'INDEX_NAME': 'myshop-default',
},
}

50 Chapter 5. Reference
djangoSHOP, Release 1.0.2

In case we need indices for different natural languages on our site, we shall add the non-default languages to this
Python dictionary using a different INDEX_NAME for each of them.
Finally configure the site, so that search queries are routed to the correct index using the currently active natural
language:

HAYSTACK_ROUTERS = ('shop.search.routers.LanguageRouter',)

5.6.2 Indexing the Products

Before we start to search for something, we first must populate its indices. In Haystack one can create more than one
kind of index for each item being added to the search database.
Each product type requires its individual indexing class. Note that Haystack does some autodiscovery, therefore this
class must be added to a file named search_indexex.py. For our product model SmartCard, this indexing
class then may look like:

Listing 6: myshop/search_indexes.py
from shop.search.indexes import ProductIndex
from haystack import indexes

class SmartCardIndex(ProductIndex, indexes.Indexable):


catalog_media = indexes.CharField(stored=True,
indexed=False, null=True)
search_media = indexes.CharField(stored=True,
indexed=False, null=True)

def get_model(self):
return SmartCard

# more methods ...

While building the index, Haystack performs some preparatory steps:

Populate the reverse index database

The base class for our search index declares two fields for holding the reverse indexes and a few additional fields to
store information about the indexed product entity:

Listing 7: shop/indexes.py
class ProductIndex(indexes.SearchIndex):
text = indexes.CharField(document=True,
indexed=True, use_template=True)
autocomplete = indexes.EdgeNgramField(indexed=True,
use_template=True)

product_name = indexes.CharField(stored=True,
indexed=False, model_attr='product_name')
product_url = indexes.CharField(stored=True,
indexed=False, model_attr='get_absolute_url')

The first two index fields require a template which renders plain text, which is used to build a reverse index in the
search database. The indexes.CharField is used for a classic reverse text index, whereas the indexes.
EdgeNgramField is used for autocompletion.

5.6. Full Text Search 51


djangoSHOP, Release 1.0.2

Each of these index fields require their own template. They must be named according to the following rules:

search/indexes/myshop/<product-type>_text.txt

and

search/indexes/myshop/<product-type>_autocomplete.txt

and be located inside the project’s template folder. The <product-type> is the classname in lowercase of the given
product model. Create two individual templates for each product type, one for text search and one for autocompletion.
An example:

Listing 8: search/indexes/smartcard_text.txt
{{ object.product_name }}
{{ object.product_code }}
{{ object.manufacturer }}
{{ object.description|striptags }}
{% for page in object.cms_pages.all %}
{{ page.get_title }}{% endfor %}

The last two fields are used to store information about the product’s content, side by side with the indexed entities.
That’s a huge performance booster, since this information otherwise would have to be fetched from the relational
database, item by item, and then being rendered while preparing the search query result.
We can also add fields to our index class, which stores pre-rendered HTML. In the above example, this is done by
the fields catalog_media and search_media. Since we do not provide a model attribute, we must provide two
methods, which creates this content:

Listing 9: myshop/search_indexes.py
class SmartCardIndex(ProductIndex, indexes.Indexable):
# other fields and methods ...

def prepare_catalog_media(self, product):


return self.render_html('catalog', product, 'media')

def prepare_search_media(self, product):


return self.render_html('search', product, 'media')

These methods themselves invoke render_html which takes the product and renders it using a templates named
catalog-product-media.html or search-product-media.html respectively. These templates are
looked for in the folder myshop/products or, if not found there in the folder shop/products. The HTML
snippets for catalog-media are used for autocompletion search, whereas search-media is used for normal a normal
full-text search invocation.

Building the Index

To build the index in Elasticsearch, invoke:

./manage.py rebuild_index --noinput

Depending on the number of products in the database, this may take some time.

52 Chapter 5. Reference
djangoSHOP, Release 1.0.2

5.6.3 Search Serializers

Haystack for Django REST Framework is a small library aiming to simplify using Haystack with Django REST
Framework. It takes the search results returned by Haystack, treating them the similar to Django database models
when serializing their fields. The serializer used to render the content for this demo site, may look like:

Listing 10: myshop/serializers.py


from rest_framework import serializers
from shop.search.serializers import ProductSearchSerializer as
˓→ProductSearchSerializerBase

from .search_indexes import SmartCardIndex, SmartPhoneIndex

class ProductSearchSerializer(ProductSearchSerializerBase):
media = serializers.SerializerMethodField()

class Meta(ProductSearchSerializerBase.Meta):
fields = ProductSearchSerializerBase.Meta.fields + ('media',)
index_classes = (SmartCardIndex, SmartPhoneIndex)

def get_media(self, search_result):


return search_result.search_media

This serializer is part of the project, since we must adopt it to whatever content we want to display on our site, whenever
a visitor enters some text into the search field.

5.6.4 Search View

In the Search View we link the serializer together with a djangoCMS apphook. This CatalogSearchApp can be
added to the same file, we already used to declare the CatalogListApp used to render the catalog view:

Listing 11: myshop/cms_apps.py


from cms.apphook_pool import apphook_pool
from shop.cms_apphooks import CatalogSearchCMSApp

class CatalogSearchApp(CatalogSearchCMSApp):
def get_urls(self, page=None, language=None, **kwargs):
return ['myshop.urls.search']

apphook_pool.register(CatalogSearchApp)

as all apphooks, it requires a file defining its urlpatterns:

Listing 12: myshop/urls/search.py


from django.conf.urls import url
from shop.search.views import SearchView
from myshop.serializers import ProductSearchSerializer

urlpatterns = [
url(r'^', SearchView.as_view(
serializer_class=ProductSearchSerializer,
)),
]

5.6. Full Text Search 53


djangoSHOP, Release 1.0.2

Display Search Results

As with all other pages in django-SHOP, the page displaying our search results is a normal CMS page too. It is
suggested to create this page on the root level of the page tree.
As the page title use “Search” or whatever is appropriate as expression. Then we change into the Advanced Setting od
the page.
As a template use one with a big placeholder, since it must display our search results. Our default template usually is
a good fit.
As the page Id field, enter shop-search-product. Some default HTML snippets, prepared for inclusion in other
templates, use this hard coded string.
Set the input field Soft root to checked. This hides our search results page from the menu list.
As Application, select “Search”. This selects the apphook we created in the previous section.
Then save the page, change into Structure mode and locate the placeholder named Main Content. Add a Bootstrap
Container plugin, followed by a Row and then a Column plugin. As the child of this column, choose the Search
Results plugin from section Shop.
Finally publish the page and enter some text into the search field. It should render a list of found products.

54 Chapter 5. Reference
djangoSHOP, Release 1.0.2

5.6.5 Autocompletion in Catalog List View

As we have seen in the previous example, the Product Search View is suitable to search for any item in the product
database. However, the site visitor sometimes might just refine the list of items shown in the catalog’s list view. Here,
loading a new page which uses a layout able to render every kind of product usually differs from the catalog’s list
layout, and hence may by inappropriate.
Instead, when someone enters some text into the search field, django-SHOP starts to narrow down the list of items in
the catalog’s list view by typing query terms into the search field. This is specially useful in situations where hundreds
of products are displayed together on the same page and the customer needs to pick out the correct one by entering
some search terms.
To extend the existing Catalog List View for autocompletion, locate the file containing the urlpatterns, which are used
by the apphook ProductsListApp. In doubt, consult the file myshop/cms_apps.py. This apphook names a
file with urlpatterns. Locate that file and add the following entry:
In order to use the Product Search View, our Product Model must inherit from shop.models.product.
CMSPageReferenceMixin. This is because we must add a reference to the CMS pages our products are assigned
to, into the search index database. Such a product may for instance be declared as:

from shop.models.product import BaseProduct, BaseProductManager, CMSPageReferenceMixin

class MyProduct(CMSPageReferenceMixin, BaseProduct):


...

objects = BaseProductManager()

...

We normally want to use the same URL to render the catalog’s list view, as well as the autocomplete view, and hence
must route onto the same view class. However the search- and the catalog’s list view classes have different bases and
a completely different implementation.
The normal List View uses a Django queryset to iterate over the products, while the autocomplete View uses a Haystack
Search queryset. Therefore we wrap both View classes into shop.search.views.CMSPageCatalogWrapper
and use it in our URL routing such as:

from django.conf.urls import url


from shop.search.views import CMSPageCatalogWrapper
from myshop.serializers import CatalogSearchSerializer

urlpatterns = [
url(r'^$', CMSPageCatalogWrapper.as_view(
search_serializer_class=CatalogSearchSerializer,
)),
# other patterns
]

The view class CMSPageCatalogWrapper is a functional wrapper around the catalog’s products list view and the
search view. Depending on whether the request contains a search query starting with q=<search-term>, either the
search view or the normal products list view is invoked.
The CatalogSearchSerializer used here is very similar to the ProductSearchSerializer, we have
seen in the previous section. The only difference is, that instead of the search_media field is uses the
catalog_media field, which renders the result items media in a layout appropriate for the catalog’s list view.
Therefore this kind of search, normally is used in combination with auto-completion, because here we reuse the same
layout for the product’s list view.

5.6. Full Text Search 55


djangoSHOP, Release 1.0.2

The Client Side

To facilitate the placement of the search input field, django-SHOP ships with a reusable AngularJS directive
shopProductSearch, which is declared inside the module shop/js/search-form.js.
A HTML snipped with a submission form using this directive can be found in the shop’s templates folder at
shop/navbar/search-form.html. If you override it, make sure that the form element uses the directive
shop-product-search as attribute:

<form shop-product-search method="get" action="/url-of-page-rendering-the-search-


˓→results">

<input name="q" ng-model="searchQuery" ng-change="autocomplete()" type="text" />


</form>

If you don’t use the prepared HTML snippet, assure that the module is initialized while bootstrapping our Angular
application:

angular.module('myShop', [..., 'django.shop.search', ...]);

5.7 Filter Products by its Attributes

Besides Full Text Search, adding a filtering functionality to an e-commerce site is another very important feature.
Customers must be able to narrow down a huge list of available products to a small set of desired products using a
combination of prepared filter attributes.
In django-SHOP, we model each product according to its own properties, the color for instance. The customer then
might filter the listed products, selecting one or more of the given properties, the color “blue” for instance.
Therefore, when creating a database schema, we add that property to our product model. This can either be a hard
coded list of enumerated choices, or if we need a more flexible approach, a foreign key onto another model referring
to that specific property. If our product model allows more than one attribute of the same property, then we would use
a many-to-many-key in our database.
The contents of this additional property model (or hard coded property list), then is used to create a set of available
filtering options, from which the customer can select one (if allowed, also more) options to narrow down the list of
products with that specific attributes.
Fortunately, the REST framework in combination with Django Filter, makes it a rather simple task to implement this
kind of filtering functionality on top of the existing product models.

5.7.1 Adding a Filter to the List View

In django-SHOP showing a list of products, normally is controlled by the classes shop.views.catalog.


ProductListView or shop.views.catalog.CMSPageProductListView. By default these View classes
are configured to use the default filter backends as provided by the REST framework. These filter backends can be
configured globally through the settings variable DEFAULT_FILTER_BACKENDS.
Additionally we can subclass the filter backends for each View class in our urls.py. Say, we need a special catalog
filter, which groups our products by a certain product attribute. Then we can create a customized filter backend

Listing 13: filters.py


from rest_framework.filters import BaseFilterBackend

(continues on next page)

56 Chapter 5. Reference
djangoSHOP, Release 1.0.2

(continued from previous page)


class CatalogFilterBackend(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
queryset = queryset.order_by('attribute__sortmetric')
return queryset

In urls.py, where we route requests to the class shop.views.catalog.ProductListView, we then replace


the default filter backends by our own implementation:

Listing 14: myshop/urls/catalog.py


from django.conf.urls import url
from rest_framework.settings import api_settings
from shop.views.catalog import ProductListView

urlpatterns = [
url(r'^$', ProductListView.as_view(
filter_backends=[CatalogFilterBackend],
),
]

The above example is very simple but gives a rough impression on its possibilities.

Working with Django-Filter

Django Filter is a generic, reusable application to alleviate writing some of the more mundane bits of view code.
Specifically, it allows users to filter down a queryset based on a model’s fields, displaying the form to let them do this.
REST framework also includes support for generic filtering backends that allow you to easily construct complex
searches and filters.
By creating a class which inherits from django_filters.FilterSet, we can build filters against each attribute
of our product. This filter then uses the passed in query parameters to restrict the set of products available from our
current catalog view. Presume that our product model uses a foreign key onto a model holding all manufactures. We
then can create a simple filter class to restrict our list view onto a certain manufacturer:

Listing 15: myshop/filters.py


from django.forms import forms, widgets
import django_filters
from djng.forms import NgModelFormMixin
from myshop.models.product import MyProduct, Manufacturer

class FilterForm(NgModelFormMixin, forms.Form):


scope_prefix = 'filters'

class ProductFilter(django_filters.FilterSet):
manufacturer = django_filters.ModelChoiceFilter(
queryset=Manufacturer.objects.all(),
widget=Select(attrs={'ng-change': 'filterChanged()'}),
empty_label="Any Manufacturer")

class Meta:
model = MyProduct
form = FilterForm
fields = ['manufacturer']

(continues on next page)

5.7. Filter Products by its Attributes 57


djangoSHOP, Release 1.0.2

(continued from previous page)


@classmethod
def get_render_context(cls, request, queryset):
"""
Prepare the context for rendering the filter.
"""
filter_set = cls()
# we only want to show manufacturers for products available in the current
˓→list view

filter_field = filter_set.filters['manufacturer'].field
filter_field.queryset =filter_field.queryset.filter(
id__in=queryset.values_list('manufacturer_id'))
return dict(filter_set=filter_set)

To this filter class we can combine as many fields as we need, but in this example, we just use the foreign key to the
manufacturer model. For all available filter field types, please check the appropriate documentation in Django Filter.
We then can add this filter class to our product list view. In django-SHOP this normally is done through the url
patterns:

Listing 16: myshop/urls.py


urlpatterns = [
url(r'^$', ProductListView.as_view(
filter_class=ProductFilter,
)),
# other patterns
]

By appending ?manufacturer=7 to the URL, the above filter class will restrict the products in our list view to
those manufactured by the database entry with a primary key of 7.

Populate the Render Context

Filtering functionality without an appropriate user interface doesn’t make much sense. Therefore, when rendering the
product’s list view, we might want to add some input fields or special links, so that the customer can narrow down the
result set. To do this, the rendering template requires additional context data.
Since django-SHOP honours the principle of cohesion, each filter set is responsible for providing the context re-
quired to render its specific filtering parameters. This extra context must be provided by a class-method named
get_render_context(request, queryset), which must return a dictionary containing an instance of that
filter set.
While rendering HTML pages, this extra context then can be used to render various tag filtering elements, such as
a <select>-box. Since our ProductFilter can be rendered as form fields, we just have to use this Django
template:
..code-block:: django
{{ filter.filter_set.form }}

The Client Side

If your site uses the provided AngularJS directive <shop-list-products>, we typically want to use that as
well, when the customer applies a product filter. Therefore this directive listens on events named shop.catalog.
filter and queries the backend with the given properties. This allows us to add a set of filter options to the product’s
list view, without having to care about how to fetch that filtered list from the server.

58 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Since we don’t event want to care about controlling change events on the filtering <select> box, django-SHOP is
shipped with a reusable directive named shop.product.filter.
Sample HTML snippet:

<div shop-product-filter="manufacturer">
{{ filter.filter_set.form }}
</div>

or if your filter set forms uses more than one attribute:

<div shop-product-filter="['manufacturer', 'brand']">


{{ filter.filter_set.form }}
</div>

The Angular directive shop.product.filter is declared inside the shop’s shop/js/filters.js module,
so make sure to include that file. Additionally, that module must be initialized while bootstrapping our Angular
application:

angular.module('myShop', [..., 'django.shop.filter', ...]);

Each time the customer selects another manufacturer, the function filterChanged emits an event intercepted by
the AngularJS directive shopListProducts, which consequently fetches a list of products using the filtering class
as shown above.
Apart from forwarding changes detected in our <select> box, this directive also modifies the URL and appends the
selected properties. This is required, whenever the user navigates away from the product’s list view and returns back,
so that the same filters are applied. Additionally the directive clears the search query field, because full text search in
combination with property filtering is confusing and doesn’t make sense.

5.8 Cascade Plugins

Django-SHOP extends the used eco-system arround django-CMS plugins, djangocms-cascade, by additional shop-
specific plugins. This allows us to create a whole shopping site, which consists of many different elements, without
having to craft templates by hand – with one exception: The product detail views.
Therefore all we have to focus on, is a default page template with one big placeholder. This placeholder then is
subdivided into containers, rows, columns and other elements of the Cascade plugin collection.
This however requires a completely different approach, from a designer’s point of view. The way web design has been
done a few years ago, starting with the screenshot of a finished page, must be rethought. This has been discussed in
length by many web-designers, especially by Brad Frost in his excellent book on Atomic Web Design. He propagates
to reverse the design process and start with the smallest entity, which he calls Atoms. They form to bigger components,
named Molecules, which themselves aggregate to Organisms.
Some designers nowadays build those components directly in HTML and CSS or SASS, instead of drawing their
screens using programs such as InDesign or PhotoShop (which by the way never was intended for this kind of work).
It also exempts having the programmer to convert those screens into HTML and CSS – a time consuming and unsat-
isfying job.
According to Frost, the next bigger component after the Organism is the template. This is where djangocms-cascade
jumps in. Each of the Cascade plugins is shipped with its own default template, which can easily be overwritten by
the designers own implementation.

5.8. Cascade Plugins 59


djangoSHOP, Release 1.0.2

5.8.1 Overriding Templates

For all plugins described here, we can override the provided templates with our own implementation. If the shop
framework provides a template, named /shop/folder/my-organism.html, then we may override it using
/merchantimplementaion/folder/my-organism.html.
This template then usually extends the existing framework template with

{% extends "/shop/folder/my-organism.html" %}

{% block shop-some-identifier %}
<div>...</div>
{% endblock %}

This is in contrast to Django’s own implementation for searching the template, but allows to extend exiting templates
more easily.

5.8.2 Breadcrumb

The BreadcrumbPlugin has four different rendering options: Default, Soft-Root, With Catalog Count and Empty. It
can be added exclusively to the placeholder named Breadcrumb, unless otherwise configured.
The Default breadcrumb behaves as expected. Soft-Root appends the page title to the existing breadcrumb, it shall be
used for pages marked as soft root. A breadcrumb of type With Catalog Count adds a badge containing the number of
items. Use an Empty to hide the breadcrumb otherwise displayed by the placeholder as default.

5.8.3 Cart

The CartPlugin has four different rendering options: Editable, Static, Summary and Watch-List. Refer to the Cart
using a Cascade Plugin for details.

5.8.4 Checkout Forms

All Forms added to the checkout page are managed by members of the Cascade plugin system. All these plugin
inherit from a common base class, shop.cascade.plugin_base.DialogFormPluginBase. They all have
in common to render and validate one specific Form, which itself inherits from shop.forms.DialogForm or
shop.forms.DialogModelForm.
A nice aspect of this approach is, that . . .
• if we add, change or delete attributes in a form, fields are added, changed or deleted from the rendered HTML
as well.
• we get client side form validation for free, without having to write any Javascript nor HTML.
• if we add, change or delete attributes in a form, this modification propagates down to both form validation
controllers: That one in Javascript used on the client as well as the final one, validating the form on the server.
• if our forms are made out of models, all of the above works as well.
• we can arrange each of those form components using the Structure editor from django-CMS toolbar. This is
much faster, than by crafting templates manually.
As we can see from this approach, django-SHOP places great value on the principles of a Single Source of Truth,
when working with customized database models and forms.
Many of these Forms can be rendered using two different approaches:

60 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Form dialog

Here we render all model fields as input fields and group them into an editable form. This is the normal use case.

Static summary

Here we render all model fields as static strings without wrapping it into a form. This shall be used to summarize all
inputs, preferably on the last process step.
These are the currently available plugins provided by django-SHOP to build the checkout page:

Customer Form Plugin

The Customer Form is used to query information about some personal information, such as the salutation, the
first- and last names, its email address etc. In simple terms, this form combines the fields from the model classes
shop.models.customer.Customer and email_auth.models.User or auth.models.User respec-
tively. This means that fields, we add to our Customer model, are reflected automatically into this form.

Guest Form Plugin

The Guest Form is a reduced version of the Customer Form. It only asks for the email address, but nothing else. We
use it for customers which do not want to create an account.

Shipping- and Billing Address Forms

There are two form plugins, where customers can add their shipping and/or billing address. The billing address
offers a checkbox allowing to reuse the shipping address. By overriding the form templates, this behavior can be
switched. Both plugins provide a form made up from the model class implementing shop.models.address.
AddressModel.

Select the Payment Provider

For each payment provider registered within django-SHOP, this plugin creates a list of radio buttons, where customers
can choose their desired payment provider. By overriding the rendering templates, additional forms, for instance to
add credit card data, can be added.

Select a Shipping Method

For each shipping provider registered within django-SHOP, this plugin creates a list of radio buttons, where customers
can choose their desired shipping method.

Extra Annotations Plugin

This plugin provides a form, where customers can enter an extra annotation, while they proceed through the checkout
process.

5.8. Cascade Plugins 61


djangoSHOP, Release 1.0.2

Accept Condition Plugin

Normally customers must click onto a checkbox to accept various legal requirements, such as the terms and conditions
of this site. This plugin offers a text editor, where the merchant can enter a paragraph, possibly with a link onto another
CMS page explaining them in more details.

Required Form Fields Plugin

Most checkout forms have one or more required fields. To labels of required input fields, an asterisk is appended. This
plugin can be used to add a short text message stating “* These fields are required”. It normally should be placed
between the last checkout form and the proceed button.

Proceed Button

This plugin adds a styleable proceed button to any placeholder. This kind of button differs from a clickable link
button in that sense, that it first sends all gathered form data to the server and awaits a response. Only if all forms are
successfully validated, this button proceeds to the given link.
This proceed button can also handle two non-link targets: “Reload Page” and “Purchase Now”.
The first target is useful to reload the page in a changed context, for instance if a site visitor logged in and now shall
get a personalized page.
The second target is special to django-SHOP and exclusively used, when the customer performs The Purchasing
Operation.

5.8.5 Authentication

Before proceeding with various input forms, we must know the authentication status of our site visitors. These different
states are explained here in detail: Anonymous Users and Visiting Customers.
Therefore we need pluggable forms, where visitors can sign in and out, change and rest passwords and so on. All this
authentication forms are handled by one single plugin

Authentication Plugin

This plugin handles a bunch of authentication related forms. Lets list them:

Login Form

This is a simple login form accepting a username and password.

62 Chapter 5. Reference
djangoSHOP, Release 1.0.2

This form normally is used in combination with Link type: CMS Page.

Logout Form

This logout form just adds a button to sign out from the site.

This form normally is used in combination with Link type: CMS Page.

Shared Login/Logout Form

This combines the Login Form with the Logout Form so, that anonymous visitors see the login form, while logged in
users see the logout form. This form normally is used in combination with Link type: Reload Page.

Password Reset Form

This form offers a field, so that registered users, which forgot their password, can enter their email address to start a
password reset procedure.

5.8. Cascade Plugins 63


djangoSHOP, Release 1.0.2

Login & Reset Form

This extends the Shared Login/Logout Form by combining it with the Password Reset Form form.

If someone clicks on the link Password Forgotten? the form extends to

64 Chapter 5. Reference
djangoSHOP, Release 1.0.2

This form normally is used in combination with Link type: Reload Page.

Change Password Form

This form offers two field to change the password. It only appears for logged in users.

5.8. Cascade Plugins 65


djangoSHOP, Release 1.0.2

Register User Form

Using this form, anonymous visitors can register themselves. After having entered their email address and their desired
passwords, they become registered users.

66 Chapter 5. Reference
djangoSHOP, Release 1.0.2

This form normally is used in combination with Link type: Reload Page.

Continue as Guest Form

This form just adds a button, so that visitors can declare themselves as guest users who do not want to register an
account, nor expose their identity.

5.8. Cascade Plugins 67


djangoSHOP, Release 1.0.2

This form normally is used in combination with Link type: Reload Page.

5.8.6 Process Bar

The ProcessBarPlugin can be used to group many forms plugins onto the same page, by dividing them up into
different block. Only one block is visible at a time. At to top of that page, a progress bar appears which shows the
active step.
This plugin checks the validity of all of its forms and allows to proceed to the next step only, if all of them are valid.

Each step in that process bar must contain a Next Step Button, so that the customer can move to the next step, provided
all forms are valid.
The last step shall contain a Proceed Button which shall be configured to take appropriate action, for instance to start
the purchasing operation using the Link type “Purchase Now”.

Note: This plugin requires the AngularJS directive <bsp-process-bar> as found in the npm package angular-
bootstrap-plus.

5.8.7 Catalog

The catalog list view is handled by the ShopCatalogPlugin.

68 Chapter 5. Reference
djangoSHOP, Release 1.0.2

This plugin requires a CMS page, which uses the apphook ProductsListApp. First assure that we Create the Cat-
alogListApp. This CMSapp must be implemented by the merchant; it thus is part of the project, rather than the
django-SHOP framework.

5.8.8 Viewing Orders

The Order Views plugin is used to render the list- and detail views of orders, specific to the currently logged in
customer. Without a number in the URL, a list of all orders belonging to the current customer is shown. By adding
the primary key of a specific order to the URL, all ordered items from that specific order are shown. We name this the
order detail view, although it is a list of items.
This plugin requires a CMS page, which as uses the CMSApp OrderApp. This CMS application is part of the shop
framework and always available in the Advanced Settings of each CMS page.

Caveat when editing the Order Detail Page

The Order List- and Detail Pages share one common entity in our CMS page tree. The Order Detail view just rendered
in a different way. Editing this pseudo page therefore is not possible because it is not part of the CMS.

5.8.9 Search Results

Rendering search results is handled by the Search Results plugin.


On a site offering full-text search, add a page to display search results. First assure that we have a Search View assigned
to that page as apphook. This CMSapp must be implemented by the merchant; it thus is part of the project, rather than
the django-SHOP framework.

5.9 Cart and Checkout

In django-SHOP the cart’s content is always stored inside the database. In previous versions of the software, the cart’s
content was kept inside the session for anonymous users and stored in the database for logged in users. Now the cart
is always stored in the database. This approach simplifies the code and saves some random access memory, but adds
another minor problem:
From a technical point of view, the checkout page is the same as the cart. They can both be on separate pages, or be
merged on the same page. Since what we would normally name the “Checkout Page”, is only a collection of Cascade
Plugins, we won’t go into further detail here.

5.9.1 Expired Carts

Sessions expire, but then the cart’s content of anonymous customers still remains in the database. We therefore must
assure that these carts will expire too, since they are of no use for anybody, except, maybe for some data-mining
experts.
By invoking

./manage.py shopcustomers
Customers in this shop: total=3408, anonymous=140, expired=88,
active=1108, guests=2159, registered=1109, staff=5.

5.9. Cart and Checkout 69


djangoSHOP, Release 1.0.2

we gather some statistics about customers having visited of our django-SHOP site. In this example we see that 1109
customers bought as registered users, while 2159 bought as guests. There are 88 customers in the database, but they
don’t have any associated session anymore, hence they can be considered as expired. Invoking

./manage.py shopcustomers --delete-expired

deletes those expired customers, and with them their expired carts. This task shall be performed by a cronjob on a
daily or weekly basis.

5.9.2 Cart Models

The cart consists of two models classes Cart and CartItem, both inheriting from BaseCart and
BaseCartItem respectively. As with most models in django-SHOP, these are using the Deferred Model Pat-
tern, so that inheriting from a base class automatically sets the foreign keys to the appropriate model. This gives the
programmer the flexibility to add as many fields to the cart, as the merchant requires for his special implementation.
In most use-cases, the default cart implementation will do the job. These default classes can be found at shop.
models.defaults.cart.Cart and shop.models.defaults.cart_item.CartItem. To materialize
the default implementation, it is enough to import these two files into the merchants shop project. Otherwise we
create our own cart implementation inheriting from BaseCart and BaseCartItem. Since the item quantity can
not always be represented by natural numbers, this field must be added to the CartItem implementation rather
than its base class. Its field type must allow arithmetic operations, so only IntegerField, FloatField or
DecimalField are allowed as quantity.

Note: Assure that the model CartItem is imported (and materialized) before model Product and classes derived
from it.

The Cart model uses its own manager. Since there is only one cart per customer, accessing the cart must be performed
using the request object. We can always access the cart for the current customer by invoking:

from shop.models.cart import CartManager

cart = CartModel.objects.get_or_create_from_request(request)

Adding a product to the cart, must be performed by invoking:

from shop.models.cart import CartItemManager

cart_item = CartItemManager.get_or_create(
cart=cart, product=product, quantity=quantity, **extras)

This returns a new cart item object, if the given product could not be found in the current cart. Otherwise it returns
the existing cart item, increasing the quantity by the given value. For products with variations it’s not always trivial
to determine if they shall be considered as existing cart items, or as new ones. Since django-SHOP can’t tell that
difference for any kind of product, it delegates this question. Therefore the class implementing the shop’s products
shall override their method is_in_cart. This method is used to tell the CartItemManager whether a product
has already been added to the cart or is new.
Whenever the method cart.update(request) is invoked, the cart modifiers run against all items in the cart.
This updates the line totals, the subtotal, extra costs and the final sum.

70 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Watch List

Instead of implementing a separate watch-list (some would say wish-list), django-SHOP uses a simple trick. When-
ever the quantity of a cart item is zero, this item is considered to be in the watch list. Otherwise it is considered to be
in the cart. The train of though is as follows: A quantity of zero, never makes sense for items in the cart. On the other
side, any quantity makes sense for items in the watch-list. Therefore reducing the quantity of a cart item to zero is the
same as keeping an eye on it, without actually wanting it to purchase.

5.9.3 Cart Views

Displaying the cart in django-SHOP is as simple, as adding any other page to the CMS. Change into the Django
admin backend and enter into the CMS page tree. At an appropriate location in that tree add a new page. As page
title use “Cart”, “Basket”, “Warenkorb”, “Cesta”, or whatever is appropriate in the natural language used for that site.
Multilingual CMS installations offer a page title for each language.
In the CMS page editor click onto the link named Advanced Settings at the bottom of the popup window. As template,
choose the default one, provided it contains at least one big placeholder.
Enter “shop-cart” into the Id-field just below. This identifier is required by some templates which link directly onto
the cart view page. If this field is not set, some links onto the cart page might not work properly.
It is suggested to check the checkbox named Soft root. This prevents that a menu item named “Cart” will appear side
by side with other pages from the CMS. Instead, we prefer to render a special cart symbol located on the right of the
navigation bar.

Cart using a Cascade Plugin

Click onto View on site and change into front-end editing mode to use the grid-system of djangocms-cascade. Locate
the main placeholder and add a Row followed by at least one Column plugin; both can be found in section Bootstrap.
Below that column plugin, add a child named Cart from section Shop. This Cart Plugin can be rendered in four
different ways:

5.9. Cart and Checkout 71


djangoSHOP, Release 1.0.2

Editable Cart

An “Editable Cart” is rendered using the Angular JS template engine. This means that a customer may change the
number of items, delete them or move them to the watch-list. Each update is reflected immediately into the cart’s
subtotal, extra fields and final totals.
Using the above structure, the rendered cart will look similar to this.

Depending on the chosen template, this layout may vary.

Static Cart

An alternative to the editable cart is the static cart. Here the cart items are rendered by the Django template engine.
Since here everything is static, the quantity can’t be changed anymore and the customer would have to proceed to the
checkout without being able to change his mind. This probably only makes sense when purchasing a single product.

Cart Summary

This only displays the cart’s subtotal, the extra cart fields, such as V.A.T., shipping costs and the final total.

72 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Watch List

A special view of the cart is the watch list. It can be used by customers to remember items they want to compare or buy
sometimes later. The watch-list by default is editable, but does not allow to change the quantity. This is because the
watch-list shares the same object model as the cart items. If the quantity of an item 0, then that cart item is considered
to be watched. If instead the quantity is 1 ore more, the item is considered to be in the cart. It therefore is very easy to
move items from the cart to the watch-list and vice versa. This concept also disallows to have an item in both the cart
and the watch-list. This during online shopping, often can be a major point of confusion.

Render templates

The path of the templates used to render the cart views 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 cart.
• Search for a template named editable.html, static.html, watch.html or summary.html.
These templates are written to be easily extensible by the customized templates. To override the “editable cart” add
a template with the path, say myshop/cart/editable.html to the projects template folder. This template then
shall begin with {% extend "shop/cart/editable.html" %} and only override the {% block %}...
{% endblock %} interested in.
Many of these template blocks are themselves embedded inside HTML elements such as <script id="shop/..
..html" type="text/ng-template">. The reason for this is that the editable cart is rendered in the browser
by AngularJS using so called directives. Hence it becomes very straight-forward to override Angular’s script templates
using Django’s internal template engine.

Multiple templates

If for some special reasons we need different cart templates, then we must add this line to the projects settings.py:

CMSPLUGIN_CASCADE_PLUGINS_WITH_EXTRA_RENDER_TEMPLATES = {
'ShopCartPlugin': (
(None, _("default")), # the default behavior
('myproject/cart/other-editable.html', _("extra editable")),
)
}

This will add an extra select button to the cart editor. The site administrator then can choose between the default
template and an extra editable cart template.

Proceed to Checkout

On the cart’s view, the merchant may decide whether to implement the checkout forms together with the cart, or to
create a special checkout page onto which the customer can proceed. From a technical point of view, it doesn’t make
any difference, if the cart and the checkout are combined on the same CMS page, or if they are split across two or
more pages. In the latter case simply add a button at the end of each page, so that the customer can easily proceed to
the next one.

5.9. Cart and Checkout 73


djangoSHOP, Release 1.0.2

On the checkout page, the customer has to fill out a few forms. These can be a contact form, shipping and billing
addresses, payment and shipping methods, and many more. Which ones depend on the configuration, the legal regula-
tions and the requirements of the shop’s implementation. In Cascade Plugins all shop specific CMS plugins are listed.
They can be combined into whatever makes sense for a successful checkout.

Add a Cart via manually written Cart Template

Instead of using the CMS plugin system, the template for the cart can also be implemented manually. Based on an
existing page template, locate the element, where the cart shall be inserted. Then use one of the existing templates
in the folder django-shop/shop/templates/shop/cart/ as a starting point, and insert it at an appropri-
ate location in the page template. Next, in the project’s settings.py, add this specialized template to the list
CMS_TEMPLATES and select it for that page.
From a technical point of view, it does not make any difference whether we use the cart plugin or a handcrafted
template. If the HTML code making up the cart has to be adopted to the merchants needs, we normally are better off
and much more flexible, if we override the template code as described in section Render templates. Therefore, it is
strongly discouraged to craft cart and checkout templates by hand.

5.9.4 Cart Modifiers

Cart Modifiers are simple plugins that allow the merchant to define rules in a programmatic way, how the totals of
a cart are computed and how they are labeled. A typical job is to compute tax rates, adding discounts, shipping and
payment costs, etc.
Instead of implementing each possible combination for all of these use cases, the django-SHOP framework offers
an API, where third party applications can hooks into every computational step. One thing to note here is that Cart
Modifiers are not only invoked, when the cart is complete and the customer wants to proceed to the checkout, but also
for each item before being added to the cart.
This allows the programmer to vary the price of certain items, depending on the current state of the cart. It can for
instance be used, to set one price for the first item, and other prices for every further items added to the cart.
Cart Modifiers are split up into three different categories: Generic, Payment and Shipping. In the shops settings.
py they must be configured as a list or tuple such as:

SHOP_CART_MODIFIERS = (
'shop.modifiers.defaults.DefaultCartModifier',
'shop.modifiers.taxes.CartExcludedTaxModifier',
'myshop.modifiers.PostalShippingModifier',
'shop.modifiers.defaults.PayInAdvanceModifier',
'shop_stripe.modifiers.StripePaymentModifier',
)

Generic modifiers are applied always. The Shipping and Payment modifiers are applied only for the selected shipping
and/or payment method. If the customer has not yet decided, how to ship or how to pay, then the corresponding
modifiers are not applied.
When updating the cart, modifiers are applied in the order of the above list. Therefore it makes a difference, if taxes
are applied before or after having applied the shipping costs.
Moreover, whenever in the detail view the quantity of a product is updated, then all configured modifiers are ran for
that item. This allows the ItemModelSerializer, to even change the unit price of a product, depending on the
total content of the cart.
Cart modifiers are easy to write and they normally consist only of a few lines of code. It is the intention of django-
SHOP to seed an eco-system for these kinds of plugins. Besides computing the total, cart modifiers can also be used
to sum up the weight, if the merchant’s product models specifies it.

74 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Here is an incomplete list of some useful cart modifiers:

Generic Cart Modifiers

These kinds of cart modifiers are applied unconditionally onto the cart. A typical instance is the
DefaultCartModifier, the CartIncludeTaxModifier or the CartExcludeTaxModifier.

DefaultCartModifier

The shop.modifiers.default.DefaultCartModifier is required for almost every shopping cart. It han-


dles the most basic calculations, ie. multiplying the items unit prices with the chosen quantity. Since this modifier sets
the cart item’s line total, it must be listed as the first entry in SHOP_CART_MODIFIERS.

Payment Cart Modifier

From these kinds of modifiers, only that for the chosen payment method is applied. Payment Modifiers are used to
add extra costs or discounts depending on the chosen payment method. By overriding the method is_disabled a
payment method can be disabled; useful to disable certain payments in case the carts total is below a certain threshold.

Shipping Cart Modifier

From these kinds of modifiers, only that for the chosen shipping method is applied. Shipping Modifiers are used to
add extra costs or discounts depending on chosen shipping method, the number of items in the cart and their weight.
By overriding the method is_disabled a shipping method can be disabled; useful to disable certain payments in
case the cart’s total is below a certain threshold or the weight is too high.

How Modifiers work

Cart modifiers should extend the shop.modifiers.base.BaseCartModifier class and extend one or more
of the given methods:

Note: Until version 0.2 of django-SHOP, the Cart Modifiers returned the amount and label for the extra item rows,
and django-SHOP added them up. Since Version 0.3 cart modifiers must change the line subtotals and cart total
themselves.

class shop.modifiers.base.BaseCartModifier
Cart Modifiers are the cart’s counterpart to backends.
It allows to implement taxes and rebates / bulk prices in an elegant and reusable manner: Every time the cart is
refreshed (via it’s update() method), the cart will call all subclasses of this modifier class registered with their
full path in settings.SHOP_CART_MODIFIERS.
The methods defined here are called in the following sequence: 1. pre_process_cart: Totals are not computed,
the cart is “rough”: only relations and quantities are available 1a. pre_process_cart_item: Line totals are not
computed, the cart and its items are “rough”: only relations and quantities are available 2. process_cart_item:
Called for each cart_item in the cart. The modifier may change the amount in cart_item.line_total. 2a.
add_extra_cart_item_row: It optionally adds an object of type ExtraCartRow to the current cart item. This
object adds additional information displayed on each cart items line. 3. process_cart: Called once for the whole
cart. Here, all fields relative to cart items are filled. Here the carts subtotal is used to computer the carts total.

5.9. Cart and Checkout 75


djangoSHOP, Release 1.0.2

3a. add_extra_cart_row: It optionally adds an object of type ExtraCartRow to the current cart. This object adds
additional information displayed in the carts footer section. 4. post_process_cart: all totals are up-to-date, the
cart is ready to be displayed. Any change you make here must be consistent!
Each method accepts the HTTP request object. It shall be used to let implementations determine their prices
according to the session, and other request information.
arrange_watch_items(watch_items, request)
Arrange all items, which are being watched. Override this method to resort and regroup the returned items.
arrange_cart_items(cart_items, request)
Arrange all items, which are intended for the shopping cart. Override this method to resort and regroup
the returned items.
pre_process_cart(cart, request)
This method will be called before the Cart starts being processed. It shall be used to populate the cart with
initial values, but not to compute the cart’s totals.
pre_process_cart_item(cart, item, request)
This method will be called for each item before the Cart starts being processed. It shall be used to populate
the cart item with initial values, but not to compute the item’s linetotal.
process_cart_item(cart_item, request)
This will be called for every line item in the Cart: Line items typically contain: product, unit_price,
quantity and a dictionary with extra row information.
If configured, the starting line total for every line (unit price * quantity) is computed by the DefaultCart-
Modifier, which typically is listed as the first modifier. Posterior modifiers can optionally change the cart
items line total.
After processing all cart items with all modifiers, these line totals are summed up to form the carts subtotal,
which is used by method process_cart.
post_process_cart_item(cart, item, request)
This will be called for every line item in the Cart, while finally processing the Cart. It may be used to
collect the computed line totals for each modifier.
process_cart(cart, request)
This will be called once per Cart, after every line item was treated by method process_cart_item.
The subtotal for the cart is already known, but the total is still unknown. Like for the line items, the total is
expected to be calculated by the first cart modifier, which typically is the DefaultCartModifier. Posterior
modifiers can optionally change the total and add additional information to the cart using an object of type
ExtraCartRow.
post_process_cart(cart, request)
This method will be called after the cart was processed in reverse order of the registered cart modifiers.
The Cart object is “final” and all the fields are computed. Remember that anything changed at this point
should be consistent: If updating the price you should also update all relevant totals (for example).
add_extra_cart_item_row(cart_item, request)
Optionally add an ExtraCartRow object to the current cart item.
This allows to add an additional row description to a cart item line. This method optionally utilizes and/or
modifies the amount in cart_item.line_total.
add_extra_cart_row(cart, request)
Optionally add an ExtraCartRow object to the current cart.
This allows to add an additional row description to the cart. This method optionally utilizes cart.subtotal
and/or modifies the amount in cart.total.

76 Chapter 5. Reference
djangoSHOP, Release 1.0.2

5.10 Controlling the Cart Icon

On e-commerce sites, typically a cart- or basket symbol is located on the top right corner of the navigation bar and
clicking on it, normally loads the cart page.
Together with the cart icon, we often want to display an additional caption, such as the number of items and/or
the cart’s total. The cart item typically is rendered using the templatetag {% cart_icon %}. It can be styled
using the template myshop/templatetags/cart-icon.html, or if it doesn’t exist, falls back on shop/
templatetags/cart-icon.html.

5.10.1 Cart Icon Caption

This is where the client-side cart controller enters the scene. Adding product to –, or editing the cart changes the
number of items and/or the cart’s total. Therefore we must update its caption whenever we detect a modification in the
cart. A typical use pattern, for example is:
<a href="{% page_url 'shop-cart' %}">
<i class="fa fa-shopping-cart fa-fw fa-lg"></i>
<shop-carticon-caption caption-data="{num_items: {{ cart.num_items|default:0 }} }
˓→"></shop-carticon-caption>

</a>

The AngularJS directive <shop-carticon-caption ...> is itself styled using an Angular template such as:
<script id="shop/carticon-caption.html" type="text/ng-template">
<ng-pluralize count="caption.num_items" when="{'1': '{% trans "1 Item" context
˓→"cart icon" %}', 'other': '{% trans "{} Items" context "cart icon" %}'}"></ng-

˓→pluralize>

</script>

Whenever this AngularJS directive receives an event of type shop.carticon.caption, then it updates the cart
icon’s caption with the current state of the cart. The emitter of such an event typically is the cart editor or an add-to-cart
directive. If this function has already computed the new caption data, it may send it to the cart item, such as:
$scope.$emit('shop.carticon.caption', caption_data);

otherwise, if it emits the signal without object, the AngularJS directive shopCarticonCaption will fetch the
updated caption data from the server. The latter invokes an additional HTTP request but is useful, if the caption shall
for instance contain the cart’s total, since this has to be computed on the server anyway.

5.11 Payment Providers

Payment Providers are simple classes, which create an interface from an external Payment Service Provider (shortcut
PSP) to our django-SHOP framework.
Payment Providers must be aggregates of a Payment Cart Modifier. Here the Payment Cart Modifier computes extra
fees when selected as a payment method, whereas our Payment Provider class, handles the communication with the
configured PSP, whenever the customer submits the purchase request.
In django-SHOP Payment Providers normally are packed into separate plugins, so here we will show how to create
one yourself instead of explaining the configuration of an existing Payment gateway.
A precautionary measure during payments with credit cards is, that the used e-commerce implementation never sees
the card numbers or any other sensible information. Otherwise those merchants would have to be PCI-DSS certified,
which is an additional, but often unnecessary bureaucratic task, since most PSPs handle that task for us.

5.10. Controlling the Cart Icon 77


djangoSHOP, Release 1.0.2

5.11.1 Checkout Forms

Since the merchant is not allowed to “see” sensitive credit card information, some Payment Service Providers require,
that customers are redirected to their site so that there, they can enter their credit card numbers. This for some
customers is disturbing, because they visually leave the current shop site.
Therefore other PSPs allow to create form elements in HTML, whose content is send to their site during the purchase
task. This can be done using a POST submission, followed by a redirection back to the client. Other providers use
Javascript for submission and return a payment token to the customer, who himself forwards that token to the shopping
site.
All in all, there are so many different ways to pay, that it is quite tricky to find a generic solution compatible for all of
them.
Here django-SHOP uses some Javascript during the purchase operation. Lets explain how:

The Purchasing Operation

During checkout, the clients final step is to click onto a button labeled something like “Buy Now”. This button belongs
to an AngularJS controller, provided by the directive shop-dialog-proceed. It may look similar to this:
.. code-block:: html

<button shop-dialog-proceed ng-click=”proceedWith(‘PURCHASE_NOW’)” class=”btn btn-


success”>Buy Now</button>
Whenever the customer clicks onto that button, the function proceedWith('PURCHASE_NOW') is invoked in the
scope of the AngularJS controller, belonging to the given directive.
This function first uploads the current checkout forms to the server. There they are validated, and if everything is
OK, an updated checkout context is send back to the client. See shop.views.checkout.CheckoutViewSet.
upload() for details.
Next, the success handler of the previous submission looks at the given action. In proceedWith, we used the magic
keyword PURCHASE_NOW, which starts a second submission to the server, requesting to begin with the purchase oper-
ation (See shop.views.checkout.CheckoutViewSet.purchase() for details.). This method determines
he payment provider previously chosen by the customer. It then invokes the method get_payment_request()
of that provider, which returns a Javascript expression.
On the client, this returned Javascript expression is passed to the eval() function and executed; it then normally starts
to submit the payment request, sending all credit card data to the given PSP.
While processing the payment, PSPs usually need to communicate with the shop framework, in order to inform us
about success or failure of the payment. To communicate with us, they may need a few endpoints. Each Payment
provider may override the method get_urls() returning a list of urlpatterns, which then is used by the Django
URL resolving engine.
class MyPSP(PaymentProvider):
namespace = 'my-psp-payment'

def get_urls(self):
urlpatterns = [
url(r'^success$', self.success_view, name='success'),
url(r'^failure$', self.failure_view, name='failure'),
]
return urlpatterns

def get_payment_request(self, cart, request):


(continues on next page)

78 Chapter 5. Reference
djangoSHOP, Release 1.0.2

(continued from previous page)


js_expression = 'scope.charge().then(function(response) { $window.location.
˓→href=response.data.thank_you_url; });'

return js_expression

@classmethod
def success_view(cls, request):
# approve payment using request data returned by PSP
cart = CartModel.objects.get_from_request(request)
order = OrderModel.objects.create_from_cart(cart, request)
order.populate_from_cart(cart, request)
order.add_paypal_payment(payment.to_dict())
order.save(with_notification=True)
return HttpResponseRedirect(order.get_abolute_url())

@classmethod
def failure_view(cls, request):
"""Redirect onto an URL informing the customer about a failed payment"""
cancel_url = Page.objects.public().get(reverse_id='cancel-payment').get_
˓→absolute_url()

return HttpResponseRedirect(cancel_url)

Note: The directive shop-dialog-proceed evaluates the returned Javascript expression inside a chained
then(...)-handler from the AngularJS promise framework. This means that such a function may itself return
a new promise, which is resolved by the next then()-handler.

As we can see in this example, by evaluating arbitrary Javascript on the client, combined with HTTP-handlers for any
endpoint, django-SHOP is able to offer an API where adding new Payment Service Providers doesn’t require any
special tricks.

5.12 Order

During checkout, at a certain point the customer has to click on a button named “Purchase Now”. This operation
performs quite a few tasks: One of them is to convert the cart with its items into an order. The final task is to reset the
cart, which means to remove its content. This operation is atomic and not reversible.

5.12.1 Order Models

An order consists of two models classes Order and OrderItem, both inheriting from BaseOrder and
BaseOrderItem respectively. As with most models in django-SHOP, they are Deferred Model Pattern, so that
inheriting from a base class automatically sets the foreign keys to the appropriate model. This gives the programmer
the flexibility to add as many fields to the order model, as the merchant requires for his special implementation.
In most use-cases, the default implementation of the order model will do the job. These default classes can be found
at shop.models.defaults.order.Order and shop.models.defaults.order_item.OrderItem.
To materialize the default implementation, it is enough to import these two files into the merchant’s shop
project. Otherwise the programmer may create his own order implementation inheriting from BaseOrder and/or
BaseOrderItem.

Note: Assure that the model OrderItem is imported (and materialized) before model Product and classes derived

5.12. Order 79
djangoSHOP, Release 1.0.2

from it.

The order item quantity can not always be represented by natural numbers, therefore this field must be added to the
OrderItem implementation rather than its base class. Since the quantity is copied from the cart item to the order
item, its field type must must correspond to that of CartItem.quantity.

Create an Order from the Cart

Whenever the customer performs the purchase operation, the cart object is converted into a new order object by
invoking:

from shop.models.order import OrderModel

order = OrderModel.objects.create_from_cart(cart, request)


order.populate_from_cart(cart, request)

This invocation of order.populate_from_cart operation is atomic and can take some time. It normally is
performed by the payment provider, whenever a successful payment was received.
Since the merchant’s implementation of Cart, CartItem, Order and OrderItem may contain extra fields the
shop framework isn’t aware of, the content of these fields also shall be transferred, whenever a cart is converted into
an order object, during the purchasing operation.
If required, the merchant’s implementation of Order shall override the method populate_from_cart(cart,
request), which provides a hook to copy those extra fields from the cart object to the order object.
Similarly the merchant’s implementation of OrderItem shall override the method
populate_from_cart_item(cart_item, request), which provides a hook to copy those extra
fields from the cart item to the order item object.

Order Numbers

In commerce it is mandatory that orders are numbered using a unique and continuously increasing sequence. Each
merchant has his own way to generate this sequence numbers and in some implementations it may even come from an
external generator, such as an ERP system. Therefore django-SHOP does not impose any numbering scheme for the
orders. This intentionally is left over to the merchant’s implementation, which may be implemented as:

from django.db import models


from django.utils.datetime_safe import datetime
from shop.models import order

class Order(order.BaseOrder):
number = models.PositiveIntegerField("Order Number", null=True, default=None,
˓→unique=True)

def get_or_assign_number(self):
if self.number is None:
epoch = datetime.now().date()
epoch = epoch.replace(epoch.year, 1, 1)
qs = Order.objects.filter(number__isnull=False, created_at__gt=epoch)
qs = qs.aggregate(models.Max('number'))
try:
epoc_number = int(str(qs['number__max'])[4:]) + 1
self.number = int('{0}{1:05d}'.format(epoch.year, epoc_number))
except (KeyError, ValueError):
(continues on next page)

80 Chapter 5. Reference
djangoSHOP, Release 1.0.2

(continued from previous page)


# the first order this year
self.number = int('{0}00001'.format(epoch.year))
return self.get_number()

def get_number(self):
return '{0}-{1}'.format(str(self.number)[:4], str(self.number)[4:])

@classmethod
def resolve_number(cls, number):
number = number[:4] + number[5:]
return dict(number=number)

Here we override these three methods, otherwise the order number would be identical to its primary key which is not
suitable for all e-commerce sites.

Method get_or_assign_number()

Is used to assign a new number to an Order object, if none has been assigned yet, otherwise it returns the assigned one.

Method get_number()

Retrieves the order number assigned to an order in a human readable form. Here the first four digits specify the year
in which the order was generated, whereas the last five digits are a continuous increasing sequence.

Classmethod resolve_number(number)

Chances are high that we use the order number as slug, or for any other similar identification purpose. If we look
up for a certain order object using Order.objects.get(...) or Order.objects.filter(...), then we
might want to use a number previously retrieved with get_number. This classmethod therefore must reverse the
operation of building order numbers.

5.12.2 Order Views

Displaying the last or former orders in django-SHOP is as simple, as adding two pages to the CMS. Change into the
Django admin backend and enter into the CMS page tree. At an appropriate location in that tree add a new page. As
page title use “My Orders”, “Ihre Bestellungen”, “Mis Pedidos”, or whatever is appropriate in the natural language
used for that site. Multilingual CMS installations offer a page title for each language.
In the CMS page editor click onto the link named Advanced Settings at the bottom of the popup window. As template,
choose the default one, provided it contains at least one big placeholder.
Enter “shop-order” into the Id-field just below. This identifier is required by some templates which link directly onto
the orders list view page. If this field is not set, some links onto this page might not work properly.
The Order Views must be rendered by their own CMS apphook. Locate the field Application and choose “View
Orders”.
Below this “My Orders” page, add another page named “Thanks for Your Order”, “Danke für Ihre Bestellung” or
“Gracias por su pedido”. Change into the Advanced Settings view and as the rendering template select “Inherit the
template of the nearest ancestor”. Next enter “shop-order-last” into the Id-field just below. As Application choose
again “View Orders”.

5.12. Order 81
djangoSHOP, Release 1.0.2

CMS Apphook for the Order

The apphook for the Order View must be provided by the Django project. This is a simple snippet of boilerplate which
has to be added to the merchant’s implementation of the file myshop/cms_apps.py:

from cms.apphook_pool import apphook_pool


from shop.cms_apphooks import OrderCMSApp

class OrderApp(OrderCMSApp):
pass

apphook_pool.register(OrderApp)

This apphook uses the class shop.views.order.OrderView to render the order’s list- and detail views using
the serializers shop.serializers.order.OrderListSerializer and shop.serializers.order.
OrderDetailSerializer. Sometimes these defaults aren’t enough and must be extended by a customized seri-
alizer. Say, our Order class contains the rendered shipping and billing addresses. Then we can extend our serializer
class by adding them:

Listing 17: myshop/serializers.py


from shop.serializers.order import OrderDetailSerializer

class CustomOrderSerializer(OrderDetailSerializer):
shipping_address_text = serializers.CharField(read_only=True)
billing_address_text = serializers.CharField(read_only=True)

We now can replace the urls attribute in our apphook class with, say ['myshop.urls.order'] and exchange
the default serializer with our customized one:

Listing 18: myshop/urls/order.py


from django.conf.urls import url
from shop.views.order import OrderView
from myshop.serializers import CustomOrderSerializer

urlpatterns = [
url(r'^$', OrderView.as_view()),
url(r'^(?P<pk>\d+)$', OrderView.as_view(many=False,
detail_serializer_class=CustomOrderSerializer)),
]

Now, when invoking the order detail page appending ?format=api to the URL, then two new fields,
shipping_address_text and billing_address_text shall appear in our context.

Add the Order list view via CMS-Cascade Plugin

Click onto View on site and change into front-end editing mode to use the grid-system of djangocms-cascade. Locate
the main placeholder and add a Row followed by at least one Column plugin; both can be found in section Bootstrap.
Below that column plugin, add a child named Order Views from section Shop.
We have to perform this operation a second time for the page named “Thanks for Your Order”. The context menus for
copying and pasting may be helpful here.
Note that the page “My Orders” handles two views: By invoking it as a normal CMS page, it renders a list of all orders
the currently logged in customer has purchased at this shop:

82 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Clicking on one of the orders in this list, changes into a detail view, where one can see a list of items purchased during
that shopping session:

The rendered list is a historical snapshot of the cart in the moment of purchase. If in the meantime the prices of
products, tax rates, shipping costs or whatever changed, then that order object always keeps the values at that time in
history. This even applies to translations. Strings are translated into their natural language on the moment of purchase.
Therefore the labels added to the last rows of the cart, always are rendered in the language which was used during the
checkout process.

Render templates

The path of the templates used to render the order views 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 order.
• Search for a template named list.html or detail.html.
These templates are written to be easily extensible by the customized templates. To override them, add a template with
the path, say myshop/order/list.html to the projects template folder.

5.12.3 Order Workflows

Order Workflows are simple plugins that allow the merchant to define rules in a programmatic way, which actions to
perform, whenever a certain event happened. A typical event is the confirmation of a payment, which itself triggers
further actions, say to print a delivery note.
Instead of implementing each possible combination for all of these use cases, the django-SHOP framework offers
a Finite State Machine, where only selected state transition can be marked as possible. These transition further can
trigger other events themselves. This prevents to accidently perform invalid actions such as fulfilling orders, which
haven’t been paid yet.

5.12. Order 83
djangoSHOP, Release 1.0.2

In class shop.models.order.BaseOrder contains an attribute status which is of type FSMField. In prac-


tice this is a char-field, which can hold preconfigured states, but which can not be changed by program code. Instead,
by calling specially decorated class methods, this state then changes from one or more allowed source states into one
predefined target state. We denote this as a state transition.
An incomplete example:

class Order(models.Model):
# other attributes

@transition(field=status, source='new', target='created')


def populate_from_cart(self, cart, request):
# perform some side effects ...

Whenever an Order object is initialized, its status is new and is not yet populated with cart items, meaning that it
resides in a pending state. As we have seen earlier, this object must be populated from the cart. If this succeeds, the
status of our new Order object switches to created.
In django-SHOP the merchant can add as many payment providers he wants. This is done in settings.py
through the configuration directive SHOP_ORDER_WORKFLOWS which takes a list of so called “Order Workflow
Mixin” classes. On bootstrapping the application and constructing the Order class, it additionally inherits from these
mixin classes. This gives the merchant an easy to configure, yet very powerful tool to model the selling process of his
e-commerce site according to his needs. Say, we want to accept bank transfer in advance, so we must add 'shop.
payment.defaults.PayInAdvanceWorkflowMixin' to our configuration setting. Additionally we must
assure that the checkout process has been configured to offer the corresponding cart modifier:

SHOP_CART_MODIFIERS = (
...
'shop.modifiers.defaults.PayInAdvanceModifier',
...
)

This mixin class contains a few transition methods, lets for instance have a closer look onto

@transition(field='status', source=['created'], target='awaiting_payment')


def awaiting_payment(self):
"""Signals that an Order awaits payments."""

This method actually does nothing, beside changing the status from “created” to “awaiting_payment”. It is invoked by
the method get_payment_request() from ForwardFundPayment, which is the default payment provider
of the configured PayInAdvanceModifier cart modifier.
The class PayInAdvanceWorkflowMixin has two other transition methods worth mentioning:

@transition(field='status', source=['awaiting_payment'],
target='prepayment_deposited', conditions=[is_fully_paid],
custom=dict(admin=True, button_name=_("Mark as Paid")))
def prepayment_fully_deposited(self):
"""Signals that the current Order received a payment."""

This method can be invoked by the Django admin backend when saving an existing Order object, but only under
the condition that it is fully paid. The method is_fully_paid() iterates over all payments associated with its
Order object, sums them up and compares them against the total. If the entered payment equals or exceeds the order’s
total, this method returns True and the condition for the given transition is met. This then adds a button labeled
“Mark as Paid” at the bottom of the admin view. Whenever the merchant clicks on this button, the above method
prepayment_fully_deposited is invoked. This then changes the order’s status from “awaiting_payment”
to “prepayment_deposited”. The Notifications of django-SHOP can intercept this transition change and perform
preconfigured action, such as sending a payment confirmation email to the customer.

84 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Now that the order has been paid, it time to fulfill it. For this a merchant can use the workflow mixin class shop.
shipping.defaults.CommissionGoodsWorkflowMixin, which gives him a hand to keep track on the
fulfillment of each order. Since this class doesn’t know anything about an order status of “prepayment_deposited”
(this is a private definition of the class PayInAdvanceWorkflowMixin), django-SHOP provides a status to
mark the payment of an order as confirmed. Therefore another transition is added to our mixin class, which is invoked
automatically by the framework whenever the status changes to “prepayment_deposited”:

@transition(field='status', source=['prepayment_deposited',
'no_payment_required'], custom=dict(auto=True))
def acknowledge_prepayment(self):
"""Acknowledge the payment."""
self.acknowledge_payment()

This status, “payment_confirmed”, is known by all other workflow mixin classes and must be used as the source
argument for their transition methods.
For further details on Finite State Machine transitions, please refer to the FSM docs. This however does not cover
the contents of dictionary custom. One of the attributes in custom is button="Any Label" as explained in
the FSM admin docs. The other is auto=True and has been introduced by django-SHOP itself. It is used to
automatically proceed from one target to another one, without manual intervention, such as clicking onto a button.

Signals

Each state transition emits a signal before and after performing the status change. These signals, pre_transition
and post_transition can be received by any registered signal handler. In django-SHOP, the notification frame-
work listens for these events and creates appropriate notification e-mails, if configured.
But sometimes simple notifications are not enough, and the merchant’s implementation must perform actions in a
programmatic way. This for instance could be a query, which shall be sent to the goods management database,
whenever a payment has been confirmed successfully.
In Django, we typically register signal handlers in the ready method of the merchant’s application configuration:

Listing 19: myshop/apps.py


from django.apps import AppConfig

class MyShopConfig(AppConfig):
name = 'my_shop'

def ready(self):
from django_fsm.signals import post_transition
post_transition.connect(order_event_notification)

def order_event_notification(sender, instance=None, target=None, **kwargs):


if target == 'payment_confirmed':
# do whatever appropriate

In the above order event notification, use instance to access the corresponding Order object.

Finite State Machine Diagram

If graphviz is installed on the operating system, it is pretty simple to render a graphical representation of the currently
configured Finite State Machine. Simply invoke:

5.12. Order 85
djangoSHOP, Release 1.0.2

./manage.py ./manage.py graph_transitions -o fsm-graph.png

Applied to our demo shop, this gives the following graph:

5.12.4 Order Admin

The order admin backend is likely the most heavily used editor for django-SHOP installation. Here the merchant
must manage all incoming orders, payments, customer annotations, deliveries, etc. By automating common tasks, the
backend shall prevent careless mistakes: It should for instance neither be possible to ship unpaid goods, nor to cancel
a delivered order.
Since the django-SHOP framework does not know which class model is used to implement an Order, it intentionally
doesn’t register its prepared administration class for that model. This has to be done by the merchant implementing

86 Chapter 5. Reference
djangoSHOP, Release 1.0.2

the shop. It allows to add additional fields and other mixin classes, before registration.
For instance, the admin class used to manage the Order model in our shop project, could be implemented as:

Listing 20: myshop/admin.py


from django.contrib import admin
from shop.models.order import OrderModel
from shop.admin.order import (PrintInvoiceAdminMixin,
BaseOrderAdmin, OrderPaymentInline, OrderItemInline)

@admin.register(OrderModel)
class OrderAdmin(PrintInvoiceAdminMixin, BaseOrderAdmin):
fields = BaseOrderAdmin.fields + (
('shipping_address_text', 'billing_address_text',),)
inlines = (OrderItemInline, OrderPaymentInline,)

The fields shipping_address_text and billing_address_text are not part of the abstract model class
BaseOrder and therefore must be referenced separately.
Another useful mixin class to be added to this admin backend is PrintInvoiceAdminMixin. Whenever the
status of an order shows it has been paid, a button labeled “Print Invoice” is added to the order admin form. Clicking
on that button displays one ore more pages optimized for printing.
The template for the invoice and delivery note can easily be adopted to the corporate design using plain HTML and
CSS.

Rendering extra fields

The models Order and OrderItems both contain a JSON fiels to hold arbitary data, collected during the checkout
process. Here for instance, django-SHOP stores the computations as performed by the Cart Modifiers. Displaying
them in Django’s admin backend would result in a rendered Python dictionary, which is not well readable by humans.
Therefore the merchant may add a template, which is rendered using the content of that JSON field, named extra.
For the implemented order model the merchant may add a template named <myshop>/admin/order-extra.
html to its template folder. This template then shall render all the fields as available inside that JSON field. Here
rows contains a list of computations added by the cart modifiers.
Additionally, a merchant may add templates which are rendered using the contents of the JSON fields, for each of
the order item associated with the given order. Since order items can refer to different types of products, we may add
a template for each of them. It is named <myshop>/admin/orderitem-<productname>-extra.html
whereas productname is the class name in lowercase of the model implementing that product. If no such template
could be found, then a template named <myshop>/admin/orderitem-product-extra.html is used as
fallback. If no template is provided, then the content of these extra fields is not rendered.

5.12.5 Re-adding an Order to the Cart

Sometimes it can be useful to re-add the content of an order back to the cart. This functionality currently is imple-
mented only via the REST-API. By checking the field reorder before posting the data, the content of the given order
is copyied into the cart.

5.13 Managing the Deliver Process

Depending on the merchant’s setup, an order can be considered as one inseparably unit, or if partial shipping shall be
allowed, as a collection of single products, which can be delivered individually.

5.13. Managing the Deliver Process 87


djangoSHOP, Release 1.0.2

To enable partial shipping, assure that both classes shop.models.delivery.BaseDelivery and shop.
models.delivery.BaseDeliveryItem are instantiated. The easiest way to do this is to import the default
materialized classes into an existing model class:

from shop.models.defaults.delivery import Delivery, DeliveryItem

__all__ = ['Delivery', 'DeliveryItem'] # prevent IDE to complain about unused imports

5.13.1 Partial Delivery Workflow

The class implementing the Order, requires additional methods provided by the mixin shop.shipping.
delivery.PartialDeliveryWorkflowMixin. Mix this into the Order class by configuring:

SHOP_ORDER_WORKFLOWS = (
# other workflow mixins
'shop.shipping.defaults.PartialDeliveryWorkflowMixin',
)

Note: Do not combine this mixin with shop.shipping.defaults.CommissionGoodsWorkflowMixin.

5.13.2 Administration Backend

To control partial delivery, add the class shop.admin.delivery.DeliveryOrderAdminMixin to the amin


class class implementing an Order:

Listing 21: myshop/admin/order.py


from django.contrib import admin
from shop.admin.order import BaseOrderAdmin
from shop.models.defaults.order import Order
from shop.admin.delivery import DeliveryOrderAdminMixin

@admin.register(Order)
class OrderAdmin(DeliveryOrderAdminMixin, BaseOrderAdmin):
pass

5.13.3 Implementation Details

When partial delivery is activated, two additional tables are added to the database, one for each delivery and one for
each delivered order item. This allows us to split up the quantity of an ordered item into two or more delivery objects.
This can be useful, if a product is sold out, but the merchant wants to ship whatever is available on stock. The merchant
then creates a delivery object and assigns the available quantity to each linked delivery item.
If a product is not available at all anymore, the merchant can alternatively cancel that order item.

5.14 Designing an Address Model

Depending on the merchant’s needs, the business model and the catchment area of the site, the used address models
may vary widely. Since django-SHOP allows to subclass almost every database model, addresses are no exception

88 Chapter 5. Reference
djangoSHOP, Release 1.0.2

here. Therefore the class shop.models.address.BaseAddress does not provide any defaults, except for a
foreign key to the Customer model and a priority field used to sort multiple addresses by relevance.

5.14.1 Create a Customized Address Model

All the fields which make up an address, such as the addressee, the street name, zip code, etc. are part of the concrete
model implementing an address. It is the merchant’s responsibility to define which address fields are required for the
site’s needs. Therefore the base address model does not contain any address related fields, they instead have to be
declared by the merchant.
A concrete implementation of the shipping address model may look like this, which not really by coincidence is similar
to the address model as shipped by default (see below).

from shop.models.address import BaseShippingAddress, ISO_3166_CODES

class ShippingAddress(BaseShippingAddress):
name = models.CharField(
"Full name",
max_length=1024,
)

address1 = models.CharField(
"Address line 1",
max_length=1024,
)

address2 = models.CharField(
"Address line 2",
max_length=1024,
)

zip_code = models.CharField(
"ZIP / Postal code",
max_length=12,
)

city = models.CharField(
"City",
max_length=1024,
)

country = models.CharField(
"Country",
max_length=3,
choices=ISO_3166_CODES,
)

class Meta:
verbose_name = "Shipping Address"
verbose_name_plural = "Shipping Addresses"

Since the billing address may contain different fields, it must be defined separately from the shipping address. To
avoid the duplicate definition of common fields for both models, use a mixin class such as:

from django.db import models


from shop.models.address import BaseBillingAddress
(continues on next page)

5.14. Designing an Address Model 89


djangoSHOP, Release 1.0.2

(continued from previous page)

class AddressModelMixin(models.Model):
name = models.CharField(
"Full name",
max_length=1024,
)

address1 = models.CharField(
"Address line 1",
max_length=1024,
)
# other fields

class Meta:
abstract = True

class BillingAddress(BaseBillingAddress, AddressModelMixin):


tax_number = models.CharField(
"Tax number",
max_length=50,
)

class Meta:
verbose_name = "Billing Address"
verbose_name_plural = "Billing Addresses"

The Default Address Model

The simplest way is to materialize the required address classes, is to use them from our default
and convenience models: shop.models.defaults.address.ShippingAddress and shop.models.
defaults.address.BillingAddress. Before using them, we check if they fulfill our requirements. Oth-
erwise we create our own address models inheriting from shop.models.address.BaseAddress.

Note: After changing the address model, remember to create a database migration of the merchant implementation,
and apply it.

5.14.2 Multiple Addresses

In django-SHOP, if the merchant activates this feature, while setting up the site, customers can register more than one
address. Using the Checkout Address Form Plugin, we can enable this feature.
Now during checkout, the customer can select one of a previously entered shipping- and billing addresses, or if he
desires add a new one to his list of existing addresses.

5.14.3 How Addresses are used

Each active Cart object refers to one shipping address object and/or one billing address object. This means that the
customer can change those addresses whenever he uses the supplied address forms.
However, when the customer purchases the content of the cart, that address object is converted into a simple text string
and stored inside the then created Order object. This is to freeze the actual wording of the entered address. It also

90 Chapter 5. Reference
djangoSHOP, Release 1.0.2

assures that the address used for delivery and printed on the invoice is immune against accidental changes after the
purchasing operation.
By adding a template named myshop/address.txt for both address models, or myshop/
shipping-address.txt and myshop/billing-address.txt for each of them, the merchant can
define how the address shall be rendered on fulfilled orders.

Address Formatting

Whenever the customer fulfills the purchase operation, the corresponding shipping- and billing address objects are
rendered into a short paragraph of plain text, separated by the newline character. This formatted address then is used
to print address labels for parcel delivery and printed invoices.
It is the merchant’s responsibility to format these addresses according to the local practice. A customized ad-
dress template must be added into the merchant’s implementation below the templates folder named myshop/
shipping_address.txt or myshop/billing_address.txt. If both address models share the same fields,
we may also use myshop/address.txt as a fallback. Such an address template may look like:

5.14. Designing an Address Model 91


djangoSHOP, Release 1.0.2

Listing 22: myshop/address.txt


{{ address.name }}
{{ address.address1 }}{% if address.address2 %}
{{ address.address2 }}
{% endif %}
{{ address.zip_code }} {{ address.city }}
{{ address.get_country_display }}

This template is used by the method as_text() as found in each address model.

5.14.4 Use Shipping Address for Billing or vice versa

Most customers use their shipping address for billing. Therefore, unless you have really special needs, it is suggested
to share all address fields required for shipping, also with the billing address. The customer then can reuse the shipping
address for billing, if he desires to. Technically, if the billing address is unset, the shipping address is used anyway,
but in django-SHOP the merchant has to actively give permission to his customers, to reuse this address for billing.
The merchant has to actively allow this setting on the site, while editing the Address Form Plugin.

Warning: If the merchant allows to use the shipping address for billing and vice versa, then if the customer selects
both options, we end up having no address at all. It therefore is strongly recommended, that one address acts as
primary, and that the option “Use primary address” is checked only on the secondary one.

5.14.5 Address Forms

The address form, where customers can insert their address, is generated automatically and in a DRY manner. This
means that whenever a field is added, modified or removed from the address model, the corresponding fields in the
address input form, reflect those changes and without any additional programming. When creating the form template,
we have to write it using the as_div() method. This method also adds automatic client-side form validation to the
corresponding HTML code.

92 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Address Form Styling

One problem which remains with automatic form generation, is how to style the input fields. Therefore, django-SHOP
wraps every input field into a <div>-element using a CSS class named according to the field. This for instance is
useful to shorten some input fields and/or place them onto the same line.
Say, any of our address forms contain the fields zip_code and city as shown in the example above. Then they
may be styled as

.shop-address-zip_code {
width: 35%;
display: inline-block;
padding-right: 10px;
}

.shop-address-city {
width: 65%;
display: inline-block;
padding-left: 10px;
(continues on next page)

5.14. Designing an Address Model 93


djangoSHOP, Release 1.0.2

(continued from previous page)


}

so that the ZIP field is narrower and precedes the location field on the same line.

Note: If you override the supplied address form templates, assure that the statement {{ ..._address_form.
as_div }} is wrapped into a {% spaceless %}-block. Otherwise that CSS trick doesn’t work properly.

5.14.6 Arranging Address Forms

Typically, we ask the customer during the checkout process, for his shipping and/or billing addresses. This however is
completely up to the merchant; from a technical point of view, the step when to ask the customer for his addresses is
completely arbitrary and can be skipped at all for shops which exclusively ship virtual goods.
Good practice however is, to add the shipping and billing forms on the checkout process. Since we want to ensure that
a customer must enter a valid address, we wrap the address forms into a so called Validate Set of Forms Plugin. This
inhibits a customer to proceed to the next page and hence to the purchasing step, whenever at least one form did not
validate.

5.14.7 Technical Details

Each entered and validated shipping- and billing address address is associated with the current cart. This means
that the given addresses then are used while fulfilling the purchasing step. Additionally, each address belongs to the
customer which entered it. If multiple addresses are enabled, then django-SHOP assigns a priority to each of the
entered addresses in ascending order. A customer then can select one of a previously entered address.

94 Chapter 5. Reference
djangoSHOP, Release 1.0.2

5.14.8 Further Reading

A good introduction on which fields to use where and when in addresses can be found at http://www.uxmatters.com/
mt/archives/2008/06/international-address-fields-in-web-forms.php

5.15 Notifications

Whenever the status in model Order changes, the built-in Finite State Machine emits a signal using Django’s signal-
ing framework. These signals are received by django-SHOP’s Notification Framework.

5.15.1 Notification Admin

In Django’s admin backend on Start > Shop > Notification, the merchant can configure which email to send to whom,
depending on each of the emitted events. When adding or editing a notification, we get a form mask with four input
fields:

Notification Identifier

An arbitrary name used to distinguish the different notifications. It’s up to the merchant to choose a meaningful name,
“Order confirmed, paid with PayPal” could for instance be a good choice.

Event Type

Each Order Workflows declares a set of transition targets. For instance, the class PayInAdvanceWorkflowMixin
declares these targets: “Awaiting a forward fund payment”, “Prepayment deposited” and “No Payment Required”.
The merchant can attach a notification for each of these transition targets. Here he must choose one from the prepared
collection.

The Recipient

Transitions events are transmitted for changes in the order status. Each order belongs to one customer, and normally
he’s the first one to be informed, if something changes.
But other persons in the context of this e-commerce site might also be interested into a notification. In django-SHOP
all staff Users qualify, as it is assumed that they belong to the group eligible to manage the site.

Email Templates

From the section Start > Post Office > Email Templates, choose on of the Templates for Emails.

Notification attachments

Choose none, one or more static files to be attached to each email. This typically is a PDF with the terms and
conditions. We normally want to send them only to our customers, but not to the staff users, otherwise we’d fill up
their mail inbox with countless attachments.

5.15. Notifications 95
djangoSHOP, Release 1.0.2

5.15.2 Post Office

Emails for order confirmations are send asynchronously by django-SHOP. The reason for this is that it sometimes
takes a few seconds for an application server to connect via SMTP, and deliver an Email. It is unacceptable to do this
synchronously during the most sensitive phase of a purchase operation.
Therefore django-SHOP sends all generated emails using the queuing mail system Post Office. This app can hold a
set of different email templates, which use the same template language as Django itself. Emails can be rendered using
plain text, HTML or both.
When emails are queued, the chosen template object is stored side by side with its context serialized as JSON. These
queued emails are accessible in Django’s admin backend at Start > Post Office > Emails. Their status can either be
“queued”, “sent” or “failed”.
As an offline operation, ./manage.py send_queued_mail renders and sends queued emails to the given recip-
ient. During this step, the given template is rendered applying the stored context. Their status then changes to “sent”,
or in case of a problem to “failed”.
If django-SHOP is configured to run in a multilingual environment, post office renders the email in the language used
during order creation.

Templates for Emails

The Message fields can contain any code, which is valid for Django templates. Frequently, a summary of the order
is rendered in these emails, creating a list of ordered items. This list often is common across all email templates, and
therefore it is recommended to prepare it in a base template for being reused. In the merchants project folder, create
those base email templates inside the folder templates/myshop/email/.... Then inside the Message fields,
these templates can be loaded and expanded using the well known templatetag

{% extends "myshop/email/somebase.html" %}

Caveats when using an HTML Message

Displaying HTML in email clients is a pain. Nobody really can say, which HTML tags are allowed in which client –
and there are many email readers out there, far more than Internet browsers.
Therefore when designing HTML templates for emails, one must be really, really conservative. It may seem anachro-
nistic, but still a best practice is to use the <table> element, and if necessary, nest it into their <td> (tables data)
elements. Moreover, use inline styles rather than a <style> element containing blocks of CSS. It is recommended
to use a special email framework to avoid nasty quirks, when rendering the templates.
Images can be embedded into HTML emails using two different methods. One is to host the image on the web-server
and to build an absolute URI referring it. Therefore django-SHOP enriches the object RenderContext with the
base URI for that web-site and stores it as context variable named ABSOLUTE_BASE_URI. For privacy reasons, most
email clients do not load externally hosted images by default – the customer then must actively request to load them
from the external sources.
Another method for adding images to HTML emails is to inline their payload. This means that images, instead
of referring them by URI, are inlined as a base64-encoded string. Easy-thumbnails offers a template filter named
data_uri to perform this operation. This of course blows up the overall size of an email and shall only be used for
small an medium sized images.

96 Chapter 5. Reference
djangoSHOP, Release 1.0.2

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

5.16.1 From a Database Model to the Serializer

As we already know, all database models from the django-SHOP framework are owned by the merchant implemen-
tation. 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.

5.16.2 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:

5.16. REST Serializers 97


djangoSHOP, Release 1.0.2

Overriding the default Product Summary Serializer

98 Chapter 5. Reference
djangoSHOP, Release 1.0.2

Listing 23: myshop/serializers.py


1 from shop.serializers.bases import ProductSerializer
2 from myshop.models.product import MyProduct
3

4 class ProductSummarySerializer(ProductSerializer):
5 class Meta:
6 model = MyProduct
7 fields = ['id', 'product_name', 'product_url',
8 '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 <img src="...
">, 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:

1 from rest_framework.serializers import SerializerMethodField


2

3 class ProductSummarySerializer(ProductSerializer):
4 media = SerializerMethodField()
5

6 def get_media(self, product):


7 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:
1. 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.
2. Search for a subfolder named products.
3. 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 REST 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 easythumbnails_ library and its templatetag {%
thumbnail product.sample_image ... %}.

The template to render the media snippet could look like:

Listing 24: myshop/products/catalog-smartcard-media.html


{% load i18n thumbnail djng_tags %}
{% thumbnail product.sample_image 100x100 crop as thumb %}
<img src="{{ thumb.url }}" width="{{ thumb.width }}" height="{{ thumb.height }}">

The template of the products list view then may contain a list iteration such as:

5.16. REST Serializers 99


djangoSHOP, Release 1.0.2

{% for product in data.results %}


<div class="shop-list-item">
<a href="{{ product.product_url }}">
<h4>{{ product.product_name }}</h4>
{{ product.media }}
<strong>{% trans "Price" %}: {{ product.price }}</strong>
</a>
</div>
{% 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:

100 Chapter 5. Reference


djangoSHOP, Release 1.0.2

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 Create the CatalogListApp.
The urlpattern matching the regular expression ^$ routes onto the catalog list view class shop.views.catalog.
CMSPageProductListView passing in a special serializer class, for example myshop.serializers.

5.16. REST Serializers 101


djangoSHOP, Release 1.0.2

ProductSummarySerializer. This has been customized to represent our product models in our catalog tem-
plates. 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 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, shop.rest.filters.CMSPagesFilterBackend, is responsible for re-
stricting selected products on the current catalog list view.
The urlpattern matching the regular expression ^(?P<slug>[\w-]+)$ routes onto the class shop.
views.catalog.ProductRetrieveView passing in a special serializer class, myshop.serializers.
ProductDetailSerializer which has been customized to represent our product model details.
This View class inherits from rest_framework.generics.RetrieveAPIView. In addition to the given
serializer_class it can accept these fields:
• lookup_field: Model field to look up for the retrieved product. This defaults to slug.
• lookup_url_kwarg: URL argument as used by the matching RegEx. This defaults to slug.
• product_model: Restrict to products of this type. Defaults to ProductModel.
The product detail view requires another serializer, the so called AddToCartSerializer. This serializer is respon-
sible for controlling the number of items being added to the cart and gives feedback on the subtotal of that potential
cart item.
By appending the special string add-to-cart to the URL of a product’s detail view, say http://localhost:8000/de/
shop/smart-phones/apple-iphone-5/add-to-cart?format=api , one may check the legible representation of this serial-
izer:

102 Chapter 5. Reference


djangoSHOP, Release 1.0.2

This serializer is slightly different than the previous ones, because it not only serializes data and sends it from the
server to the client, but it also deserializes data submitted from the client back to the server using a post-request.
This normally is the quantity, but in more elaborated use cases, it also could contain attributes to distinguish product
variations. The AddSmartPhoneToCartSerializer for example, uses this pattern.
Since we may create our own Add this Product to Cart Serializer for each product type in our shop, hence overriding
its functionality with a customized implementation, such a serializer may return any other information relevant to the
customer. This could for instance be a rebate or just an update of the availability.

Cart and Checkout Views

CMS pages containing forms to edit the cart and the checkout views, do not require any URL routing, because their
HTML is rendered by the CMS plugin system, whereas form submissions are handled by hard coded REST endpoints.
These URLs are exclusively used by Ajax requests and never visible in the URL line of our browser. Those endpoints
are configured by adding them to the root resolver at a project level:

5.16. REST Serializers 103


djangoSHOP, Release 1.0.2

Listing 25: myshop/urls.py


urlpatterns = [
...
url(r'^shop/', include('shop.urls', namespace='shop')),
...
]

The serializers of the cart then can be accessed at http://localhost:8000/shop/api/cart/ , those of the watch-list at
http://localhost:8000/shop/api/watch/ and those handling the various checkout forms at http://localhost:8000/shop/
api/checkout/ . Accessing these URLs can be useful, specially when debugging JavaScript code.

Order List and Detail Views

The Order List and Detail Views must be accessible through a CMS page, therefore we need a speaking URL. This is
similar to the Catalog List View. This means that the Order Views require the apphook named “View Orders”, which
must be configured in the advanced settings of the Order’s CMS pages. This apphook is shipped with django-SHOP
itself and can be found at shop/cms_apps.py.
As with all other Views used by django-SHOP, the content of this View can also be rendered in its dictionary structure,
instead of HTML. Just append ?format=api to the URL and get the Order details. In our myshop example this
may look like:

104 Chapter 5. Reference


djangoSHOP, Release 1.0.2

Search Result Views

As with the Order View, also the Search Results View is accessible through a CMS page. Say, a search query di-
rected us to http://localhost:8000/en/search/?q=iphone , then the content of this query can be made visible by adding
&format=api to this URL and get the results in its dictionary structure. This is specially useful to test if a cus-
tomized search serializer returns the expected results. In our myshop example this may look like:

5.16. REST Serializers 105


djangoSHOP, Release 1.0.2

5.16.3 Final Note

In previous versions of django-SHOP, these kinds of controller implementations had to be implemented by cus-
tomized Django View classes. This programming pattern led to bloated code, because the programmer had to do a
case distinction, whether the request was of type GET, POST or some kind of Ajax. Now django-SHOP is shipped
with reusable View classes, and the merchant’s implementation must focus exclusively on serializers. This is much
easier, because it separates the business logic from the underlying request-response-cycle.

5.17 Client Side Framework

While Django doesn’t impose any client side framework, django-SHOP has to. Here we have to consider that it is
unrealistic to expect that an e-commerce site could operate without any client-side JavaScript. For instance, during
checkout the customer must be able to edit the cart interactively. We also might want to offer autocompletion and
infinite scroll.
Therefore the authors of django-SHOP have decided to add reusable JavaScript components. Here the most obvious
choice would have been jQuery, since it is used by the Django administration backend. However by using jQuery, web
designers adopting templates for their django-SHOP implementation would inevitably have to write JavaScript code
themselves. In order to prevent this, another popular client-side framework has been chosen: AngularJS.
This means that template designers only have to add shop specific HTML elements. All these directives are provided
by the django-SHOP framework. Frontend developers therefore do not have to add or adopt any JavaScript code,
except for the initialization.

106 Chapter 5. Reference


djangoSHOP, Release 1.0.2

Note: Since django-SHOP uses REST for every part of the communication, the client side framework can be replaced
by whatever appropriate.

5.17.1 Initialize the Application

As with any application, also the client side must be initialized. This in AngularJS is done straight forward. Change
the outermost HTML element, which typically is the <html> tag, to

<html ng-app="myShop">

somewhere in this file, include the JavaScript files required by Angular.


For a better organization of the included files, it is strongly recommended to use django-sekizai as assets manager:

{% load static sekizai_tags %}

{% addtoblock "js" %}<script src="{% static 'node_modules/angular/angular.min.js' %}"


˓→type="text/javascript"></script>{% endaddtoblock %}

{% addtoblock "js" %}<script src="{% static 'node_modules/angular-sanitize/angular-


˓→sanitize.min.js' %}"></script>{% endaddtoblock %}

{% addtoblock "js" %}<script src="{% static 'node_modules/angular-i18n/angular-locale_


˓→de.js' %}"></script>{% endaddtoblock %}

{% addtoblock "js" %}<script src="{% static 'node_modules/angular-animate/angular-


˓→animate.min.js' %}"></script>{% endaddtoblock %}

{% addtoblock "js" %}<script src="{% static 'node_modules/angular-messages/angular-


˓→messages.min.js' %}"></script>{% endaddtoblock %}

Before the closing </body>-tag, we then combine those includes and initialize the client side application. Say, we
declare a base template for our project:

Listing 26: myshop/pages/base.html


{% load djng_tags sekizai_tags %}
<body>
...
{% render_block "js" postprocessor "compressor.contrib.sekizai.compress" %}
<script type="text/javascript">
angular.module('myShop', ['ngAnimate', 'ngMessages', 'ngSanitize', {% with_data "ng-
˓→requires" as ng_requires %}

{% for module in ng_requires %}'{{ module }}'{% if not forloop.last %}, {% endif
˓→%}{% endfor %}{% end_with_data %}

]).config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.headers.common['X-CSRFToken'] = '{{ csrf_token }}';
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]).config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(false);
}]){% with_data "ng-config" as configs %}
{% for config in configs %}.config({{ config }}){% endfor %};
{% end_with_data %}
</script>

</body>

By using Sekizai’s templatetag render_block inside the initialization and configuration phase of our Angular
application, we can delegate the dependency resolution to template expansion and inclusion.

5.17. Client Side Framework 107


djangoSHOP, Release 1.0.2

For example, the editable cart requires its own AngularJS module, found in a separate JavaScript file. Since we honor
the principle of encapsulation, we only want to include and initialize that module if the customer loads the view to
alter the cart. Here the template for our editable cart starts with:

Listing 27: shop/cart/editable.html


{% load static sekizai_tags %}

{% addtoblock "js" %}<script src="{% static 'shop/js/cart.js' %}" type="text/


˓→javascript"></script>{% endaddtoblock %}

{% add_data "ng-requires" "django.shop.cart" %}

Sekizai then collects the content added to these add_data templatetags, and renders them using the with_data
statements shown above. This concept allows us to delegate dependency resolution and module initialization to whom
it concerns.

5.17.2 Angular Modules

The django-SHOP framework declares a bunch of Angular directives and controllers, grouped into separate mod-
ules. All these modules are placed into their own JavaScript files for instance static/shop/js/auth.js,
static/shop/js/cart.js, static/shop/js/catalog.js, etc. and use a corresponding but unique nam-
ing scheme, to avoid conflicts with other third party AngularJS modules. The naming scheme for these three modules
is unsurprisingly: django.shop.auth, django.shop.cart, django.shop.catalog, etc.
This is where Sekizai’s {% with_data "ng-requires" as ng_requires %} becomes useful. We now
can manage our AngularJS dependencies as:

angular.module('myShop', [/* other dependencies */


{% with_data "ng-requires" as ng_requires %}
{% for module in ng_requires %}'{{ module }}'{% if not forloop.last %}, {% endif
˓→%}{% endfor %}

{% end_with_data %}])

By adding Sekizai’s {% with_data "ng-config" as configs %} templatetag, we can add arbitrary con-
figuration code:

angular.module('myShop', [/* module dependencies */]


){% with_data "ng-config" as configs %}
{% for config in configs %}.config({{ config }}){% endfor %};
{% end_with_data %}

The templatetags {% with_data "ng-requires" ... %} and {% with_data "ng-config" ...


%} work, because some other template snippets declare {% add_data "ng-requires" ... %} and/or {%
add_data "ng-config" ... %}. Sekizai then collects these declarations and combines them in with_data.
Unless additional client functionality is required, these are the only parts where our project requires us to write
JavaScript.

5.18 Configuration and Settings

The django-SHOP framework itself, requires only a few configuration directives. However, since each e-commerce
site built around django-SHOP consists of the merchant’s own project, plus a collection of third party Django apps,
here is a summary of mandatory and some optional configuration settings:

108 Chapter 5. Reference


djangoSHOP, Release 1.0.2

5.18.1 Django-SHOP specific settings

App Label

This label is required internally to configure the name of the database tables used in the merchant’s implementation.
SHOP_APP_LABEL = 'myshop'

There is no default setting.

Site Framework

You should always activate Django’s site framework and set a default for
SITE_ID = 1

Alternative User Model

Django’s built-in User model lacks a few features required by django-SHOP, mainly the possibility to use the email
address as the login credential. This overridden model is 100% field compatible to Django’s internal model and even
reuses its own database table, namely auth_user.
AUTH_USER_MODEL = 'email_auth.User'

Since this user model intentionally does not enforce uniqueness on the email address, Django would complain if we
do not silence this system check:
SILENCED_SYSTEM_CHECKS = ['auth.W004']

For further information, please refer to the Customer Model documentation.

Authentication Backends

AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]

Currency

Unless Money types are specified explicitly, each project requires a default currency:
SHOP_DEFAULT_CURRENCY = 'EUR'

The typical format to render an amount is $ 1.23, but some merchant may prefer 1.23 USD. By using the config-
uration setting:
SHOP_MONEY_FORMAT = '{symbol} {amount}'

we my specify our own money rendering format, where {symbol} is C, $, £, etc. and {currency} is EUR, USD,
GBP, etc.
Unless amounts never reach a thousand, it is advised to activate a separator for better readability.

5.18. Configuration and Settings 109


djangoSHOP, Release 1.0.2

USE_THOUSAND_SEPARATOR = True

Outside of the US, it generally is a good idea to activate localization for numeric types.

USE_L10N = True

Cart Modifiers

Each project requires at least one cart modifier in order to initialize the cart. In most implementations shop.
modifiers.defaults.DefaultCartModifier is enough, but depending on the product models, the mer-
chant’s may implement an alternative.
To identify the taxes in the cart, use one of the provided tax modifiers or implement a customized one.
Other modifiers may add extra payment and shipping costs, or rebate the total amount depending on whatever appro-
priate.

SHOP_CART_MODIFIERS = [
'shop.modifiers.defaults.DefaultCartModifier',
'shop.modifiers.taxes.CartExcludedTaxModifier',
# other modifiers
]

For further information, please refer to the Cart Modifiers documentation.

Installed Django Applications

This is a configuration known to work. Special and optional apps are discussed below.

INSTALLED_APPS = [
'django.contrib.auth',
'email_auth',
'polymorphic',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'djangocms_admin_style',
'django.contrib.admin',
'django.contrib.staticfiles',
'django.contrib.sitemaps',
'djangocms_text_ckeditor',
'django_select2',
'cmsplugin_cascade',
'cmsplugin_cascade.clipboard',
'cmsplugin_cascade.sharable',
'cmsplugin_cascade.extra_fields',
'cmsplugin_cascade.segmentation',
'cms_bootstrap3',
'adminsortable2',
'rest_framework',
'rest_framework.authtoken',
'rest_auth',
'django_fsm',
'fsm_admin',
'djng',
(continues on next page)

110 Chapter 5. Reference


djangoSHOP, Release 1.0.2

(continued from previous page)


'cms',
'menus',
'treebeard',
'compressor',
'sekizai',
'sass_processor',
'django_filters',
'filer',
'easy_thumbnails',
'easy_thumbnails.optimize',
'parler',
'post_office',
'haystack',
'shop',
'my_shop_implementation',
]

• email_auth optional but recommended, overrides the built-in authentification. It must be located after
django.contrib.auth.
• polymorphic only required, if the site requires more than one type of product model. It presumes that
django-polymorphic is installed.
• djangocms_text_ckeditor optionally adds a WYSIWYG HTML editor which integrates well with
django-CMS.
• django_select2 optionally adds a select field to Django’s admin, with integrated autocompletion. Very
useful for addings links to products manually. It presumes that django-select2 is installed.
• cmsplugin_cascade adds the functionality to add CMS plugins, as provided by django-SHOP, to arbitrary
CMS placeholders. This setting including submodules can be removed, if all templates are created manually.
• cmsplugin_cascade.clipboard allows the site administrator to copy a set of plugins in one installation
and paste it into the placeholder of another one.
• cmsplugin_cascade.sharable allows the site administrator to share a preconfigurable set of plugin
attributes into an alias, to be reused by many plugins of the same type.
• cmsplugin_cascade.extra_fields allows the site administrator to add arbitrary CSS classes, styles
and ID-fields to entitled plugins.
• cmsplugin_cascade.segmentation allows to segment a set of plugins into logical units.
• cms_bootstrap3 adds some templates and templatetags to render Bootstrap 3 styled menus and navigation
bars.
• adminsortable2 allows the site administrator to sort various items in Django’s administration backend.
• rest_framework, rest_framework.authtoken and rest_auth, required, add the REST function-
ality to the django-SHOP framework.
• django_fsm and fsm_admin, required, add the Finite State Machine to the django-SHOP framework.
• djng only required for installations using AngularJS, which is the recommended JavaScript framework. It adds
the interface layer between Django and AngularJS and presumes that django-angular is installed.
• cms, menus and treebeard are required if django-SHOP is used in combination with djangoCMS.
• compressor, highly recommended. Concatenates and minifies CSS and JavaScript files on production sys-
tems. It presumes that django-compressor is installed.

5.18. Configuration and Settings 111


djangoSHOP, Release 1.0.2

• sekizai, highly recommended, allows the template designer to group CSS and JavaScript file includes. It
presumes that django-sekizai is installed.
• sass_processor, optional but recommended, used to convert SASS into pure CSS together with debugging
information. It presumes that django-sass-processor is installed.
• django_filters, optionally used to filter products by their attributes using request parameters.
• filer, highly recommended, manage your media files in Django. It presumes that django-filer is installed.
• easy_thumbnails and easy_thumbnails.optimize, highly recommended, handle thumbnail gener-
ation and optimization. It presumes that easy-thumbnails is installed.
• parler is an optional framework which handles the translation of models fields into other natural languages.
• post_office highly recommended. An asynchronous mail delivery application which does not interrupt the
request-response cycle when sending mail.
• haystack optional, handles the interface between Django and Elasticsearch – a full-text search engine. It pre-
sumes a running and available instance of ElasticSearch and that django-haystack and drf-haystack is installed.
• shop the django-SHOP framework.
• my_shop_implementation replace this by the merchant’s implementation of his shop.

Middleware Classes

This is a configuration known to work. Special middleware classes are discussed below.

MIDDLEWARE_CLASSES = (
'djng.middleware.AngularUrlMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'shop.middleware.CustomerMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.gzip.GZipMiddleware',
'shop.middleware.MethodOverrideMiddleware',
'cms.middleware.language.LanguageCookieMiddleware',
'cms.middleware.user.CurrentUserMiddleware',
'cms.middleware.page.CurrentPageMiddleware',
'cms.middleware.toolbar.ToolbarMiddleware',
)

• djng.middleware.AngularUrlMiddleware adds a special router, so that we can use Django’s


reverse function from inside JavaScript. Only required in conjunction with django-angular.
• shop.middleware.CustomerMiddleware add the Customer object to each request.
• shop.middleware.MethodOverrideMiddleware transforms PUT requests wrapped as POST re-
quests back into the PUT method. This is required for compatibility with some JS frameworks and proxies.

Static Files

If compressor and/or sass_processor are part of INSTALLED_APPS, add their finders to the list of the
default STATICFILES_FINDERS:

112 Chapter 5. Reference


djangoSHOP, Release 1.0.2

STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'sass_processor.finders.CssFinder',
'compressor.finders.CompressorFinder',
]

Django-SHOP requires a few third party packages, which are not available from PyPI, they instead must be installed
via npm install. In order to make these files available to our Django application, we use the configuration setting:

STATICFILES_DIRS = [
('node_modules', '/path/to/project/node_modules'),
]

Some files installed by npm are processed by django-sass-processor and hence their path must be made available:

NODE_MODULES_URL = STATIC_URL + 'node_modules/'

SASS_PROCESSOR_INCLUDE_DIRS = (
os.path.join(PROJECT_ROOT, 'node_modules'),
)

• The string provided by NODE_MODULES_URL is used by the special function get-setting() in the pro-
vided SASS files.
• SASS_PROCESSOR_INCLUDE_DIRS extends the list of folders to look for @import ... statements in the
provided SASS files.

Template Context Processors

Templates rendered by the django-SHOP framework require some additional objects or configuration settings. Add
them to each template using these context processors:

TEMPLATES = [{
...
'OPTIONS': {
'context_processors': (
...
'shop.context_processors.customer',
'shop.context_processors.ng_model_options',
),
},
}]

shop.context_processors.customer adds the Customer object to the rendering context.


shop.context_processors.ng_model_options adds the AngularJS specific settings to the rendering con-
text.

Configure the Order Workflow

The ordering workflow can be configured using a list or tuple of mixin classes.

SHOP_ORDER_WORKFLOWS = (
'shop.payment.defaults.PayInAdvanceWorkflowMixin',
(continues on next page)

5.18. Configuration and Settings 113


djangoSHOP, Release 1.0.2

(continued from previous page)


'shop.shipping.defaults.CommissionGoodsWorkflowMixin',
# other workflow mixins
)

This prevents to display all transitions configured by the workflow mixins inside the administration backend:
FSM_ADMIN_FORCE_PERMIT = True

Email settings

Since django-SHOP communicates with its customers via email, having a working outgoing e-mail service is a
fundamental requirement for django-SHOP. Adopt these settings to your configuration. Please remember that e-mail
is sent asynchronously via django-post_office.

EMAIL_HOST = 'smtp.example.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'no-reply@example.com'
EMAIL_HOST_PASSWORD = 'smtp-secret-password'
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = 'My Shop <no-reply@example.com>'
EMAIL_REPLY_TO = 'info@example.com'
EMAIL_BACKEND = 'post_office.EmailBackend'

Session Handling

For performance reasons it is recommended to use a memory based session store such as Redis, rather than a database
or disk based store.

SESSION_ENGINE = 'redis_sessions.session'
SESSION_SAVE_EVERY_REQUEST = True
SESSION_REDIS_PREFIX = 'myshop-session'
SESSION_REDIS_DB = 0

Caching Backend

For performance reasons it is recommended to use a memory based cache such as Redis, rather than a disk based store.
In comparison to memcached, Redis can invalidate cache entries using keys with wildcards, which is a big advantage
in django-SHOP.

CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
'LOCATION': os.environ.get('REDIS_LOCATION', 'redis://localhost:6379/0'),
'KEY_PREFIX': 'myshop-cache',
},
}

CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 3600
CACHE_MIDDLEWARE_KEY_PREFIX = 'myshop-cache'

114 Chapter 5. Reference


djangoSHOP, Release 1.0.2

5.18.2 Internationalisation Support

Always localize decimal numbers unless you operate you site in the United States:

USE_L10N = True

These settings for internationalisation are known to work in combination with django-cms and django-parler.

USE_I18N = True

LANGUAGE_CODE = 'en'

LANGUAGES = [
('en', "English"),
('de', "Deutsch"),
]

PARLER_DEFAULT_LANGUAGE = 'en'

PARLER_LANGUAGES = {
1: [
{'code': 'de'},
{'code': 'en'},
],
'default': {
'fallbacks': ['de', 'en'],
},
}

CMS_LANGUAGES = {
'default': {
'fallbacks': ['en', 'de'],
'redirect_on_fallback': True,
'public': True,
'hide_untranslated': False,
},
1: [{
'public': True,
'code': 'en',
'hide_untranslated': False,
'name': 'English',
'redirect_on_fallback': True,
}, {
'public': True,
'code': 'de',
'hide_untranslated': False,
'name': 'Deutsch',
'redirect_on_fallback': True,
},]
}

REST Framework

The REST framework requires special settings. We namely must inform it how to serialize our special Money type:

5.18. Configuration and Settings 115


djangoSHOP, Release 1.0.2

REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'shop.rest.money.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 12,
}

SERIALIZATION_MODULES = {'json': 'shop.money.serializers'}

Since the client side is not allowed to do any price and quantity computations, Decimal values are transferred to the
client using strings. This also avoids nasty rounding errors.
COERCE_DECIMAL_TO_STRING = True

Django-CMS and Cascade settings

Django-SHOP requires at least one CMS template. Assure that it contains a placeholder able to accept
CMS_TEMPLATES = [
('myshop/pages/default.html', _("Default Page")),
]

CMS_PERMISSION = False

cascade_workarea_glossary = {
'breakpoints': ['xs', 'sm', 'md', 'lg'],
'container_max_widths': {'xs': 750, 'sm': 750, 'md': 970, 'lg': 1170},
'fluid': False,
'media_queries': {
'xs': ['(max-width: 768px)'],
'sm': ['(min-width: 768px)', '(max-width: 992px)'],
'md': ['(min-width: 992px)', '(max-width: 1200px)'],
'lg': ['(min-width: 1200px)'],
},
}

CMS_PLACEHOLDER_CONF = {
'Breadcrumb': {
'plugins': ['BreadcrumbPlugin'],
'parent_classes': {'BreadcrumbPlugin': None},
'glossary': cascade_workarea_glossary,
},
'Commodity Details': {
'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],
'parent_classes': {
'BootstrapContainerPlugin': None,
'BootstrapJumbotronPlugin': None,
},
'glossary': cascade_workarea_glossary,
},
'Main Content': {
'plugins': ['BootstrapContainerPlugin', 'BootstrapJumbotronPlugin'],
'parent_classes': {
(continues on next page)

116 Chapter 5. Reference


djangoSHOP, Release 1.0.2

(continued from previous page)


'BootstrapContainerPlugin': None,
'BootstrapJumbotronPlugin': None,
'TextLinkPlugin': ['TextPlugin', 'AcceptConditionPlugin'],
},
'glossary': cascade_workarea_glossary,
},
'Static Footer': {
'plugins': ['BootstrapContainerPlugin', ],
'parent_classes': {
'BootstrapContainerPlugin': None,
},
'glossary': cascade_workarea_glossary,
},
}

Django-SHOP enriches djangocms-cascade with a few shop specific plugins.

from cmsplugin_cascade.extra_fields.config import PluginExtraFieldsConfig

CMSPLUGIN_CASCADE_PLUGINS = [
'cmsplugin_cascade.segmentation',
'cmsplugin_cascade.generic',
'cmsplugin_cascade.icon',
'cmsplugin_cascade.link',
'shop.cascade',
'cmsplugin_cascade.bootstrap3',
]

CMSPLUGIN_CASCADE = {
'link_plugin_classes': [
'shop.cascade.plugin_base.CatalogLinkPluginBase',
'cmsplugin_cascade.link.plugin_base.LinkElementMixin',
'shop.cascade.plugin_base.CatalogLinkForm',
],
'alien_plugins': ['TextPlugin', 'TextLinkPlugin', 'AcceptConditionPlugin'],
'bootstrap3': {
'template_basedir': 'angular-ui',
},
'plugins_with_sharables': {
'BootstrapImagePlugin': ['image_shapes', 'image_width_responsive', 'image_
˓→width_fixed',

'image_height', 'resize_options'],
'BootstrapPicturePlugin': ['image_shapes', 'responsive_heights', 'image_size',
˓→ 'resize_options'],

},
'bookmark_prefix': '/',
'segmentation_mixins': [
('shop.cascade.segmentation.EmulateCustomerModelMixin', 'shop.cascade.
˓→segmentation.EmulateCustomerAdminMixin'),

],
'allow_plugin_hiding': True,
}

Since we want to add arbitrary links onto the detail view of a product, django-SHOP offers a modified link plugin.
This has to be enabled using the 3-tuple link_plugin_classes.
Django-SHOP uses AngularJS rather than jQuery to control its dynamic HTML widgets. We therefore have to

5.18. Configuration and Settings 117


djangoSHOP, Release 1.0.2

override the default with this settings: CMSPLUGIN_CASCADE['bootstrap3']['template_basedir'].


For a detailed explanation of these configuration settings, please refer to the documentation of djangocms-cascade.

CK Text Editor settings

By default, django-CMS uses the CKEditor plugin which can be heavily configured. Settings which have shown to
be useful are:
CKEDITOR_SETTINGS_CAPTION = {
'language': '{{ language }}',
'skin': 'moono',
'height': 70,
'toolbar_HTMLField': [
['Undo', 'Redo'],
['Format', 'Styles'],
['Bold', 'Italic', 'Underline', '-', 'Subscript', 'Superscript', '-',
˓→'RemoveFormat'],

['Source']
],
}

CKEDITOR_SETTINGS_DESCRIPTION = {
'language': '{{ language }}',
'skin': 'moono',
'height': 250,
'toolbar_HTMLField': [
['Undo', 'Redo'],
['cmsplugins', '-', 'ShowBlocks'],
['Format', 'Styles'],
['TextColor', 'BGColor', '-', 'PasteText', 'PasteFromWord'],
['Maximize', ''],
'/',
['Bold', 'Italic', 'Underline', '-', 'Subscript', 'Superscript', '-',
˓→'RemoveFormat'],

['JustifyLeft', 'JustifyCenter', 'JustifyRight'],


['HorizontalRule'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Table'],
['Source']
],
}

Media assets handling

Django-CMS and django-SHOP rely on django-filer in combination with easy-thumbnails to manage the media
assets.
MEDIA_ROOT = '/path/to/project/media'

MEDIA_URL = '/media/'

FILER_ALLOW_REGULAR_USERS_TO_ADD_ROOT_FOLDERS = True

FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880

THUMBNAIL_OPTIMIZE_COMMAND = {
(continues on next page)

118 Chapter 5. Reference


djangoSHOP, Release 1.0.2

(continued from previous page)


'gif': '/usr/bin/optipng {filename}',
'jpeg': '/usr/bin/jpegoptim {filename}',
'png': '/usr/bin/optipng {filename}'
}

THUMBNAIL_PRESERVE_EXTENSIONS = True

THUMBNAIL_PROCESSORS = [
'easy_thumbnails.processors.colorspace',
'easy_thumbnails.processors.autocrop',
'filer.thumbnail_processors.scale_and_crop_with_subject_location',
'easy_thumbnails.processors.filters',
]

all settings are explained in detail in the documentation of django-filer and easy-thumbnails.

Full Text Search

Presuming that you installed and run an ElasticSearchEngine server, configure Haystack:

HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://localhost:9200/',
'INDEX_NAME': 'my_prefix-en',
},
}

If you want to index other natural language, say German, add another prefix:

HAYSTACK_CONNECTIONS = {
...
'de': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': 'http://localhost:9200/',
'INDEX_NAME': 'my_prefix-de',
}
}
HAYSTACK_ROUTERS = ('shop.search.routers.LanguageRouter',)

AngularJS specific settings

The cart’s totals are updated after an input field has been changed. For usability reasons it makes sense to delay this,
so that only after a certain time of inactivity, the update is triggered.

SHOP_ADD2CART_NG_MODEL_OPTIONS = "{updateOn: 'default blur', debounce: {'default':


˓→500, 'blur': 0}}"

This configuration updates the cart after changing the quantity and 500 milliseconds of inactivity or field blurring. It
is used by the “Add to cart” form.

SHOP_EDITCART_NG_MODEL_OPTIONS = "{updateOn: 'default blur', debounce: {'default':


˓→2500, 'blur': 0}}"

5.18. Configuration and Settings 119


djangoSHOP, Release 1.0.2

This configuration updates the cart after changing any of the product’s quantities and 2.5 seconds of inactivity or field
blurring. It is used by the “Edit cart” form.

Select2 specific settings

django-select2 adds a configurable autocompletion field to the project.


Change the include path to a local directory, if you prefer to install the JavaScript dependencies via npm instead of
relying on a preconfigured CDN:

SELECT2_CSS = 'node_modules/select2/dist/css/select2.min.css'
SELECT2_JS = 'node_modules/select2/dist/js/select2.min.js'

5.19 Shipping Providers

Unless you use the merchant management systems for delivery, django-SHOP provides some hooks to add shipping
providers. Shipping providers require that the Delivery model is available, otherwise there is no way to keep track
which items have been shipped with a delivery.
Version 0.13 has a complete and working implementation for a shipping provider.

5.20 Special CMS Pages

Besides the Catalog-, Cart- and Checkout Views, some pages must be accessed from already prepared templates, which
are shipped with this framework. These templates use the templatetag {% page_url %} shipped by django-CMS
with some hard coded IDs. Unless we want to rewrite those templates, we must provide a few special CMS pages,
where we specify those page IDs.

5.20.1 Customer Self Registering Page

The django-SHOP framework offers a plugin, which offers a form, where customers can enter their email address
and a password. This plugin is named Authentication using the Rendered Form: Register User.
Sometimes
self-registering
This page shall offer a form, This plugin
In the Advanced Settings of the CMS page handling this form, use shop-register-customer as the page ID.

5.20.2 Customer Details Page

This page shall offer a form, where a customer can enter his personal details, such as his or her names, email address
and whatever else is interesting for the merchant.

120 Chapter 5. Reference


djangoSHOP, Release 1.0.2

5.21 Settings

These is the complete list of setting directives available for django-SHOP.


Usage in your own code:

from shop.conf import app_settings

print(app_settings.APP_LABEL)

Note: When using as shown here, you don’t have to prefix the settings property with SHOP_....

class shop.conf.DefaultSettings

SHOP_APP_LABEL
The name of the project implementing the shop, for instance myshop.
This is required to assign the abstract shop models to a project. There is no default.
SHOP_DEFAULT_CURRENCY
The default currency this shop is working with. The default is EUR.

Note: All model- and form input fields can be specified for any other currency, this setting is only used if
the supplied currency is missing.

SHOP_VENDOR_EMAIL
The vendor’s email addresses, unless specified through the Order object.
SHOP_MONEY_FORMAT
When rendering an amount of type Money, use this format.
Possible placeholders are:
• {symbol}: This is replaced by C, $, £, etc.
• {currency}: This is replaced by Euro, US Dollar, Pound Sterling, etc.
• {code}: This is replaced by EUR, USD, GBP, etc.
• {amount}: The localized amount.
• {minus}: Only for negative amounts, where to put the - sign.
For further information about formatting currency amounts, please refer to https://docs.microsoft.com/
en-us/globalization/locale/currency-formatting
SHOP_DECIMAL_PLACES
Number of decimal places for the internal representation of a price. This is purely used by the Django
admin and is not the number of digits visible by the customer.
Defaults to 2.
SHOP_CUSTOMER_SERIALIZER
Depending on the materialized customer model, use this directive to configure the customer serializer.
Defaults to shop.serializers.defaults.customer.CustomerSerializer.

5.21. Settings 121


djangoSHOP, Release 1.0.2

SHOP_PRODUCT_SUMMARY_SERIALIZER
Serialize the smallest common denominator of all Product models available in this shop. This serialized
data then is used for Catalog List Views, Cart List Views and Order List Views.
Defaults to shop.serializers.defaults.product_summary.
ProductSummarySerializer.
SHOP_PRODUCT_SELECT_SERIALIZER
This serializer is only used by the plugin editors, when selecting a product using a drop down menu with
auto-completion.
Defaults to shop.serializers.defaults.ProductSelectSerializer.
SHOP_LINK_TO_EMPTY_CART
If True the link on the cart-icon pointing to the cart is enabled, even if there are no items are in the cart.
SHOP_ORDER_ITEM_SERIALIZER
Depending on the materialized OrderItem model, use this directive to configure the serializer.
Defaults to shop.serializers.defaults.OrderItemSerializer.
SHOP_CART_MODIFIERS
Specifies the list of Cart Modifiers. They are are applied on each cart item and the cart final sums.
This list typically starts with 'shop.modifiers.defaults.DefaultCartModifier' as its first
entry, followed by other cart modifiers.
SHOP_VALUE_ADDED_TAX
Use this convenience settings if you can apply the same tax rate for all products and you use one
of the default tax modifiers shop.modifiers.taxes.CartIncludeTaxModifier or shop.
modifiers.taxes.CartExcludedTaxModifier.
If your products require individual tax rates or you ship into states with different tax rates, then you must
provide your own tax modifier.
SHOP_ORDER_WORKFLOWS
Specifies a list of Order Workflows. Order workflows are applied after an order has been created and
conduct the vendor through the steps of receiving the payments until fulfilling the shipment.
SHOP_ADD2CART_NG_MODEL_OPTIONS
Used to configure the update behavior when changing the quantity of a product, in the product’s detail
view after adding it to the cart. For more information refer to the documentation of the NgModelOptions
directive in the AngularJS reference.
SHOP_EDITCART_NG_MODEL_OPTIONS
Used to configure the update behavior when changing the quantity of a cart item, in the cart’s edit view. For
more information refer to the documentation of the NgModelOptions directive in the AngularJS reference.
SHOP_GUEST_IS_ACTIVE_USER
If this directive is True, customers which declared themselves as guests, may request a password reset,
so that they can log into their account at a later time. Then it also makes sense to set the email field in
model email_auth.User as unique.
The default is False.
SHOP_OVERRIDE_SHIPPING_METHOD
If this directive is True, the merchant is allowed to override the shipping method the customer has chosen
while performing the checkout.
Note that if alternative shipping is more expensive, usually the merchant has to come up for the additional
costs.
The default is False.

122 Chapter 5. Reference


djangoSHOP, Release 1.0.2

SHOP_CACHE_DURATIONS
In the product’s list views, HTML snippets are created for the summary representation of each product.
By default these snippet are cached for one day.
SHOP_DIALOG_FORMS
Specify a list of dialog forms available in our shop.views.checkout.CheckoutViewSet. This
allows the usage of the endpoint resolve('shop:checkout-upload') in a generic way.
If Cascade plugins are used for the forms in the checkout view, this list can be empty.
SHOP_CASCADE_FORMS
Specify a map of Django Form classes to be used by the Cascade plugins used for the checkout view.
Override this map, if the Cascade plugins shall use a Form other than the ones provided.

5.22 Working off Asynchronous Jobs

A merchant implementation serving django-SHOP, usually runs a few asynchronous jobs, such as cleaning stale
entries, sending e-mail and building the search index. In Django, there are many ways to handle this, usually by inte-
grating Celery into Django. However, a Celery setup is unnecessarily complicated and usually not required. Instead we
can handle all of our asynchronous jobs using a short Python script, referred to as “The Worker” in the documentation.
This stand-alone program runs in the same environment as our Django app.
Here is a short example, which can be used as a blueprint for your own implementation:

Listing 28: worker.py


#!/usr/bin/env python
import os
import redis
import schedule
import time

if __name__ == '__main__':
from django import setup
from django.conf import settings
from django.core.management import call_command

# initialize Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_shop.settings')
setup()

# schedule jobs
schedule.every().sunday.do(call_command, 'shopcustomers', delete_expired=True)
schedule.every().day.at('10:00').do(call_command, 'rebuild_index',
˓→interactive=False)

schedule.every().minute.do(call_command, 'send_queued_mail')

if hasattr(settings, 'SESSION_REDIS'):
redis_con = dict((key, settings.SESSION_REDIS[key]) for key in ['host', 'port
˓→', 'db', 'socket_timeout'])

pool = redis.ConnectionPool(**redis_con)
r = redis.Redis(connection_pool=pool)
pubsub = r.pubsub()
pubsub.subscribe('django-SHOP')
else:
# we don't have a Redis message queue, emulate `pubsub`
(continues on next page)

5.22. Working off Asynchronous Jobs 123


djangoSHOP, Release 1.0.2

(continued from previous page)


pubsub = type(str('PubSub'), (), {'get_message': lambda s, timeout=1: time.
˓→sleep(timeout)})()

while True: # the main loop


message = pubsub.get_message(timeout=60)
if message and message['data'] == 'send_queued_mail':
call_command('send_queued_mail')
schedule.run_pending()

Here we schedule three jobs:


• Once a week we remove customers, whose session expired and which never made it to the checkout.
• Once a day we rebuild the search index for the Elasticsearch database.
• At least once a minute we send emails pending in the queue. If Redis is configured, django-SHOP uses its
internal message broker, and whenever an email is added to the queue, the asynchronous worker is notified, in
order to send it straightaway.

5.23 Deployment using Docker

By using Docker in combination with Docker Compose, the deployment of a django-SHOP installation becomes re-
ally simple. We make use of this in the demos, but these examples are intended to run on a local docker machine, hence
there we use the internal web server provided by uWSGI. In a productive environment, we usually use a web server to
dispatch HTTP requests onto a backend application server. This setup has been tested with NGiNX, which allows us
to dispatch multiple server names using the same IP address. Moreover, it also terminates all https connections.

5.23.1 Get started with the django-SHOP container composition

Each instance of a django-SHOP installation consists of at least 3 Docker containers. Some of them, such as
postgres, redis and elasticsearch are build from the standard images as provided by Docker-Hub. They
do not require customized Docker files.
Only the one providing the merchant implementation must be built using a project specific Dockerfile.
Before we start, let’s create a folder named docker-files. All files added to this folder shall be managed by the
version control system of your choice.

Configure uWSGI

Add a file named uwsgi.ini to the folder named docker-files. This is the main configuration file for our web
application worker. uWSGI has incredibly many configuration options and can be fine-tuned to your projects needs.
Please consult their documentation for the given configuration options.

Listing 29: docker-files/uwsgi.ini


[uwsgi]
chdir = /web
umask = 002
uid = django
gid = django
if-env = VIRTUAL_PROTO
socket = :9009
(continues on next page)

124 Chapter 5. Reference


djangoSHOP, Release 1.0.2

(continued from previous page)


endif =
if-not-env = VIRTUAL_PROTO
http-socket = :9009
endif =
exec-pre-app = /web/manage.py migrate
module = wsgi:application
buffer-size = 32768
static-map = /media=/web/workdir/media
static-map = /static=/web/staticfiles
static-expires = /* 7776000
offload-threads = %k
post-buffering = 1
processes = 1
threads = 1

Depending on whether VIRTUAL_PROTO is set to uwsgi (see below) or not, uWSGI either starts as a socket server
listening for WSGI requests, or as a pure web server listening for HTTP requests. The latter is useful for testing the
uWSGI application server, without having to run NGiNX as frontend. For example, this setup is used by the tutorial.
The directive exec-pre-app performs a database migration whenever a new version of the built containers is
started. This means that we can only perform forward migrations, which is the usual case anyway. In the rare occasion,
when we have to perform a backward migration, we have to do that manually by entering into the running container,
using docker exec -ti containername /bin/bash.
The directives static-map point onto the folders containing the collected static- and media-files. These folders
are referenced by the configuration directives STATIC_ROOT and MEDIA_ROOT in the projects settings.py, so
make sure they correspond to each other.
The directives processes and threads shall be adopted to the expected system load and the machine’s equipment.

Building the Images

We need a recipe to build the image for two of the containers in our project: wsgiapp and an optional worker. The
latter is a stand-alone Python script for Working off Asynchronous Jobs. Since it runs in the same environment as our
Django app, we use the same Docker image running two different containers.
Add a file name Dockerfile to the folder named docker-files.

Listing 30: docker-files/Dockerfile


FROM python:3.5
ENV PYTHONUNBUFFERED 1
RUN mkdir /web
WORKDIR /web
ARG DJANGO_MEDIA_ROOT=/web/workdir/media
ARG DJANGO_STATIC_ROOT=/web/staticfiles

# other additional packages outside of PyPI


RUN apt-get update
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get install -y nodejs gdal-bin
RUN rm -rf /var/lib/apt/lists/*

# install project specifiy requirements


ADD requirements /tmp/requirements
RUN pip install -r /tmp/requirements/version-0.5.txt
(continues on next page)

5.23. Deployment using Docker 125


djangoSHOP, Release 1.0.2

(continued from previous page)


RUN pip install 'uWSGI<2.1'
RUN groupadd -g 1000 django
RUN useradd -M -d /web -u 1000 -g 1000 -s /bin/bash django

# copy project relevant files into container


ADD my_shop /web/my_shop
ADD package.json /web/package.json
ADD package-lock.json /web/package-lock.json
ADD manage.py /web/manage.py
ADD wsgi.py /web/wsgi.py
ADD worker.py /web/worker.py
ADD docker-image/uwsgi.ini /web/uwsgi.ini
RUN npm install

# handle static files


ENV DJANGO_STATIC_ROOT=$DJANGO_STATIC_ROOT
RUN mkdir -p $DJANGO_STATIC_ROOT/CACHE
RUN _BOOTSTRAPPING=1 ./manage.py compilescss
RUN _BOOTSTRAPPING=1 ./manage.py collectstatic --noinput --ignore='*.scss'
RUN chown -R django.django $DJANGO_STATIC_ROOT/CACHE

# handle media files in external volume


ENV DJANGO_MEDIA_ROOT=$DJANGO_MEDIA_ROOT
RUN mkdir -p $DJANGO_MEDIA_ROOT
RUN chown -R django.django $DJANGO_MEDIA_ROOT

EXPOSE 9009
VOLUME /web/workdir

A container of this Docker image runs both, the Django application server and the asynchronous worker. Please refer
to the Docker documentation for details on the applied directives.
Ensure that the media directory is located inside a Docker volume. Otherwise all uploaded media files are lost,
whenever the image is rebuilt.
The port, on which the application server is listening for connections, must be exposed by Docker. Therefore ensure
that the setting EXPOSE matches with the settings for socket/http-socket used by the uWSGI daemon in
uwsgi.ini (see above).

Environment Variables

Some images must communicate with each other and hence require common configuration settings. In order not having
to repeatedly typing them, we use a common configuration file used by more than one Docker image configuration.
There we store our environment variables used for our configuration.
Add a file name environ to the folder named docker-files.

Listing 31: docker-files/environ


POSTGRES_DB=my_pg_database
POSTGRES_USER=my_pg_user
POSTGRES_PASSWORD=my_pg_passwd
POSTGRES_HOST=postgresdb
REDIS_HOST=redishost
ELASTICSEARCH_HOST=elasticsearch
DJANGO_EMAIL_HOST=outgoing_smtp_server
(continues on next page)

126 Chapter 5. Reference


djangoSHOP, Release 1.0.2

(continued from previous page)


DJANGO_EMAIL_PORT=587
DJANGO_EMAIL_USER=email_user
DJANGO_EMAIL_PASSWORD=email_password
DJANGO_EMAIL_USE_TLS=yes
DJANGO_EMAIL_FROM=no-reply@example.com
DJANGO_EMAIL_REPLY_TO=info@example.com

Replace the values of these environment variables with whatever is appropriate for your setup.

Composing everything together

The final step is to compose everything together, so that every service runs in its own container. This is the way Docker
is intended to be used. For this we require a file named docker-compose.yml. This file must be placed at the root
of the merchant’s project:

Listing 32: docker-compose.yml


version: '2.0'

services:
postgresdb:
restart: always
image: postgres
env_file:
- docker-files/environ
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- shopnet

redishost:
image: redis
volumes:
- 'redisdata:/data'
networks:
- shopnet

elasticsearch:
image: elasticsearch:1.7.5
container_name: elasticsearch
environment:
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- esdata:/usr/share/elasticsearch/data
networks:
- shopnet

wsgiapp:
restart: always
(continues on next page)

5.23. Deployment using Docker 127


djangoSHOP, Release 1.0.2

(continued from previous page)


build:
context: .
dockerfile: docker-files/Dockerfile
image: my_shop
env_file:
- docker-files/environ
volumes:
- shopmedia:/web/workdir/media
command: uwsgi --ini uwsgi.ini
depends_on:
- postgresdb
- redishost
- elasticsearch
networks:
- shopnet
ports:
- 9009:9009

worker:
restart: always
image: my_shop
env_file:
- docker-files/environ
command: su django -c /web/worker.py
volumes:
- shopmedia:/web/workdir/media
depends_on:
- postgresdb
- redishost
networks:
- shopnet

networks:
shopnet:

volumes:
pgdata:
redisdata:
shopmedia:
esdata:

Before proceeding with the final setup, we try to build and start a stand-alone version of this web application. This
helps to find errors much quicker, in case something went wrong.

$ docker-compose up --build

This step will take a while, especially the first time, since many Docker images must be downloaded from the Docker
hub. If all containers are up and running, point a browser onto the IP address of the docker-machine and on port 9009.
The IP address can be discovered by invoking docker-machine ip.
If everything works, we stop the containers using CTRL-C and proceed to the next section. In case a problem occurred,
check the log statements dumped onto the terminal.

128 Chapter 5. Reference


djangoSHOP, Release 1.0.2

5.23.2 Run NGiNX with Let’s Encrypt

In a production environment, usually you run these, and probably other containers behind a single NGiNX instance.
Additionally, since our customers normally do provide their user credentials and other sensitive information, such as
credit card numbers, we must ensure that our connection is secured by https.
To do so, we run a separate composition of two Docker containers using this configuration in a file named
nginx-compose.yml.

Listing 33: nginx-compose.yml


version: '2.0'

services:
nginx-proxy:
restart: always
image: jwilder/nginx-proxy:latest
ports:
- '80:80'
- '443:443'
volumes:
- '/var/run/docker.sock:/tmp/docker.sock:ro'
- '/etc/nginx/vhost.d'
- '/usr/share/nginx/html'
- '/etc/nginx/certs'
networks:
- nginx-proxy

letsencrypt-nginx-proxy-companion:
image: jrcs/letsencrypt-nginx-proxy-companion
volumes:
- '/var/run/docker.sock:/var/run/docker.sock:ro'
volumes_from:
- 'nginx-proxy'

networks:
nginx-proxy:
external: true

If we build these containers the first time, we might have to create the network, since it is declared as external:
$ docker network create nginx-proxy

To build and run the web server plus Let’s Encrypt companion, we invoke:
$ docker-compose -f nginx-compose.yml up --build -d

This spawns up two running Docker containers, where nginx-proxy is the actual webserver and
letsencrypt-nginx-proxy-companion just manages the SSL certificates using the Let’s Encrypt certifi-
cation authority. Note that you must point at least one DNS entry onto the IP address of this host. This name must
resolve by the global Domain Name Service.
Check if everything is up and running:
$ docker-compose -f nginx-compose.yml ps
Name Command
˓→State Ports
--------------------------------------------------------------------------------------
˓→----------------------------------------------
(continues on next page)

5.23. Deployment using Docker 129


djangoSHOP, Release 1.0.2

(continued from previous page)


nginxproxy_letsencrypt-nginx-proxy-companion_1 /bin/bash /app/entrypoint. ... Up
nginxproxy_nginx-proxy_1 /app/docker-entrypoint.sh ... Up
˓→ 10.9.8.7:443->443/tcp, 10.9.8.7:80->80/tcp

Pointing a browser onto the IP address of our docker-machine will raise a Gateway error. This is intended behaviour,
because our NGiNX yet does not know where to route incoming requests.

Provide django-SHOP behind NGiNX

Finally we want to run our django-SHOP instance behind the just configured NGiNX proxy. For this we have to edit
the file docker-compose.yml from above. There we change to following lines:
• In section wsgiapp, add the environment variables VIRTUAL_HOST, VIRTUAL_PROTO,
LETSENCRYPT_HOST and LETSENCRYPT_EMAIL to subsection environment, as shown below.
They are used to configure the NGiNX-Proxy.
• In section wsgiapp, add nginx-proxy to subsection networks and to the global section networks, as
shown below.
• Since we don’t need to access our WSGI application via an externally reachable port, we can remove the ports
configuration from section wsgiapp.

Listing 34: docker-compose.yml


wsgiapp:
...
environment:
- VIRTUAL_HOST=www.my_shop.com
- VIRTUAL_PROTO=uwsgi
- LETSENCRYPT_HOST=www.my_shop.com
- LETSENCRYPT_EMAIL=ssladmin@my_shop.com
...
networks:
- shopnet
- nginx-proxy
...
networks:
shopnet
nginx-proxy:
external: true

Re-create and run the Docker containers using:

$ docker-compose up --build -d

The container wsgiapp then starts to communicate with the container nginx-proxy and reconfigures its
virtual hosts settings without requiring any other intervention. The same also applies for the container
letsencrypt-nginx-proxy-companion, which then issues a certificate from the Let’s Encrypt Certification
Authority. This may take a few minutes.
To check if everything is up and running, invoke:

$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------------
my_shop_elasticsearch_1 /docker-entrypoint.sh elas ... Up 9200/tcp, 9300/tcp
(continues on next page)

130 Chapter 5. Reference


djangoSHOP, Release 1.0.2

(continued from previous page)


my_shop_postgresdb_1 docker-entrypoint.sh postgres Up 5432/tcp
my_shop_redishost_1 docker-entrypoint.sh redis ... Up 6379/tcp
my_shop_webapp_1 uwsgi --ini uwsgi.ini Up 9007/tcp
my_shop_worker_1 su django -c /web/worker.py Up 9007/tcp

5.23.3 Troubleshooting

If anything goes wrong, a good place to start is to check the logs. Accessing the logs is as easy as invoking:
$ docker container logs my_shop_webapp_1

5.23. Deployment using Docker 131


djangoSHOP, Release 1.0.2

132 Chapter 5. Reference


CHAPTER 6

How To’s

Some recipes on how to perform certain tasks in django-SHOP.


This collection of recipes unfortunately is not finished yet.

6.1 Add Customized HTML Snippets

When working in Structure Mode as provided by django-CMS, while editing the DOM tree inside a placeholder, we
might want to add a HTML snippet which is not part of the Cascade ecosystem. Instead of creating an additional
Django template, it often is much easier to just add a customized plugin. This plugin then is available when editing a
placeholder in Structure Mode.

6.1.1 Customized Cascade plugin

Creating a customized plugin for the merchant’s implementaion of that e-commerce project is very easy. Just add this
small Python module:

Listing 1: myshop/cascade.py
from cms.plugin_pool import plugin_pool
from shop.cascade.plugin_base import ShopPluginBase

class MySnippetPlugin(ShopPluginBase):
name = "My Snippet"
render_template = 'myshop/cascade/my-snippet.html'

plugin_pool.register_plugin(MySnippetPlugin)

then, in the project’s settings.py register that plugin together with all other Cascade plugins:

133
djangoSHOP, Release 1.0.2

CMSPLUGIN_CASCADE_PLUGINS = (
'cmsplugin_cascade.segmentation',
'cmsplugin_cascade.generic',
'cmsplugin_cascade.link',
'shop.cascade',
'cmsplugin_cascade.bootstrap3',
'myshop.cascade',
...
)

The template itself myshop/cascade/my-snippet.html can contain all templatetags as configured within the
Django project.
Often we want to associate customized styles and/or scripts to work with our new template. Since we honor the
principle of encapsulation, we somehow must refer to these files in a generic way. This is where django-sekizai helps
us:

Listing 2: myshop/cascade/my-snippet.html
{% load static sekizai_tags %}

{% addtoblock "css" %}<link href="{% static 'myshop/css/my-snippet.css' %}" rel=


˓→"stylesheet" type="text/css" />{% endaddtoblock %}

{% addtoblock "js" %}<script src="{% static 'myshop/js/my-snippet.js' %}" type="text/


˓→javascript"></script>{% endaddtoblock %}

<div>
my snippet code goes here...
</div>

Note: The main rendering template requires a block such as {% render_block "css" %} and {%
render_block "js" %} which then displays the stylesheets and scripts inside the appropriate HTML elements.

Further customizing the plugin

Sometimes we require additional parameters which shall be customizable by the merchant, while editing the plugin.
For Cascade this can be achieved very easily. First think about what kind of data to store, and which form widgets are
appropriate for that kind of editor. Say we want to add a text field holding the snippets title, then change the change
the plugin code from above to:

class MySnippetPlugin(ShopPluginBase):
...
title = GlossaryField(widgets.TextInput(), label=_("Title"))

Inside the rendering template for that plugin, the newly added title can be accessed as:

<h1>{{ instance.glossary.title }}</h1>


<div>...

Cascade offers many more options than just these. For details please check its reference guide.

134 Chapter 6. How To’s


djangoSHOP, Release 1.0.2

6.1.2 Creating a customized Form snippet

Sometimes we might need a dialog form, to store arbitrary information queried from the customer using a customized
form. Say we need to know, when to deliver the goods. This information will be stored inside the dictionary Cart.
extra and thus transferred automatically to Order.extra whenever the cart object is converted into an order
object.
Our form plugin now must inherit from shop.cascade.plugin_base.DialogFormPluginBase instead of
our ordinary shop plugin class:

from cms.plugin_pool import plugin_pool


from shop.models.cart import CartModel
from shop.cascade.plugin_base import DialogFormPluginBase

class DeliveryDatePlugin(DialogFormPluginBase):
name = "Delivery Date"
form_class = 'myshop.forms.DeliveryDateForm'
render_template = 'myshop/checkout/delivery-date.html'

def get_form_data(self, context, instance, placeholder):


cart = CartModel.objects.get_from_request(context['request'])
initial = {'delivery_date': getattr(cart, 'extra', {}).get('delivery_date', '
˓→')}

return {'initial': initial}

DialogFormPluginBase.register_plugin(DeliveryDatePlugin)

here additionally we have to specify a form_class. This form class can inherit from shop.forms.base.
DialogForm or shop.forms.base.DialogModelForm. Its behavior is almost identical to its Django’s coun-
terparts:

Listing 3: myshop/forms.py
class DeliveryDateForm(DialogForm):
scope_prefix = 'data.delivery_date'

date = fields.DateField(label="Delivery date")

@classmethod
def form_factory(cls, request, data, cart):
delivery_date_form = cls(data=data)
if delivery_date_form.is_valid():
cart.extra.update(delivery_date_form.cleaned_data)
return delivery_date_form

The scope_prefix marks the JavaScript object below our AngularJS $scope. This must be an identifier which is
unique across all dialog forms building up our ecosystem of Cascade plugins.
The classmethod form_factory must, as its name implies, create a form object of the class it belongs to. As in
our example from above, we use this to update the cart’s extra dictionary, whenever the customer submitted a valid
delivery date.
The last piece is to put everything together using a form template such as:

Listing 4: templates/myshop/checkout/delivery-date.html
{% extends "shop/checkout/dialog-base.html" %}

(continues on next page)

6.1. Add Customized HTML Snippets 135


djangoSHOP, Release 1.0.2

(continued from previous page)


{% block dialog_form %}
<form name="{{ delivery_date_form.form_name }}" novalidate>
{{ delivery_date_form.as_div }}
</form>
{% endblock %}

6.2 Handling Discounts

Generally, this is how you implement a “bulk rebate” module, for instance.

6.3 Taxes

As a general rule, the unit price of a product, shall always contain the net price. When our products show up in
the catalog, their method get_price(request) is consulted by the framework. It is here where you add tax,
depending on the tax model to apply. See below.

6.3.1 Use Cart Modifiers to handle Value Added Tax

Django-SHOP is not shipped with any kind of built-in tax handling code. This is because tax models vary from
product to product and region to region. Therefore the tax computation shall be pluggable and easily exchangeable.

American tax model

The American tax model presumes that all prices are shown as net prices, hence the subtotal is the sum of all net
prices. On top of the subtotal we add the taxes and hence compute the total.
A simple tax cart modifier which adds the tax on top of the subtotal:
from shop.serializers.cart import ExtraCartRow
from shop.modifiers.base import BaseCartModifier

VALUE_ADDED_TAX = 9.0

class CartIncludeTaxModifier(BaseCartModifier):
taxes = VALUE_ADDED_TAX / 100

def add_extra_cart_row(self, cart, request):


amount = cart.subtotal * self.taxes
instance = {
'label': "plus {}% VAT".format(VALUE_ADDED_TAX),
'amount': amount,
}
cart.extra_rows[self.identifier] = ExtraCartRow(instance)
cart.total += amount

European tax model

The European tax model presumes that all prices are shown as gross prices, hence the subtotal already contains the
taxes. However, we must report the contained taxes on the invoice.

136 Chapter 6. How To’s


djangoSHOP, Release 1.0.2

A simple tax cart modifier which reports the tax already included in the subtotal:

from shop.serializers.cart import ExtraCartRow


from shop.modifiers.base import BaseCartModifier

VALUE_ADDED_TAX = 19.0

class CartExcludedTaxModifier(BaseCartModifier):
taxes = 1 - 1 / (1 + VALUE_ADDED_TAX / 100)

def add_extra_cart_row(self, cart, request):


amount = cart.subtotal * self.taxes
instance = {
'label': "{}% VAT incl.".format(VALUE_ADDED_TAX),
'amount': amount,
}
cart.extra_rows[self.identifier] = ExtraCartRow(instance)

Note that here we do not change the current total.

Mixed tax models

When doing business to business, then in Europe the American tax model is used. Sites handling both private cus-
tomers as well as business customers must provide a mixture of both tax models. Since business customers can be
identified through the Customer objects provided by request object, we can determine which tax model to apply
in each situation.

6.3.2 Varying Taxes per Item

For certain kind of products, different tax rates must be applied. If your e-commerce site must handle these kinds of
products, then we add a tag to our product model. This could be an enum field, with one value per tax rate or a decimal
field containing the rate directly.
In this example we use the latter, where each product contains a field named vat, containing the tax rate in percent.

from shop.serializers.cart import ExtraCartRow


from shop.modifiers.base import BaseCartModifier
from myshop.models.product import Product

class TaxModifier(BaseCartModifier):
def __init__(self, identifier=None):
super(TaxModifier, self).__init__(identifier)
self.tax_rates = Product.objects.order_by('vat').values('vat').
˓→annotate(count=Count('vat'))

def pre_process_cart(self, cart, request):


for rate in self.tax_rates:
tax_attr = '_{}_vat_{vat}'.format(self.identifier, **rate)
setattr(cart, tax_attr, Money(0))

def add_extra_cart_item_row(self, cart_item, request):


vat = cart_item.product.vat
tax_attr = '_{0}_vat_{1}'.format(self.identifier, vat)
amount = cart_item.line_total * Decimal(vat) / 100
setattr(cart_item, tax_attr, amount)
(continues on next page)

6.3. Taxes 137


djangoSHOP, Release 1.0.2

(continued from previous page)

def post_process_cart_item(self, cart, cart_item, request):


tax_attr = '_{0}_vat_{1}'.format(self.identifier, cart_item.product.vat)
setattr(cart, tax_attr, getattr(cart, tax_attr) + getattr(cart_item, tax_
˓→attr))

def add_extra_cart_row(self, cart, request):


for rate in self.tax_rates:
tax_attr = '_{}_vat_{vat}'.format(self.identifier, **rate)
instance = {
'label': "plus {vat}% VAT".format(**rate),
'amount': getattr(cart, tax_attr),
}
cart.extra_rows['{}:vat_{vat}'.format(self.identifier, **rate)] =
˓→ExtraCartRow(instance)

def process_cart(self, cart, request):


super(TaxModifier, self).process_cart(cart, request)
for rate in self.tax_rates:
tax_attr = '_{}_vat_{vat}'.format(self.identifier, **rate)
cart.total += getattr(cart, tax_attr)

First, in method pre_process_cart we add additional attributes to the cart object, in order to have a placeholder
where to sum up the taxes for each tax rate.
In method add_extra_cart_item_row we compute the tax amount for each item individually and store it as
additional attribute in each cart item.
In method post_process_cart_item we sum up the tax amount over all cart items.
In method add_extra_cart_row we report the sum of all tax rates individually. They will show up on the invoice
using one line per tax rate.
Finally, in method process_cart we sum up all tax amounts for all rates and add them to the cart’s total.

138 Chapter 6. How To’s


CHAPTER 7

Django/Python compatibility table

django-SHOP Django Python


1.8 1.9 1.10 1.11 2.0 2.7 3.4 3.5 3.6
0.10.x X X X X X
0.11.x X X X X X X
0.12.x X X X X X
0.13.x X X X X X

139
djangoSHOP, Release 1.0.2

140 Chapter 7. Django/Python compatibility table


CHAPTER 8

Development and Community

8.1 Changelog for django-SHOP

8.1.1 1.0.2

• Revert the change of the quantity field to use a PositiveIntegerField in the default implementations
of CartItem and OrderItem models. This caused #766. This change was scheduled for version 1.1 but
unfortunately slipped into version 1.0.1.

8.1.2 1.0.1

• Fix error in admin interface for ‘‘Notification‘‘detail view.


• Refactor all internal model checks to use classmethod check() provided by Django.
• Changed the field type of quantity in shop.models.defaults.cart_item.CartItem
and shop.models.defaults.order_item.OrderItem from IntegerField to
PositiveIntegerField.

8.1.3 1.0

• Replace various files containing Python requirements against Pipfile to be used by pipenv.
• Migrated all default templates to use Bootstrap-4 and replace all tables using the HTML tag <table> against
flex elements.
• Switch to py.test in favor of Django test-cases.
• It now is possible to override the forms for selecting the payment-, shipping- and extra annotation using a
configuration directive.
• Adopted to django-CMS version 3.5.
• Fix all compatibility issues with Django-1.11.

141
djangoSHOP, Release 1.0.2

• Fix all compatibility issues with Django REST framework 3.8.


• Upgrade to angular-ui-bootstrap version 2.5. This requires djangocms-cascade version 0.17.x and a slight mod-
ification of the navbar rendering.
• Add Order number to Order List View.
• It is possible to access the Order Detail View anonymously by using a secret in the URL.
• Remove directory example in favor of the new project cookiecutter-django-shop.
• Customized Template Engine which keeps track on referenced images and stores then as attachments to be used
in multipart email messages. This requires a patched version of django-post_office.
• Add relatated_name to fields delivery and item to the model Delivery. Check your reverse rela-
tions.
• Added an apphook PasswordResetApp, so that all pages, even those to reset the password, can now be
handled by a page by the CMS.
• Pagination of catalog list view can distinguish between auto-infinte, manual-infinte and pagination.
• Pagination of catalog list view prevents widow items.
• Cart widget displays a short summary of products after adding a product, or mouse-over event.
• AddToCart now optionally renders a modal dialog after adding the product.
• All forms in the checkout process can be overridden using a settings variable.
• Buttons are configurable to be disabled, if wrapping form is invalid.
• Unified all management commands into shop with different subcommands.
• Add management command shop check-pages to verify mandatory and recommended CMS pages.
• Add management command shop review-settings to verify the configuration settings.
• Refactored payment- and shipping-modifiers into their own submodules, so that they stay side-by-side with their
order workflow mixins.
• All payment- and shipping-modifiers support an instantiation either as list or as instance. This allows to imple-
ment payment- or shipping-service-provider offering different payment- or shipping methods themselves.
• Changed all relative import against absolute ones.
• In context for email template rendering, renamed data to a more meaningful name such as order.
• Add support for inlined images when sending HTML emails.
• Replace FSM signal post_transition against a function transition_change_notification
which either is invoked by OrderAdmin.save_model() or while processing an Order through the frontend
by the customer.
• In Order event notification, add data about each delivery to the serialized Order data.
• Upgrade to djangocms-bootstrap version 1.0.2.
• Fix: Do not always refetch cart data from server.
• Improve style of rendering for invoice and delivery notes in the Order backend.
• Use specific naming for relatation of model DeliveryItem to models OrderItem and Delivery.
• Add reusable scroll-spy for AngularJS directive navbar.

142 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

8.1.4 0.12.2

• Fix #729: Issue with Notification admin transition choices (RETURN_VALUE).


• Adopted templates to be used by angular-ui-bootstrap version 2.5.
• Compatible with django-CMS version 3.5.

8.1.5 0.12.1

• Fix: #724: broken amount rendering when USE_TOUSAND_SEPARATOR is True.


• Adopt shoplinkplugin.js to use function initializeLinkTypes as required by djangocms-
cascade version 0.16.

8.1.6 0.13

• Drop support for Django-1.9, add support for Django-1.11.


• Add method get_weight() to product model, so that a cart modifier may sum up the product weights.
• Configured Cart modifiers may be a list, rather than a single instance.
• Refactor shipping and payment modifiers in shop/modifiers/defaults.py into their own files shop/
shipping/modifiers.py and shop/payment/modifiers.py.
• Refactor shipping workflows in shop/shipping/base.py and shop/shipping/defaults.py into
their own file shop/shipping/workflows.py. Extract TRANSITION_TARGETS into their common
base class.
• Refactor payment workflows in shop/payment/base.py and shop/shipping/defaults.py into
their own file shop/payment/workflows.py.
• Remove unused class ShippingProvider.
• Add support for SendCloud integration.
• When partial delivery is configured, it now is possible to create multiple deliveries concurrently.
• Add configuration directive SHOP_MANUAL_SHIPPING_ID which shall be used to make the input field for
the “Shipping ID” readonly.
• Add configuration directive SHOP_OVERRIDE_SHIPPING_METHOD which shall be used to allow the mer-
chant to choose another shipping provider, instead of that selected by the customer.
• Model DeliveryItem was moved into shop.models.defaults.delivery_item to prevent acci-
dental instantiation.
• Add OrderPaymentInline to OrderAdmin only, if status requires a payment or a refund.
• In OrderAdmin add tick to inform about a fullfilled Order payment.
• In ManualPaymentWorkflowMixin unified methods prepayment_partially_deposited() and
prepayment_fully_deposited() into method payment_deposited().
• Add method __str__() to model BaseDelivery.
• All models which can be used in the DialogForm, can offer a method as_text() which may render a nicely
formatted representation of its content.
• Add method reorder_form_fields to Customer model, so that inheriting models can fix the order of form
fields.

8.1. Changelog for django-SHOP 143


djangoSHOP, Release 1.0.2

8.1.7 0.12

• Adopted for django-angular version 2.0, which breaks its API. Invalid forms rejected by the server are send with
a status code of 422 now. Check their changelog for details.
• Adopted to AngularJS-1.6.6, which required to replace all .success() handlers against a promise .then().
• RESTifyed the communication with the server, by using HTTP methods PUT and DELETE where appropriate.
• Rename PayInAdvanceWorkflowMixin to ManualPaymentWorkflowMixin, since its purpose is to
handle all incoming/outgoing payments manually.
• Move LeftExtensionPlugin and RightExtensionPlugin into module shop/cascade/
extensions and allow them to be used on the ShopOrderViewsPlugin as well.
• Refactored ShopReorderButtonPligin and ShopOrderAddendumFormPlugin to use the new
djng-forms-set directive, as provided by django-angular version 2.0.
• ShopOrderAddendumFormPlugin can optionally render historical annotations for the given order.
• Added hook methods cancelable() and refund_payment() to BaseOrder to allow a better order
cancelling interface.
• Paid but unshipped orders, now can be refunded. Possible be refactoring class
CancelOrderWorkflowMixin, which handles payment refunds.
• Add Order status to Order Detail View, so that the customer immediately sees what’s going on.
• Reject method POST on Order List View.
• Fix: On re-add item to cart, use product_code to identify if that product already exists in cart.
• Do not render buttons and links related to the watch-list, when it is not available.
• Use Sekizai’s templatetags {% add_data %} and {% with_data %} instead of Sekizai’s post-
processors djng.sekizai_processors.module_config and djng.sekizai_processors.
module_list, which now are deprecated.
• Remove HTTP-Header X-HTTP-Method-Override and use PUT and DELETE requests natively.
• Remove django-angular dependency djng.url from project.
• Endpoints in JavaScript are always referenced through HTML. This eliminates the need for 'djng.
middleware.AngularUrlMiddleware' in MIDDLEWARE_CLASSES of your settings.py.
• Use Django’s internal password validator configuration AUTH_PASSWORD_VALIDATORS in your
settings.py.
• Refactored all templates for authentication forms to simplify inheritance and to use the promise chain (offered
by django-angular 2.0). This allows to do fine-grained adoptions in the submit buttons behaviour.
• Decoupled all checkout forms. They don’t require dialog.js, forms-sets.js and auth.js anymore.
Instead use the functionality provided by django-angular 2.0 form directives.
• Use a REST endpoint to add, modify and delete multiple shipping and billing addresses. This simplifies the
address forms. Remove shipping-address.js and replace it against a more generic address.js.
• Use an event broadcast shop.carticon.caption to inform the carticon about changes in the cart.
• Add an overridable CartIconCaptionSerializer to specify what to render in the cart-icon.
• Use event broadcasting to inform the checkout forms if configured in summary mode. This decouples checkout
form updates, from rendering their summary on another page or process step.
• Add operator to test Money type against booleans.

144 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

• Fix: Adopt polymorphic ModelAdmin-s to django-polymorphic>=1.0.


• Add to ShopProceedButton: Disable button if any form in this set is invalid.
• Use vanilla Javascript in serverside JS-expressions.
• Decoupled CheckoutViewSet from CartViewSet, so that the checkout only handles forms relevant to
the checkout process.
• Endpoint digest in CheckoutViewSet, returns a full description of all forms, plus the current cart’s con-
tent. Fetching from there is emit a shop.checkout.digest event.
• Added directives shop-payment-method and shop-shipping-method which update the cart and emit
a shop.checkout.digest event on change.
• Fix: All form input field get their own unique HTML id. Previously some id’s were used twice and caused
collisions.
• Fix: Do not rebuild list of cart items, on each change of quantity.
• Separate CartController into itself and a CartItemControler.
• Consistent naming of emit and broadcast events.
• Introduce CartSummarySerializer to retrieve a smaller checkout digest.
• In Shipping- and Payment Method Form, optionally show additional charges below the radio fields, depending
on the selected method.
• Remove angular-message from the list of npm dependencies.
• Fix: Products with active=False are exempted from the catalog list views and accessing them raises a Not
Found page.

8.1.8 0.11.7

• Fix: Python3 can not handle None type in max() function.


• Smoother animation when showing Payment form.

8.1.9 0.11.6

• Fix #708: Passing None when calling django.template.loader.select_template in shop/


cascade/catalog.py.

8.1.10 0.11.5

• Fix: Money formatter did not work for search results.


• Image building uses docker-compose with official images instead of a crafted Dockerfile.

8.1.11 0.11.4

• Fix: Template context error while rendering Order List-View as Visitor.


• Fix: Money formatter to allow the usage of the thousand separator.
• Fix: It now is possible to use the ProductListView as the main CMS landing page.

8.1. Changelog for django-SHOP 145


djangoSHOP, Release 1.0.2

• Fix: Template exception if left- or right extension was missing on the OrderList and/or OrderDetail
view.
• Add option to Catalog List View: It now is possible to redirect automatically onto a lonely product.
• Add options to override the add-to-cart template when using the appropriate CMS Cascade plugin.
• Add option to add a list of products to the navigation node serving a catalog list page.
• Upgrade external dependencies to their latest compatible versions.

8.1.12 0.11.3

• Fix: Problems with missing Left- and Right Extension Plugin.


• Ready for Django-1.11 if used with django-CMS-3.4.5
• Ready for django-restframework-3.7
• Tested with recent versions of other third party libraries.
• Fix issues with enum types when importing fixtures.
• Add Swedish Kronor to currencies.

8.1.13 0.11.2

• Do not render buttons and links related to the watch-list, when it is not available.
• Fix: Adopt polymorphic ModelAdmin-s to django-polymorphic>=1.0.
• Use Sekizai’s internal templatetags {% with_data ... %} and {% with_data %} to render
Sekizai blocks ng-requires and ng-config rather than using the deprecated postprocessors djng.
sekizai_processors.module_list and djng.sekizai_processors.module_config.
Adopt your templates accordingly as explained in Client Side Framework.
• Rename PayInAdvanceWorkflowMixin to ManualPaymentWorkflowMixin, since its purpose is to
handle all incoming/outgoing payments manually.
• Move LeftExtensionPlugin and RightExtensionPlugin into module shop/cascade/
extensions and allow them to be used on the ShopOrderViewsPlugin as well.
• ShopOrderAddendumFormPlugin can optionally render historical annotations for the given order.
• Added hook methods cancelable() and refund_payment() to BaseOrder to allow a better order
cancelling interface.
• Paid but unshipped orders, now can be refunded. Possible be refactoring class
CancelOrderWorkflowMixin, which handles payment refunds.
• Add Order status to Order Detail View, so that the customer immediately sees what’s going on.
• Add support for Python-3.6.

8.1.14 0.11.1

• Fix migration 0007_notification to handle field mail_to correctly.


• Allow transition to cancel order only for special targets.
• Add operator to test Money type against booleans.

146 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

8.1.15 0.11

• Fix: shop.rest.renderers.CMSPageRenderer always uses the template offered by the CMS page,
rather than invoking method get_template_names() from the corresponding APIView class.
• Feature: Add class:shop.rest.renderers.ShopTemplateHTMLRenderer which is the counterpart of shop.rest.
renderers.CMSPageRenderer, usable for hardcoded Django views.
• Refactor: In examples polymorphic and i18n_polymorphic, renamed SmartPhone to
SmartPhoneVariant.
• Feature: In shop.money.fields.MoneyFormField use a widget which renders the currency.
• Refactor: In shop.money.fields.MoneyField, drop support for implicit default value, since it causes
more trouble than benefit.
• Fix: Handle non-decimal types in shop.money.fields.MoneyField.get_db_prep_save().
• Fix: In AngularJS, changes on filters and the search field did not work on Safari.
• Fix: In shop.views.auth.AuthFormsView.post() create a customer object from request for a visit-
ing customers, rather than responding with BAD REQUEST.
• Fix: shop.models.order.OrderManager.get_summary_url() only worked for views rendered
as CMS page. Now it also works for static Django views.
• Simplified all methods get_urls() from all classes derived from CMSApp by exploiting CMS-PR 5898
introduced with django-CMS-3.4.4.
• Remove field customer from shop.serializers.order.OrderListSerializer, since it in-
terfered with the customer object on the global template_context namespace, causing template
shop/navbar/login-logout.html to fail.
• Management command fix_filer_bug_965 is obsolete with django-filer-1.2.8.
• Fix: Use caption in Order Detail View.
• Add Leaflet Map plugin from djangocms-cascade for demonstration purpose.
• Moved package.json into example/package.json (and with it node_modules) since it shall be
part of the project, rather than the Django app.
• Fix: In shop.models.order.BaseOrderItem.populate_from_cart_item() the
unit_price is takes from the cart_item, rather than beeing recalculated.
• shop.cascade.cart.ShopCartPlugin accepts two children: ShopLeftExtension and
ShopRightExtension which can be used to add plugins inside the cart’s table footer.
• In shop.models.notification.Notification renamed field mail_to to recipient and con-
verted it to a ForeignKey. Added an enum field notify to distinguish between different kinds of recipients.
• Refactored CustomerStateField into a reusable shop.models.fields.ChoiceEnumField
which can be used for both, Notify as well as CustomerState.
• Adopted to djangocms-cascade version 0.14, which allows to render static pages using plugin descriptions in
JSON.
• Added Paginator to Order List View.
• Refactored shop.app_settings into shop.conf.app_settings to be usable by Sphinx in doc-
strings.
• Added shop.models.order.BaseOrder.get_all_transitions() which returns all possible
transitions for the the Order class.

8.1. Changelog for django-SHOP 147


djangoSHOP, Release 1.0.2

• In shop.rest.renderers.ShopTemplateHTMLRenderer do not pollute template_context


with serialized data on the root level.
• Fix #623: Template auth/register-user.html did not validate properly, when Reset password was
checked.
• Added AngularJS filter range to emulate enumerations in JavaScript.
• Fallback to hard-coded URL if CMS page for “Continue Shopping” is missing.

8.1.16 0.10.2

• Fixed migration error in 0004_ckeditor31.py.


• Fixed #554: Email is no longer created when notification is triggered.
• Fixed: Using a ManyToManyField through ProductPage ignores the blank attribute, when saving a prod-
uct in the admin backend.
• Hard code “Cart” into tooltip for cart icon, until https://github.com/divio/django-cms/issues/5930 is fixed.
• Renders a nicer summary when rendering a multiple address form.
• Fixed: When placeholder is None raises AttributeError.

8.1.17 0.10.1

• Fixed #537 and #539: Rendering data in template has different results after upgrading to 0.10.

8.1.18 0.10.0

• In the backend, OrderAdmin and OrderItemAdmin may render the dictionary extra from their associated
models using a special template.
• In OrderAdmin use methods get_fields() and get_readonly_fields() as intended.
• Tested with Django-1.10. Drop support for Django-1.8.
• If an anonymous customer logs in, his current cart is merged with a cart, which has previously been created.
This has been adopted to re-use the method Product.is_in_cart() in and finds it’s Merge the contents of the other
cart into this one, afterwards delete it.
• Moved field salutation from shop.models.customer.BaseCustomer into the merchant imple-
mentation. If your project does not use the provided default customer model shop.models.defaults.
customer.Customer, then you should add the salutation field to your implementation of the Customer
model, if that makes sense in your use-case.
• Refactored the defaults settings for shop using an AppSettings object.
• Refactored all serializers into their own folder shop/serializers with submodules bases.
py, cart.py, order.py and defaults.py. The serializers CustomerSerializer,
ProductSummarySerializer and OrderItemSerializer now are configurable through the
application settings.
• AngularJS directive <shop-auth-form ...> now listens of the event “pressed ENTER key” and submits
the form data accordingly.
• Upgraded to AngularJS version 1.5.9.
• HTML5 mode is the default now.

148 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

• The previously required additional endpoint for the autocomplete search, can now be be merged into the
same endpoint as connected to the catalog’s list view. This has been made possible by the wrapper shop.
search.views.CMSPageCatalogWrapper which dispatch incoming requests to either the shop.
views.catalog.ProductListView or, for search queries to shop.search.views.SearchView.
• Added choice option “Infinite Scroll” to the Cascade plugins Catalog List View and Search Results. They
influence if the paginator is rendered or trigger an event to load more results from the server.
• Changed all Cascade plugins to follow the new API introduced in djangocms-cascade version 0.12.
• Directive shop-product-filter must be member of a <form ...> element.
• Unified the plugins ShippingAddressFormPlugin and BillingAddressFormPlugin into one plugin named
CheckoutAddressPlugin, where the merchant can choose between the shipping- or billing form.
• Refactored shop.forms.checkout.AddressForm and fixed minor bugs when editing multiple ad-
dresses.
• In address models, replaced CharField for country against a special CountryField.
• Change value of BaseShippingAddress.address_type to shipping and
BaseBillingAddress.address_type to billing.
• Method shop.models.order.OrderManager.get_latest_url() falls back to
reverse('shop-order-last') if no such page with ID shop-order-last was found in the
CMS.
• Use menu_title instead of page title for link and tooltip content.
• In DialogForm, field plugin_id is not required anymore.
• After a new customer recognized himself, the signal customer_recognized is fired so that other apps can
act upon.
• Unified ProductCommonSerializer, ProductSummarySerializer and
ProductDetailSerializer into a single ProductSerializer, which acts as default for the
ProductListView and ProductRetrieveView.
• Dependency to djangocms-cascade is optional now.
• Added alternative compressor for {% render_block "js/css" "shop.sekizai_processors.
compress" %} which can handle JS/CSS files provided using {% addtoblock "js/css" ... %}
even if located outside the /static/ folders.
• Added method post_process_cart_item to the Cart Modifiers.
• In CartItem the product_code is mandatory now. It moves from being optionally kept in dict
CartItem.extra into the CartItem model itself. This simplifies a lot of boilerplate code, otherwise
required by the merchant implementation. Please read Release notes for version 0.10 for details.
• In shop.models.product.BaseProduct added a hook method get_product_variant(self,
**kwargs) which can be overridden by products with variations to return a product variant.

8.1.19 0.9.3

• Added template context processor shop.context_processors.ng_model_options() to add the


settings EDITCART_NG_MODEL_OPTIONS and ADD2CART_NG_MODEL_OPTIONS. Please check your
templates to see, if you still use ng_model_options.
• Allows to add children to the CartPlugin. These children are added to the table foot of the rendered cart.
• Added AngularJS directive <ANY shop-forms-set> which can be used as a wrapper, when the proceed
button shall be added to a page containing <form ...> elements with built in validation.

8.1. Changelog for django-SHOP 149


djangoSHOP, Release 1.0.2

• All Cascade plugins use GlossaryField instead of a list of PartialFormField s. This is much more
“Djangonic”, but requires djangocms-cascade version 0.11 or later.
• All urlpatterns are compatible with configurations adding a final / to the request URL.
• The URL for accessing an Order object, now uses the order number instead of it’s primary key.

8.1.20 0.9.2

• Minimum required version of django-filer is now 1.2.5.


• Minimum required version of djangocms-cascade is now 0.10.2.
• Minimum required version of djangoshop-stripe is now 0.2.0.
• Changed the default address models to be more generic. Please read the Release notes for version 0.9 if you are
upgrading from 0.9.0 or 0.9.1.
• Fixed shop.money.fields.decontruct() to avoid repetitive useless generation of migration files.
• Using cached_property decoration for methods unit_price and line_total in shop.models.
order.OrderItem.
• Fixed #333: Accessing the cart when there is no cart associated with a customer.
• Removed Apphook shop.cms_apps.OrderApp. This class now must be added to the
project’s cms_apps.py. This allows the merchant to override the shop.rest.serializers.
OrderListSerializer and shop.rest.serializers.OrderDetailSerializer.
• Bugfix: declared django-rest-auth as requirement in setup.py.
• Refactored shop.models.deferred -> shop.deferred. This allows to add a check for pending mappings into the
ready-method of the shop’s AppConfig.
• Prepared for Django-1.10: Replaced all occurrences of django.conf.urls.patterns() by a simple list.
• Method get_render_context in classes extending from django_filters.FilterSet now must be
a classmethod accepting a request object and the querystring.
• Method get_renderer_context in class CMSPageProductListView now fetches the rendering con-
text for filtering after the queryset have been determined. This allows us to adopt the context.
• Function loadMore() in CatalogListController bypasses the existing search query. This allows to
use hard coded links for tag search.
• Using Python’s Enum class to declare customer states, such as UNRECOGNIZED, GUEST or REGISTERED.
• Created a customized database field to hold the customers states, as stored by the above Enum.
• Fixed: A server-side invalidated email addresses was accepted anyway, causing problems for returning cus-
tomers.
• Renamed CMS Page IDs for better consistency: * personal-details -> shop-customer-details
to access the Customer Detail Page. * reset-password -> shop-password-reset to access the Reset
Password Page. * new: shop-register-customer to access the Register User Page.
• Moved all non-Python dependencies from bower_components into node_modules.
• The breadcrumb now is responsible itself for being wrapped into a Bootstrap container.
• Use Sekizai processors from django-angular. Replaced shop-ng-requires against ng-requires and
shop-ng-config against ng-config.

150 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

8.1.21 0.9.1

• Support for Python 3


• Support for Django-1.9
• Added abstract classes class:shop.models.delivery.BaseDelivery and class:shop.models.delivery.BaseDeliveryItem
for optional partial shipping.

8.1.22 0.9.0

• Separated class:shop.views.catalog.ProductListView into its base and the new class


class:shop.views.catalog.CMSPageProductListView which already has added it appropriate filters.
• Moved wsgi.py into upper folder.
• Prototype of shop.cascade.DialogFormPluginBase.get_form_data changed. It now accepts
context, instance and placeholder.
• Fixed: It was impossible to enter the credit card information for Stripe and then proceed to the next step. Using
Stripe was possible only on the last step. This restriction has gone.
• It now also is possible to display a summary of your order before proceeding to the final purchasing step.
• To be more Pythonic, class:shop.models.cart.CartModelManager raises a DoesNotExist exception instead
of None for visiting customers.
• Added method filter_from_request to class:shop.models.order.OrderManager.
• Fixed: OrderAdmin doesn’t ignores error if customer URL can’t be resolved.
• Fixed: Version checking of Django.
• Fixed: Fieldsets duplication in Product Admin.
• CartPlugin now can be child of ProcessStepPlugin and BootstrapPanelPlugin.
• Added ShopAddToCartPlugin.
• All Checkout Forms now can be rendered as editable or summary.
• All Dialog Forms now can declare a legend.
• In DialogFormPlugin, method form_factory always returns a form class instead of an error dict if form
was invalid.
• Added method OrderManager.filter_from_request, which behaves analogous to CartManager.
get_from_request.
• Fixed lookups using MoneyField by adding method get_prep_value.
• Dropped support for South migrations.
• Fixed: In ProductIndex, translations now are always overridden.
• Added class SyncCatalogView which can be used to synchronize the cart with a catalog list view.
• Content of Checkout Forms is handled by a single transaction.
• All models such as Product, Order, OrderItem, Cart, CartItem can be overridden by the merchant’s implemen-
tation. However, we are using the deferred pattern, instead of configuration settings.
• Categories must be implemented as separate django-SHOP addons. However for many implementations pages
form the django-CMS can be used as catalog list views.

8.1. Changelog for django-SHOP 151


djangoSHOP, Release 1.0.2

• The principle on how cart modifiers work, didn’t change. There more inversion of control now, in that sense,
that now the modifiers decide themselves, how to change the subtotal and final total.
• Existing Payment Providers can be integrated without much hassle.

8.1.23 Since version 0.2.1 a lot of things have changed. Here is a short summary:

• The API of django-SHOP is accessible through a REST interface. This allows us to build MVC on top of that.
• Changed the two OneToOne relations from model Address to User, one was used for shipping, one for billing.
Now abstract BaseAddress refers to the User by a single ForeignKey giving the ability to link more than one
address to each user. Additionally each address has a priority field for shipping and invoices, so that the latest
used address is offered to the client.
• Replaced model shop.models.User by the configuration directive settings.AUTH_USER_MODEL, to be
compliant with Django documentation.
• The cart now is always stored inside the database; there is no more distinction between session based carts and
database carts. Carts for anonymous users are retrieved using the visitor’s session_key. Therefore we don’t need
a utility function such get_or_create_cart anymore. Everything is handled by the a new CartManager,
which retrieves or creates or cart based on the request session.
• If the quantity of a cart item drops to zero, this items is not automatically removed from the cart. There are
plenty of reasons, why it can make sense to have a quantity of zero.
• A WatchList (some say wish-list) has been added. This simply reuses the existing Cart model, where the item
quantity is zero.
• Currency and CurrencyField are replaced by Money and MoneyField. These types not only store the amount,
but also in which currency this amount is. This has many advantages:
– An amount is rendered with its currency symbol as a string. This also applies for JSON data-structures,
rendered by the REST framework.
– Money types of different currencies can not be added/substracted by accident. Normal installations woun’t
be affected, since each shop system must specify its default currency.
• Backend pools for Payment and Shipping have been removed. Instead, a Cart Modifier can inherit from
PaymentModifier or ShippingModifier. This allows to reuse the Cart Modifier Pool for these back-
ends and use the modifiers logic for adding extra rows to he carts total.
• The models OrderExtraRow and OrderItemExtraRow has been replaced by a JSONField extra_rows
in model OrderModel and OrderItemModel. OrderAnnotation now also is stored inside this extra
field.
• Renamed for convention with other Django application:
– date_created -> created_at
– last_updated -> updated_at
– ExtraOrderPriceField -> BaseOrderExtraRow
– ExtraOrderItemPriceField -> BaseItemExtraRow

8.1.24 Version 0.2.1

This is the last release on the old code base. It has been tagged as 0.2.1 and can be examined for historical reasons.
Bugs will not be fixed in this release.

152 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

8.1.25 Version 0.2.0

• models.FloatField are now automatically localized.


• Support for Django 1.2 and Django 1.3 dropped.
• Product model now has property can_be_added_to_cart which is checked before adding the product to
cart
• In cart_modifiers methods get_extra_cart_price_field and get_extra_cart_item_price_field
accepts the additional object request which can be used to calculate the price according to the state of a
session, the IP-address or whatever might be useful. Note for backwards compatibility: Until version 0.1.2,
instead of the request object, an empty Python dictionary named state was passed into the cart modifiers.
This state object could contain arbitrary data to exchange information between the cart modifiers. This
Python dict now is a temporary attribute of the request object named cart_modifier_state. Use it
instead of the state object.
• Cart modifiers can add an optional data field beside label and value for both, the ExtraOrderPriceField
and the ExtraOrderItemPriceField model. This extra data field can contain anything serializable as JSON.

8.1.26 Version 0.1.2

• cart_required and order_required decorators now accept a reversible url name instead and redirect to cart by
default
• Added setting SHOP_PRICE_FORMAT used in the priceformat filter
• Separation of Concern in OrderManager.create_from_cart: It now is easier to extend the Order class with cus-
tomized data.
• Added OrderConfirmView after the shipping backend views that can be easily extended to display a confirmation
page
• Added example payment backend to the example shop
• Added example OrderConfirmView to the example shop
• Unconfirmed orders are now deleted from the database automatically
• Refactored order status (requires data migration)
– removed PAYMENT and added CONFIRMING status
– assignment of statuses is now linear
– moved cart.empty() to the PaymentAPI
– orders now store the pk of the originating cart
• Checkout process works like this:
1. CartDetails
2. CheckoutSelectionView
– POST –> Order.objects.create_from_cart(cart) removes all orders originating from this cart
that have status < CONFIRMED(30)
– creates a new Order with status PROCESSING(10)
3. ShippingBackend
– self.finished() sets the status to CONFIRMING(20)

8.1. Changelog for django-SHOP 153


djangoSHOP, Release 1.0.2

4. OrderConfirmView
– self.confirm_order() sets the status to CONFIRMED(30)
5. PaymentBackend
– self.confirm_payment() sets the status to COMPLETED(40)
– empties the related cart
6. ThankYouView
– does nothing!

8.1.27 Version 0.1.1

• Changed CurrencyField default decimal precision back to 2

8.1.28 Version 0.1.0

• Bumped the CurrencyField precision limitation to 30 max_digits and 10 decimal places, like it should have been
since the beginning.
• Made Backends internationalizable, as well as the BillingShippingForm thanks to the introduciton of a new
optional backend_verbose_name attribute to backends.
• Added order_required decorator to fix bug #84, which should be used on all payment and shipping views
• Added cart_required decorator that checks for a cart on the checkout view #172
• Added get_product_reference method to Product (for extensibility)
• Cart object is not saved to database if it is empty (#147)
• Before adding items to cart you now have to use get_or_create_cart with save=True
• Changed spelling mistakes in methods from payed to paid on the Order model and on the API. This is potentially
not backwards compatible in some border cases.
• Added a mixin class which helps to localize model fields of type DecimalField in Django admin view.
• Added this newly created mixin class to OrderAdmin, so that all price fields are handled with the correct local-
ization.
• Order status is now directly modified in the shop API
• CartItem URLs were too greedy, they now match less.
• In case a user has two carts, one bound to the session and one to the user, the one from the session will be used
(#169)
• Fixed circular import errors by moving base models to shop.models_bases and base managers to
shop.models_bases.managers

8.1.29 Version 0.0.13

(Version cleanup)

154 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

8.1.30 Version 0.0.12

• Updated translations
• Split urls.py into several sub-files for better readability, and put in a urls shubfolder.
• Made templates extend a common base template
• Using a dynamically generated form for the cart now to validate user input. This will break your cart.html
template. Please refer to the changes in cart.html shipped by the shop to see how you can update your own
template. Basically you need to iterate over a formset now instead of cart_items.
• Fixed a circular import problem when user overrode their own models

8.1.31 Version 0.0.11

• Performance improvement (update CartItems are now cached to avoid unnecessary db queries)
• Various bugfixes

8.1.32 Version 0.0.10

• New hooks were added to cart modifiers: pre_process_cart and post_process_cart.


• [API change] Cart modifiers cart item methods now recieve a state object, that allows them to pass information
between cart modifiers cheaply.
• The cart items are not automatically saved after process_cart_item anymore. This allows for cart modifiers that
change the cart’s content (also deleting).
• Changed the version definition mechanism. You can now: import shop; shop.__version__. Also, it now con-
forms to PEP 386
• [API Change] Changed the payment backend API to let get_finished_url and get_cancel_url return strings in-
stead of HttpResponse objects (this was confusing)
• Tests for the shop are now runnable from any project
• added URL to CartItemView.delete()

8.1.33 Version 0.0.9

• Changed the base class for Cart Modifiers. Methods are now expected to return a tuple, and not direectly append
it to the extra_price_fields. Computation of the total is not done using an intermediate “current_total” attribute.
• Added a SHOP_FORCE_LOGIN setting that restricts the checkout process to loged-in users.

8.1.34 Version 0.0.8

• Major change in the way injecting models for extensibility works: the base models are now abstract, and the shop
provides a set of default implementations that users can replace / override using the settings, as usual. A special
mechanism is required to make the Foreign keys to shop models work. This is explained in shop.utils.loaders

8.1. Changelog for django-SHOP 155


djangoSHOP, Release 1.0.2

8.1.35 Version 0.0.7

• Fixed bug in the extensibility section of CartItem


• Added complete German translations
• Added verbose names to the Address model in order to have shipping and billing forms that has multilingual
labels.

8.1.36 Version 0.0.6

(Bugfix release)
• Various bugfixes
• Creating AddressModels for use with the checkout view (the default ones at least) were bugged, and would
spawn new instances on form post, instead of updating the user’s already existing ones.
• Removed redundant payment method field on the Order model.
• The “thank you” view does not crash anymore when it’s refreshed. It now displays the last order the user placed.
• Fixed a bug in the shippingbilling view where the returned form was a from class instead of a from instance.

8.1.37 Version 0.0.5

• Fix a bug in 0.0.4 that made South migration fail with Django < 1.3

8.1.38 Version 0.0.4

• Addresses are now stored as one single text field on the Order objects
• OrderItems now have a ForeignKey relation to Products (to retrieve the product more easily)
• New templatetag (“products”)
• Made most models swappable using settings (see docs)
• Changed checkout views. The shop uses one single checkout view by default now.
• Created new mechanism to use custom Address models (see docs)
• Moved all Address-related models to shop.addressmodel sub-app
• Removed Client Class
• Removed Product.long_description and Product.short_description from the Product superclass
• Bugfixes, docs update

8.1.39 Version 0.0.3

• More packaging fixes (missing templates, basically)

8.1.40 Version 0.0.2

• Packaging fix (added MANIFEST.in)

156 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

8.1.41 Version 0.0.1

• Initial release to Pypi

8.2 Release Notes

8.2.1 Release notes for version 0.9

0.9.2

The default address models have changed in 0.9.2. If you are upgrading from 0.9.0 or 0.9.1 and your project is using
the default address models, you need to add a migration to make the necessary changes to your models:
./manage.py makemigrations --empty yourapp

Next, edit the migration file to look like this:


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

class Migration(migrations.Migration):

dependencies = [
# makemgirations will generate the dependencies for you.
]

operations = [
migrations.RenameField("ShippingAddress", "addressee", "name"),
migrations.RenameField("ShippingAddress", "street", "address1"),
migrations.RenameField("ShippingAddress", "supplement", "address2"),
migrations.RenameField("ShippingAddress", "location", "city"),

migrations.AlterField("ShippingAddress", "name", models.CharField(


verbose_name="Full name", max_length=1024
)),
migrations.AlterField("ShippingAddress", "address1", models.CharField(
verbose_name="Address line 1", max_length=1024
)),
migrations.AlterField("ShippingAddress", "address2", models.CharField(
verbose_name="Address line 2", max_length=1024
)),
migrations.AlterField("ShippingAddress", "city", models.CharField(
verbose_name="City", max_length=1024
)),

migrations.RenameField("BillingAddress", "addressee", "name"),


migrations.RenameField("BillingAddress", "street", "address1"),
migrations.RenameField("BillingAddress", "supplement", "address2"),
migrations.RenameField("BillingAddress", "location", "city"),

migrations.AlterField("BillingAddress", "name", models.CharField(


verbose_name="Full name", max_length=1024
(continues on next page)

8.2. Release Notes 157


djangoSHOP, Release 1.0.2

(continued from previous page)


)),
migrations.AlterField("BillingAddress", "address1", models.CharField(
verbose_name="Address line 1", max_length=1024
)),
migrations.AlterField("BillingAddress", "address2", models.CharField(
verbose_name="Address line 2", max_length=1024
)),
migrations.AlterField("BillingAddress", "city", models.CharField(
verbose_name="City", max_length=1024
)),
]

Finally, apply the migration:

./manage.py migrate yourapp

0.9.3

This version requires djangocms-cascade 0.11.0 or higher. Please ensure to run the migrations which convert the
Cascade elements:

./manage.py migrate shop

8.2.2 Release notes for version 0.10

Upgrading to 0.10

This version requires django-CMS version 3.4.2 or higher and djangocms-cascade version 0.12.0 or higher. It is well
tested with Django-1.10 but should work as well with Django-1.9.
There has been a lot of effort in getting a cleaner and more consistent API. If you upgrade from version 0.9 please
note the following changes:
The REST serializers have been moved into their own submodule shop.serializers. They now are separated
into bases and defaults following the same naming convention as beeing used in shop.models and shop.
admin. Please ensure that you change your import statements.
Serializers ProductCommonSerializer, ProductSummarySerializer and
ProductDetailSerializer have been unified into a single ProductSerializer, which acts as de-
fault for the ProductListView and the ProductRetrieveView. The ProductSummarySerializer
(which is used to serialize attributes available across all products of the site) now must be configured using the settings
directive SHOP_PRODUCT_SUMMARY_SERIALIZER.
All Angular directives have been checked for HTML5 mode compatibility. It is strongly recommended over hashbang
mode.
Billing and shipping address have been unified into one single address form which makes them easier to interchange.
The salutation field has been removed from the address model and can now optionally be added to the merchant
representation.
All AngularJS directives for the catalog list and catalog search view support infinite scroll, as well as manual pagina-
tion.
After upgrading to angular-ui-bootstrap version 0.14, all corresponding directives have to be prefixed with uib-..
..

158 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

There is no more need for a special URL pattern to handle auto-completion search. Instead use the wrapping view
shop.search.views.CMSPageCatalogWrapper.
The model CartItem has a new CharField product_code. This replaces the product_code, which option-
ally is kept inside its extra dict. This requires to simplify some templates implementing {{ somevar.extra.
product_code }} into {{ somevar.product_code }}; it applies to the cart, the add-to-cart and the order
templates. Also check for ProductSerializer-s implemented for products with variations.
Look for methods implementing get_product_variant since its signature changed.
requires a database migration by the merchant implementation. Such a migration file must contain a datamigration,
for instance:

from __future__ import unicode_literals

from django.db import migrations, models

def forwards(apps, schema_editor):


CartItem = apps.get_model('myshop', 'CartItem')
for item in CartItem.objects.all():
item.product_code = item.extra.get('product_code', '')
item.save()

def backwards(apps, schema_editor):


CartItem = apps.get_model('myshop', 'CartItem')
for item in CartItem.objects.all():
item.extra['product_code'] = item.product_code
item.save()

class Migration(migrations.Migration):

dependencies = [
('myshop', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='cartitem',
name='product_code',
field=models.CharField(blank=True, help_text='Product code of added item.
˓→', max_length=255, null=True, verbose_name='Product code'),

),
migrations.RunPython(forwards, reverse_code=backwards),
]

8.2.3 Release notes for version 0.11

Since some payment providers require to have an existing order object before the payment is fulfilled, method
OrderManager.create_from_cart() does not invoke the method order.populate_from_cart()
anymore. This now must be performed by the payment service provider in two steps:

order = OrderModel.objects.create_from_cart(cart, request)


order.populate_from_cart(cart, request)

8.2. Release Notes 159


djangoSHOP, Release 1.0.2

8.3 Frequently Asquest Questions

8.3.1 Frontend Editing

Order Plugin

Editing the Order Plugin seems to be broken


This has to do with the way, django-CMS handles the placeholder in its templates. Here the problem is, that we’re
using the same template for both, the Order List View and their Detail Views. The Order Detail Views however, are
not managed by the CMS, but rather by a CMSApphook. The latter confuses the CMS placeholder. It therefore is
strongly recommended to edit the Order Page only while in List View Mode.

JavaScript

Can I use django-SHOP without AngularJS?


When using REST, then client side actions have to be handles somehow using JavaScript. AngularJS was chosen
in 2014, because it was the only MVVM-ish framework at the time. However, the intention always has been, that
merchants implementing their e-commerce site on top of django-SHOP do not have to write a single line of code in
JavaScript. The idea is, that everything shall be adoptable using the special HTML elements introduced by django-
SHOP.
Unless all these directives are replaced by another JavaScript framework, such as React, Ember, Vue.js, Angular2/4,
Aurelia, etc., one can setup django-SHOP without any JavaScript at all. Then however, a lot of functionality is lost
and the user experience will be modest.

CMS pages as categories

My products have a many-to-many relation with the CMS PageModel. However, in the admin for the product,
the multi-select widget dos not show any pages.
In the product’s admin view, only CMS pages which in their advanced settings are marked as CatalogList, are
eligible to be connected with a product.

8.4 Contributing

8.4.1 Naming conventions

The official name of this project is django-SHOP. Third party plugins for django-SHOP shall follow the same naming
convention as for plugins of django-CMS: Third party package names shall start with djangoshop followed by a dash;
no space or dash shall be added between django and shop, for example: djangoshop-stripe
Django-SHOP should be capitalised at the start of sentences and in title-case headings.

8.4.2 Running tests

It’s important to run tests before committing :)

160 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

Setting up the environment

We highly suggest you run the tests suite in a clean environment, using a tool such as virtualenv.
1. Clone the repository and cd into it:

git clone https://github.com/awesto/django-shop


cd django-shop

2. Create a virtualenv, and activate it:

virtualenv ~/.virtualenvs/django-shop
source ~/.virtualenvs/django-shop/bin/activate

3. Install the project in development mode:

pip install -e .

4. Install the development requirements:

pip install -r requirements/test_py3.txt

That’s it! Now, you should be able to run the tests:

export DJANGO_SHOP_TUTORIAL=polymorphic
py.test example

We use tox as a CI tool. To run the full CI test suite and get a coverage report, all you have to do is this:

pip install tox


tox

If you work on a certain part of the code base and you want to run the related tests, you may only want to run the tests
affecting that part. In such a case use py.test from your testing environment and specify the file to test, or for more
granularity the class name or even the method name. Here are two examples:

py.test testshop/test_money.py
py.test testshop/test_money.py -k test_pickle

Measuring which lines of code have been “seen” be the test runner is an important task while testing. Do this by
creating a coverage report, for example with:

coverage run $(which py.test) testshop


coverage report

or if you to test only a specific class

coverage run .tox/py27-django19/bin/py.test testshop/test_money.py


coverage report -m shop/money/*.py

Note: Using tox and py.test is optional. If you prefer the conventional way of running tests, you can do this:
django-admin.py test tests --settings shop.testsettings.

8.4. Contributing 161


djangoSHOP, Release 1.0.2

8.4.3 Community

Most of the discussion around django SHOP takes place on IRC (Internet Relay Chat), on the freenode servers in the
#django-shop channel.
We also have a mailing list and a google group:

http://groups.google.com/group/django-shop

8.4.4 Code guidelines

Unless otherwise specified, follow PEP 8 as closely as possible.


An exception to PEP 8 is our rules on line lengths. Don’t limit lines of code to 79 characters if it means the code looks
significantly uglier or is harder to read. Consider 100 characters as a soft, and 119 as a hard limit. Here soft limit
means, that unless a line must be splitted across two lines, it is more readable to stay with a long line.
Use the issue tracker only to report bugs. Send unsolicited pull requests only to fix bug – never to add new features.
Use stack-overflow to ask for questions related to django-SHOP.
Most pull requests will be rejected without proper unit testing.
Before adding a new feature, please write a specification using the style for Django Enhancement Proposals.
More information about how to send a Pull Request can be found on GitHub: http://help.github.com/
send-pull-requests/

8.5 CORE DEVELOPERS

• Jacob Rief
• René Fleschenberg

8.6 CONTRIBUTORS

• abelradac
• Adrien Lemaire
• airtonix
• Aleš Kocjančič
• Anders Petersson
• Andrés Reyes Monge
• Arturo Fernandez
• Audrey Roy
• Benjamin Wohlwend
• Ben Lopatin
• Bojan Mihelac
• Chris Morgan

162 Chapter 8. Development and Community


djangoSHOP, Release 1.0.2

• Dino Perovic
• fivethreeo
• German Larrain
• Hamza Khchine
• ikresoft
• Issac Kelly
• Jacek Mitr˛ega
• Jonas Obrist
• Justin Steward
• Kristian Øllegaard
• maltitco
• Maltsev Artyom
• Martin Ogden
• Mike Yumatov
• Mikhail Kolesnik
• Nicolas Pascal
• Pavel Zhukov
• Pedro Gracia
• Per Rosengren
• Raúl Cumplido
• Roberth Solís
• Rolo Mawlabaux
• rubengrill
• Simon Luijk
• Sławomir Ehlert
• Stephen Muss
• Thomas Woolford

8.7 RETIRED CORE DEVELOPERS

• Chris Glass (chrisglass)


• Martin Brochhaus

8.7. RETIRED CORE DEVELOPERS 163


djangoSHOP, Release 1.0.2

164 Chapter 8. Development and Community


CHAPTER 9

To be written

9.1 Address Model

DjangoSHOP is shipped with a default address model as found in shop.models.defaults.address.


ShippingAddress and shop.models.defaults.address.BillingAddress. It is based on a recom-
mendation on International Address Fields in Web Forms.
Some people might feel that this address model is not suitable for their specific use-case, or in their selling region.
Since django-SHOP allows to override each model, we simply might want to write our own one.

9.1.1 Overriding the Default Models

To start with, have a look at the implementation of the default address models mentioned before. Chances are high
that you might want to use the same address model for shipping, as well as for billing. Therefore instead of writing
them twice, we use a mixin class:
from django.db import models

class AddressModelMixin(models.Model):
name = models.CharField("Full name", max_length=1024)
address1 = models.CharField("Address line 1", max_length=1024)
address2 = models.CharField("Address line 2", max_length=1024, blank=True,
˓→null=True)

zip_code = models.CharField("ZIP", max_length=12)


city = models.CharField("City", max_length=1024)

class Meta:
abstract = True

This mixin class then is used to instantiate the billing address models:
from shop.models.address import BaseShippingAddress, BaseBillingAddress

(continues on next page)

165
djangoSHOP, Release 1.0.2

(continued from previous page)


class BillingAddress(BaseBillingAddress, AddressModelMixin):
class Meta:
verbose_name = "Billing Address"

In Europe for B2B commerce, the customer’s tax number is associated with the location for delivery. We therefore
have to add it to our shipping address models:
class ShippingAddress(BaseShippingAddress, AddressModelMixin): tax_number = mod-
els.CharField(“Tax number”, max_length=100)
class Meta: verbose_name = “Shipping Address”

9.1.2 Multiple Addresses

Depending on the shop’s requirements, each customer may have no, one or multiple billing- and/or shipping addresses.
On an e-commerce site selling digital goods, presumably only the billing address makes sense. A shop with many
returning customers probably wants to allow them to store more than one address.
In django-SHOP each address model has a foreign key onto the customer model, hence all of the above use-cases are
possible.

9.1.3 Rendering the Address Forms

During checkout

9.2 How to create a Payment backend

Payment backends must be listed in settings.SHOP_PAYMENT_BACKENDS

9.2.1 Shop interface

While we could solve this by defining a superclass for all payment backends, the better approach to plugins is to
implement inversion-of-control, and let the backends hold a reference to the shop instead.
The reference interface for payment backends is located at
class shop.payment.api.PaymentAPI
Currently, the shop interface defines the following methods:

Common with shipping

PaymentAPI.get_order(request)
Returns the order currently being processed.
Parameters request – a Django request object
Return type an Order instance
PaymentAPI.add_extra_info(order, text)
Adds an extra info field to the order (whatever)
Parameters

166 Chapter 9. To be written


djangoSHOP, Release 1.0.2

• order – an Order instance


• text – a string containing the extra order information
PaymentAPI.is_order_payed(order)
Whether the passed order is fully paid or not
Parameters order – an Order instance
Return type bool
PaymentAPI.is_order_complete(order)
Whether the passed order is in a “finished” state
Parameters order – an Order instance
Return type bool
PaymentAPI.get_order_total(order)
Returns the order’s grand total.
Parameters order – an Order instance
Return type Decimal
PaymentAPI.get_order_subtotal(order)
Returns the order’s sum of item prices (without taxes or S&H)
Parameters order – an Order instance
Return type Decimal
PaymentAPI.get_order_short_name(order)
A short human-readable description of the order
Parameters order – an Order instance
Return type a string with the short name of the order
PaymentAPI.get_order_unique_id(order)
The order’s unique identifier for this shop system
Parameters order – an Order instance
Return type the primary key of the Order (in the default implementation)
PaymentAPI.get_order_for_id(id)
Returns an Order object given a unique identifier (this is the reverse of get_order_unique_id())
Parameters id – identifier for the order
Return type the Order object identified by id

Specific to payment

PaymentAPI.confirm_payment(order, amount, transaction_id, save=True)


This should be called when the confirmation from the payment processor was called and that the payment
was confirmed for a given amount. The processor’s transaction identifier should be passed too, along with an
instruction to save the object or not. For instance, if you expect many small confirmations you might want to
save all of them at the end in one go (?). Finally the payment method keeps track of what backend was used for
this specific payment.
Parameters
• order – an Order instance

9.2. How to create a Payment backend 167


djangoSHOP, Release 1.0.2

• amount – the paid amount


• transaction_id – the backend-specific transaction identifier
• save – a bool that indicates if the changes should be committed to the database.

9.2.2 Backend interface

The payment backend should define the following interface for the shop to be able do to anything sensible with it:

Attributes

PaymentBackend.backend_name
The name of the backend (to be displayed to users)
PaymentBackend.url_namespace
“slug” to prepend to this backend’s URLs (acting as a namespace)

Methods

PaymentBackend.__init__(shop)
must accept a “shop” argument (to let the shop system inject a reference to it)
Parameters shop – an instance of the shop
PaymentBackend.get_urls()
should return a list of URLs (similar to urlpatterns), to be added to the URL resolver when urls are loaded. These
will be namespaced with the url_namespace attribute by the shop system, so it shouldn’t be done manually.

Security

In order to make your payment backend compatible with the SHOP_FORCE_LOGIN setting please make sure to add
the @shop_login_required decorator to any views that your backend provides.

9.3 Multi-Tenancy

If a site built with the django-SHOP framework shall be used by more than one vendor, we speak about a multi-tenant
environment. Django-SHOP does not implement multi-tenancy out of the box, it however is quite simple to extend
and customize.

9.3.1 Terminology

To distinguish the roles in a multi-tenant environment, we define the merchant as the site owner. On the other side, a
vendor owns a range of products. Each new product, he adds to the site, is assigned to him. Later on, existing products
can only be modified and deleted by the vendor they belong to.

168 Chapter 9. To be written


djangoSHOP, Release 1.0.2

9.3.2 Product Model

Since we are free to declare our own product models, This can be achieved by adding a foreign key onto the User
model:

from shop.models.product import BaseProduct

class Product(BaseProduct):
# other product attributes
merchant = models.ForeignKey(
User,
verbose_name=_("Merchant"),
limit_choices_to={'is_staff': True},
)

Note: unfinished docs

9.4 How to secure your catalog views

Chances are that you don’t want to allow your users to browse all views of the shop as anonymous users.

9.4. How to secure your catalog views 169


djangoSHOP, Release 1.0.2

170 Chapter 9. To be written


CHAPTER 10

License

Django-SHOP is licensed under the terms of the BSD license.

171
djangoSHOP, Release 1.0.2

172 Chapter 10. License


Python Module Index

s
shop.payment.api, 166

173
djangoSHOP, Release 1.0.2

174 Python Module Index


Index

Symbols get_order_short_name()
__init__() (shop.payment.api.PaymentBackend (shop.payment.api.PaymentAPI method),
method), 168 167
get_order_subtotal()
A (shop.payment.api.PaymentAPI method),
add_extra_cart_item_row() 167
(shop.modifiers.base.BaseCartModifier get_order_total()
method), 76 (shop.payment.api.PaymentAPI method),
add_extra_cart_row() 167
(shop.modifiers.base.BaseCartModifier get_order_unique_id()
method), 76 (shop.payment.api.PaymentAPI method),
add_extra_info() (shop.payment.api.PaymentAPI 167
method), 166 get_urls() (shop.payment.api.PaymentBackend
arrange_cart_items() method), 168
(shop.modifiers.base.BaseCartModifier
method), 76
I
arrange_watch_items() is_order_complete()
(shop.modifiers.base.BaseCartModifier (shop.payment.api.PaymentAPI method),
method), 76 167
is_order_payed() (shop.payment.api.PaymentAPI
B method), 167
backend_name (shop.payment.api.PaymentBackend
attribute), 168 P
BaseCartModifier (class in shop.modifiers.base), PaymentAPI (class in shop.payment.api), 166
75 post_process_cart()
(shop.modifiers.base.BaseCartModifier
C method), 76
confirm_payment() post_process_cart_item()
(shop.payment.api.PaymentAPI method), (shop.modifiers.base.BaseCartModifier
167 method), 76
pre_process_cart()
D (shop.modifiers.base.BaseCartModifier
DefaultSettings (class in shop.conf ), 121 method), 76
pre_process_cart_item()
G (shop.modifiers.base.BaseCartModifier
get_order() (shop.payment.api.PaymentAPI method), 76
method), 166 process_cart() (shop.modifiers.base.BaseCartModifier
get_order_for_id() method), 76
(shop.payment.api.PaymentAPI method), process_cart_item()
167 (shop.modifiers.base.BaseCartModifier

175
djangoSHOP, Release 1.0.2

method), 76
Python Enhancement Proposals
PEP 8, 162

S
shop.payment.api (module), 166
SHOP_ADD2CART_NG_MODEL_OPTIONS
(shop.conf.DefaultSettings attribute), 122
SHOP_APP_LABEL (shop.conf.DefaultSettings at-
tribute), 121
SHOP_CACHE_DURATIONS (shop.conf.DefaultSettings
attribute), 122
SHOP_CART_MODIFIERS (shop.conf.DefaultSettings
attribute), 122
SHOP_CASCADE_FORMS (shop.conf.DefaultSettings at-
tribute), 123
SHOP_CUSTOMER_SERIALIZER
(shop.conf.DefaultSettings attribute), 121
SHOP_DECIMAL_PLACES (shop.conf.DefaultSettings
attribute), 121
SHOP_DEFAULT_CURRENCY
(shop.conf.DefaultSettings attribute), 121
SHOP_DIALOG_FORMS (shop.conf.DefaultSettings at-
tribute), 123
SHOP_EDITCART_NG_MODEL_OPTIONS
(shop.conf.DefaultSettings attribute), 122
SHOP_GUEST_IS_ACTIVE_USER
(shop.conf.DefaultSettings attribute), 122
SHOP_LINK_TO_EMPTY_CART
(shop.conf.DefaultSettings attribute), 122
SHOP_MONEY_FORMAT (shop.conf.DefaultSettings at-
tribute), 121
SHOP_ORDER_ITEM_SERIALIZER
(shop.conf.DefaultSettings attribute), 122
SHOP_ORDER_WORKFLOWS (shop.conf.DefaultSettings
attribute), 122
SHOP_OVERRIDE_SHIPPING_METHOD
(shop.conf.DefaultSettings attribute), 122
SHOP_PRODUCT_SELECT_SERIALIZER
(shop.conf.DefaultSettings attribute), 122
SHOP_PRODUCT_SUMMARY_SERIALIZER
(shop.conf.DefaultSettings attribute), 121
SHOP_VALUE_ADDED_TAX (shop.conf.DefaultSettings
attribute), 122
SHOP_VENDOR_EMAIL (shop.conf.DefaultSettings at-
tribute), 121

U
url_namespace (shop.payment.api.PaymentBackend
attribute), 168

176 Index

You might also like