In order to demonstrate Django's rapid development potential, we will begin by constructing a simple, but fully-featured, e-commerce store. The goal is to be up and running with a product catalog and products for sale, including a simple payment processing interface, in about half-an-hour. If this seems ambitious, remember that Django offers a lot of built-in shortcuts for the most common web-related development tasks. We will be taking full advantage of these and there will be side discussions of their general use.
In addition to building our starter storefront, this article aims to demonstrate some other Django tools and techniques. In this article by Jesse Legg, author of Django 1.2 e-commerce, we will:
- Create our Django Product model to take advantage of the automatic admin tool
- Build a flexible but easy to use categorization system, to better organize our catalog of products
- Utilize Django's generic view framework to expose a quick set of views on our catalog data
- Finally, create a simple template for selling products through the Google Checkout API
(Read more interesting articles on Django 1.2 e-commerce here.)
Before we begin, let's take a moment to check our project setup. Our project layout includs two directories: one for files specific to our personal project (settings, URLs, and so on), and the other for our collection of e-commerce Python modules (coleman). This latter location is where the bulk of the code will live. If you have downloaded the source code from the Packt website, the contents of the archive download represents everything in this second location.
Designing a product catalog
The starting point of our e-commerce application is the product catalog. In the real world, businesses may produce multiple catalogs for mutually exclusive or overlapping subsets of their products. Some examples are: fall and spring catalogs, catalogs based on a genre or sub-category of product such as catalogs for differing kinds of music (for example, rock versus classical), and many other possibilities. In some cases a single catalog may suffice, but allowing for multiple catalogs is a simple enhancement that will add flexibility and robustness to our application.
As an example, we will imagine a fictitious food and beverage company, CranStore.com, that specializes in cranberry products: cranberry drinks, food, and deserts. In addition, to promote tourism at their cranberry bog, they sell numerous gift items, including t-shirts, hats, mouse pads, and the like. We will consider this business to illustrate examples as they relate to the online store we are building.
We will begin by defining a catalog model called Catalog. The basic model structure will look like this:
class Catalog(models.Model):
name = models.CharField(max_length=255
slug = models.SlugField(max_length=150)
publisher = models.CharField(max_length=300)
description = models.TextField()
pub_date = models.DateTimeField(default=datetime.now)
This is potentially the simplest model we will create. It contains only five, very simple fields. But it is a good starting point for a short discussion about Django model design. Notice that we have not included any relationships to other models here. For example, there is no products ManyToManyField. New Django developers tend to overlook simple design decisions such as the one shown previously, but the ramifications are quite important.
The first reason for this design is a purely practical one. Using Django's built-in admin tool can be a pleasure or a burden, depending on the design of your models. If we were to include a products field in the Catalog design, it would be a ManyToManyField represented in the admin as an enormous multiple-select HTML widget. This is practically useless in cases where there could be thousands of possible selections.
If, instead, we attach a ForeignKey to Catalog on a Product model (which we will build shortly), we instantly increase the usability of Django's automatic admin tool. Instead of a select-box where we must shift-click to choose multiple products, we have a much simpler HTML drop-down interface with significantly fewer choices. This should ultimately increase the usability of the admin for our users.
For example, CranStore.com sells lots of t-shirts during the fall when cranberries are ready to harvest and tourism spikes. They may wish to run a special catalog of touristy products on their website during this time. For the rest of the year, they sell a smaller selection of items online. The developers at CranStore create two catalogs: one is named Fall Collection and the other is called Standard Collection.
When creating product information, the marketing team can decide which catalog an individual product belongs to by simply selecting them from the product editing page. This is more intuitive than selecting individual products out of a giant list of all products from the catalog admin page.
Secondly, designing the Catalog model this way prevents potential "bloat" from creeping into our models. Imagine that CranStore decides to start printing paper versions of their catalogs and mailing them to a subscriber list. This would be a second potential ManyToManyField on our Catalog model, a field called subscribers. As you can see, this pattern could repeat with each new feature CranStore decides to implement.
By keeping models as simple as possible, we prevent all kinds of needless complexity. In addition we also adhere to a basic Django design principle, that of "loose coupling". At the database level, the tables Django generates will be very similar regardless of where our ManyToManyField lives. Usually the only difference will be in the table name. Thus it generally makes more sense to focus on the practical aspects of Django model design. Django's excellent reverse relationship feature also allows a great deal of flexibility when it comes time to using the ORM to access our data.
Model design is difficult and planning up-front can pay great dividends later. Ideally, we want to take advantage of the automatic, built-in features that make Django so great. The admin tool is a huge part of this. Anyone who has had to build a CRUD interface by hand so that non-developers can manage content should recognize the power of this feature. In many ways it is Django's "killer app".
Creating the product model
Finally, let's implement our product model. We will start with a very basic set of fields that represent common and shared properties amongst all the products we're likely to sell. Things like a picture of the item, its name, a short description, and pricing information.
class Product(models.Model):
name = models.CharField(max_length=300)
slug = models.SlugField(max_length=150)
description = models.TextField()
photo = models.ImageField(upload_to='product_photo',
blank=True)
manufacturer = models.CharField(max_length=300,
blank=True)
price_in_dollars = models.DecimalField(max_digits=6,
decimal_places=2)
Most e-commerce applications will need to capture many additional details about their products. We will add the ability to create arbitrary sets of attributes and add them as details to our products later in this article. For now, let's assume that these six fields are sufficient.
A few notes about this model: first, we have used a DecimalField to represent the product's price. Django makes it relatively simple to implement a custom field and such a field may be appropriate here. But for now we'll keep it simple and use a plain and built-in DecimalField to represent currency values.
Notice, too, the way we're storing the manufacturer information as a plain CharField. Depending on your application, it may be beneficial to build a Manufacturer model and convert this field to a ForeignKey.
Lastly, you may have realized by now that there is no connection to a Catalog model, either by a ForeignKey or ManyToManyField. Earlier we discussed the placement of this field in terms of whether it belonged to the Catalog or in the Product model and decided, for several reasons, that the Product was the better place. We will be adding a ForeignKey to our Product model, but not directly to the Catalog. In order to support categorization of products within a catalog, we will be creating a new model in the next section and using that as the connection point for our products.
(Read more interesting articles on Django 1.2 e-commerce here.)
Categorizing products
So far we've built a simple product model and simple catalog model. These models are excellent building blocks on which to begin adding new, more sophisticated functionality. As it stands, our catalog model design is unconnected to our products. We could add, as mentioned earlier, a ForeignKey from Product to Catalog. But this would allow for little in the way of organizing within the catalog, other than what we can do with the basic filter(), order_by(), and other ORM methods that Django provides.
Product categories are an extremely common solution to organization problems in web-based stores. Almost all major Internet retailers organize their products this way. It helps them to provide a more structured interface for their users and can give search engine indexers more precise pages to crawl. We'll discuss more about these tactics later, but let's begin by adding categories to the simple model designs that we created earlier.
Even though our Catalog model is relatively simple, we must make some design decisions before adding our category information. First, we have designed Catalog so that our e-commerce site can produce different catalogs for different scenarios. Each scenario, however, may have different category requirements.
Suppose our fictitious cranberry business, CranStore.com, wants to create a special catalog for the holiday season, when it begins selling numerous gifts and decorative items such as cranberry garland, dried cranberry ornaments, cranberry scented candles and the like. The majority of these special holiday gifts are not available year-round. However, some products will be available in other catalogs at different times of the year—for example, the very popular cranberry-scented candles.
We need to be able to support this use-case. In order to do so, we have to structure our models in a way that may feel awkward at first, but ultimately allows for great flexibility. Let's write a category model called CatalogCategory and add it to our products application's models.py:
class CatalogCategory(models.Model):
catalog = models.ForeignKey('Catalog',
related_name='categories')
parent = models.ForeignKey('self', blank=True, null=True,
related_name='children')
name = models.CharField(max_length=300)
slug = models.SlugField(max_length=150)
description = models.TextField(blank=True)
In addition, we can now add a relationship between the Product model we wrote earlier and our new CatalogCategory model. The full, updated model appears as follows:
class Product(models.Model):
category = models.ForeignKey('CatalogCategory',
related_name='products')
name = models.CharField(max_length=300)
slug = models.SlugField(max_length=150)
description = models.TextField()
photo = models.ImageField(upload_to='product_photo',
blank=True)
manufacturer = models.CharField(max_length=300,
blank=True)
price_in_dollars = models.DecimalField(max_digits=6,
decimal_places=2)
There are a couple of big changes here, so let's tackle the CatalogCategory first. This model creates a relationship to our earlier Catalog model, but also includes a ForeignKey relationship to itself. This is called a recursive relationship and is designed as a simple way of creating category hierarchies. A category is a top-level category if it has no parent relationship to other category. Otherwise, it is a sub-category.
The second change is the addition of the ForeignKey to a category on our Product model. An initial design inclination here is to relate products directly to their catalog. We even discussed this design earlier in the article, and it would be great for simple catalogs where we didn't need the extra organization functionality of categories.
However, when categories and sub-categories get involved, the design
has the potential to become seriously complex. This approach to our Product
model allows us to manage a single relationship between products and
their categories, thus keeping it relatively simple. This relationship
is also implicitly a relationship between our product and a catalog. We
still have access to the catalog our product lives in, via the category.
Another advantage of this design is that it remains extremely easy for non-developers to create products, catalogs, and complicated category structures, all within the built-in Django admin tool. As illustrated in the screenshots throughout this article, this model design gives us an extremely intuitive admin interface.
One final note about the above code: the default description when CatalogCategory model objects are printed or displayed in the admin is not particularly helpful. Let's add a custom __unicode__ method to this model that prints a more informative display of our categories. This method will include the parent category information if we're working with a sub-category, as well as the catalog to which the category belongs.
def __unicode__(self):
if self.parent:
return u'%s: %s - %s' % (self.catalog.name,
self.parent.name,
self.name)
return u'%s: %s' % (self.catalog.name, self.name)
Adding additional product details
The application we've built so far is actually quite powerful, but in this section we'll take our design to another level of flexibility. Products, as they currently exist, have a limited amount of information that can be stored about them. These are the six fields discussed earlier: name, description, photo, manufacturer, and price. These attributes will be common to all products in our catalog. There may be more attributes appropriate for this model, for example, size or weight, but we have left those unimplemented for now.
A lot of product information, though, is specific to only certain products or certain kinds of products. Capturing this information requires a more sophisticated design. Our goal, as always, will be to keep things as simple as possible and to take advantage of the built-in functionality that Django offers.
In an ideal world, we would like to allow an unlimited number of fields to capture all the potential descriptive information for any product. Unfortunately, this is not possible using the relational database systems that drive the majority of web-based applications. Instead, we must create a set of models with relationships in such a way that it effectively gives us what we need.
Database administrators are constantly implementing designs like this. But when it comes to frameworks like Django, with their own ORM systems, there's a great temptation to try and create fancy models with ManyToManyFields all over the place, which can capture everything and do so very cleverly. This is almost always more complex than is necessary, but because Django makes it so easy, it becomes a great temptation.
In our case we will build a fairly simple pair of models that allow us to store arbitrary information for any product. These models are the ProductDetail and ProductAttribute and they are as follows:
class ProductDetail(models.Model):
'''
The ``ProductDetail`` model represents information unique to a
specific product. This is a generic design that can be used
to extend the information contained in the ``Product`` model with
specific, extra details.
'''
product = models.ForeignKey('Product',
related_name='details')
attribute = models.ForeignKey('ProductAttribute')
value = models.CharField(max_length=500)
description = models.TextField(blank=True)
def __unicode__(self):
return u'%s: %s - %s' % (self.product,
self.attribute,
self.value)
class ProductAttribute(models.Model):
'''
The "ProductAttribute" model represents a class of feature found
across a set of products. It does not store any data values
related to the attribute, but only describes what kind of a
product feature we are trying to capture. Possible attributes
include things such as materials, colors, sizes, and many, many
more.
'''
name = models.CharField(max_length=300)
description = models.TextField(blank=True)
def __unicode__(self):
return u'%s' % self.name
As noted in the model docstrings, the ProductDetail design relates a kind of attribute to a specific product and stores the attribute value, as well as an optional extended description of the value.
Suppose our friends at CranStore.com offer a new selection of premium cranberries, created from special seedlings developed over years of careful breeding. Each variety of these premium cranberries features different characteristics. For example, some are extremely tart, while others are extra sweet, and so on.
To capture this additional information, we created a Tartness ProductAttribute and used it to add ProductDetail instances for the relevant product. You can see the end-result of adding this attribute in the previous screenshot.
Viewing the product catalog
We now have a relatively complete and sophisticated product catalog with products, categories, and additional product information. This acts as the core of our e-commerce application. Now we will write some quick views and get our catalog running and published to a web server.
In the Django design philosophy, views represent a specific interpretation of the data stored in our models. It is through views that templates, and ultimately the outside world, access our model data. Very often the model data we expose in our views are simply the model objects themselves. In other words, we provided direct access to a model object and all of its fields to the template.
Other times, we may be exposing smaller or larger portions of our model data, including QuerySets or lists of models or a subset of all model data that match a specific filter or other ORM expression.
Exposing a full model object or set of objects according to some filter parameter, is so common that Django provides automatic built-in assistance. This is accomplished using 'generic views', of which Django includes over a dozen. They are broken into four basic kinds: simple, date-based, list-based, and create/update/delete (CRUD).
Simple generic views include two functions: direct_to_template and redirect_to. These are by far the simplest possible views and writing by hand more than once would be overkill. The direct_to_template generic view simply renders a regular Django HTML template, with an optional set of variables added to the context. The redirect_to generic view is even simpler; it raises a standard HTTP 302 status code, otherwise known as a "non-permanent redirection". It can also optionally raise a permanent redirection (HTTP 301).
The real power behind generic views becomes evident in the next set: the date-based views. These are designed to create automatic archives for all of your site content organized by date. This is ideal for newspaper or blog applications, where content is published frequently and finding content based on a date is an effective way to interact with the information.
There are seven different date-based views. The majority of these are for specific time intervals: by year, by month, by week, by day, and for the current day. The remaining two views are for content indexes, called archive_index, which can often be used for homepages and for details on a specific object, useful for permalink pages.
The next set of views is called list-based because they process and present lists of model data. There are only two kinds of list-based views: object_list and object_detail. The former provides a template with context variable that represents a list of objects. Usually the template will iterate over this list using a for-loop tag. The latter view, object_detail, provides a simple view of a single item in the list—for example, a single product out of a list of all products in the catalog.
And lastly we have CRUD views. CRUD stands for create, update, and delete, and is used to provide automatic interfaces for users to modify model data in templates. Sometimes users will need to edit information, but are not staff members and cannot use the Django admin tool. This often happens, when users are editing content they have created (user-generated content) or more simply when they need to edit their profile information, such as payment method or shipping address.
CRUD views are extremely useful for simplifying a very common pattern of gathering and editing data. When combined with Django's forms module, its power can be extended even further.
For now, let's build an initial set of views on our product catalog using the list-based generic views. We have two use-cases. The first use-case is where we enlist a set of products in our catalog or within a category in our catalog. The second use-case is a detail page for every product we are selling. Generic views make writing these pages easy. For our catalog homepage we will implement a special-case of the list-based object_list view.
When using generic views, it is rarely necessary to create a views.py file or write any view handling code at all. The bulk of the time, generic views are integrated directly into the URL pattern definitions in our urls.py file. Here is the basic set of URLs for our product catalog:
urlpatterns = patterns(
'django.views.generic.list_detail',
url(r'^product/$', 'object_list',
{'queryset': Product.objects.all()}),
url(r'^product/(?P<slug>[-\w]+)/$', 'object_detail',
{'queryset': Product.objects.all()}))
(Read more interesting articles on Django 1.2 e-commerce here.)
Designing simple product HTML templates
Now that we have exposed our product data via generic views and created corresponding URL patterns, we need to implement some templates to render the product information to our browser. Another advantage of using generic views is that templates are automatically specified for each view based upon the name of the application and model in use. Since our urls.py file is part of the products application and we are rendering generic views on our Product model, our application will automatically look for templates in the following locations within the template folder: products/product_detail.html and products/product_list.html.
These templates will get rather simple contexts. The object_list template will receive a variable whose default name is object_list. It is a QuerySet corresponding to the data exposed by your view. The object_detail template will receive a variable whose default name is object. It corresponds to the object you are viewing in more detail.
In our example, the list view will receive a QuerySet of all Product objects, while the detail view receives the object specified by the slug in the URL. In our list template we loop over the products like so (extra HTML portions are removed):
{% for object in object_list %}
<h3><a href="{{ object.get_absolute_url }}">
{{ object.name }}</a></h3>
<p>{{ object.description }}</p>
{% endfor %}
The detail template works similarly, though the output in the template includes more details about the item:
<h1>{{ object.name }}</h1>
<p>{{ object.description }}</p>
<p>Price: ${{ object.price_in_dollars }}</p>
<hr/>
<p><a href="{% url product_list %}">Back to product list</a></p>
We can print the extra attributes, if any happen to exist, very easily in the template. The output from the snippet below is demonstrated in the following screenshot:
<h1>{{ object.name }}</h1>
<p>{{ object.description }}</p>
<p>Price: ${{ object.price_in_dollars }}</p>
{% for detail in object.details.all %}
{% if forloop.first %}<p>Additional details:</p>{%endif%}
<li>{{ detail.attribute.name }}: {{ detail.value }}</li>
{% endfor %}
We now have a very spartan, but functional product catalog rendering to HTML from our Django database. We can add, modify, and delete products, catalogs and additional attributes. Now let's get started selling. In the next section we will create a Google Checkout API account and use its sandbox to set up a simple order mechanism for our online store.
Getting paid: A quick payment integration
An online store is of little use if you can't sell products to customers. However, to succeed in our promise to build a fully functional web store in 30 minutes, we will make use of a quick payment-integration with the Google Checkout API.
First, you will need to sign-up for a Google Checkout account. For the examples in this article, it is recommended that you use the Checkout sandbox, which is a test bed, non-live version of the Google Checkout system. You can get a seller account in the sandbox at the following URL: http://sandbox.google.com/checkout/sell.
Once you have an account, you can access the API document from this URL: http://code.google.com/apis/checkout/developer/.
We will create a very simple Buy It Now button using the Checkout API. After you create a sandbox seller account and log in for the first time, you can access several automatic checkout tools via the Tools tab at the top of the page. From here, select the Buy It Now button and you can generate HTML for an initial button. We will build on top of this HTML in our templates.
The buy it now HTML will looks like this (note that the actual merchant ID numbers, even though they're in a sandbox account, have been removed):
<form action="https://sandbox.google.com/checkout/api/checkout/
v2/checkoutForm/Merchant/xxxxx" id="BB_BuyButtonForm" method=
"post" name="BB_BuyButtonForm">
<input name="item_name_1"
type="hidden" value="Cranberry Preserves"/>
<input name="item_description_1"
type="hidden" value="Tasty jam made from cranberries."/>
<input name="item_quantity_1" type="hidden" value="1"/>
<input name="item_price_1" type="hidden" value="0.5"/>
<input name="item_currency_1" type="hidden" value="USD"/>
<input name="_charset_" type="hidden" value="utf-8"/>
<input alt="" src="https://sandbox.google.com/checkout/buttons/
buy.gif?merchant_id=xxxxx&w=117&h=48&style=white&
variant=text&loc=en_US" type="image"/>
</form>
Now we can copy and paste this HTML code into our product detail template. We will then modify the form code to use our Django template variables for the appropriate HTML form fields. Ultimately the form will look like this:
<form action="https://sandbox.google.com/checkout/api/checkout/
v2/checkoutForm/Merchant/xxxxx" id="BB_BuyButtonForm" method="post"
name="BB_BuyButtonForm">
<input name="item_name_1" type="hidden" value="{{ object.name }}"/>
<input name="item_description_1"
type="hidden" value="{{ object.description }}"/>
<input name="item_quantity_1" type="hidden" value="1"/>
<input name="item_price_1"
type="hidden" value="{{ object.price_in_dollars }}"/>
<input name="item_currency_1" type="hidden" value="USD"/>
<input name="_charset_" type="hidden" value="utf-8"/>
<input alt="" src="https://sandbox.google.com/checkout/buttons/
buy.gif?merchant_id=xxxxx&w=117&h=48&style=white&
variant=text&loc=en_US" type="image" />
</form>
Our simple product catalog that started out as a couple of Django models in the beginning of this article is now fully capable of processing transactions and selling products. If these Buy It Now buttons were activated through the real Google Checkout web service and not the sandbox, they would be totally live and able to sell any item in our product database.
Unfortunately, the Buy It Now button only supports selling a single item at a time. The method we've covered above will work for some very simple applications, but more than likely we will need something more sophisticated for a larger site. The typical method of supporting complicated transactions is to implement a "shopping cart" tool, a pattern that can be seen on many of the largest e-commerce sites.
The buy it now button has other problems as well. You cannot specify a choice of sizes, for example: small, medium, or large. These may be stored on our Product model, but could not be specified in a single Buy It Now button without additional work. It is also a requirement of the "buy it now" tool that the customer checkout on Google's site with a minimal amount of custom branding. If we want to provide a full-fledged checkout experience, including our own branding and HTML design, we must look to the full Checkout API.
Summary
Congratulations! You have built a complete, functional e-commerce site in 30 minutes. It is spartan and simple, but it will work for some applications. We have skipped over many aspects of an e-commerce application, but can build upon this skeleton framework. You should now have built a basic framework that:
- Represents Product information, such as price and description in a Django model
- Stores and organizes products using the database and a catalog Django model
- Employs generic views and their related templates to run a simple web-based storefront
- Sells products and accepts payment through the Google Checkout system
If you have read this article you may be interested to view:
- Django 1.2 e-commerce: Data Integration
- Django 1.2 e-commerce: Generating PDF Reports from Python using ReportLab
About the Author :
Jesse Legg
Jesse Legg is a web engineer and writer based in Somerville, Massachusetts. He has built Python and Django tools in environments ranging from web startups to higher education and has contributed several applications to the Django open source community. He has been working in the technology industry for almost a decade and has developed web applications in a range of technologies, including Python, Django, PHP, and Javascript. He writes an occasional blog at jesselegg.com.
Post new comment