Django for E-Commerce: A Developers Guide (with Wagtail CMS Tutorial)
Published: 2021-09-01 18:57:07


> In a rush? Skip to the [tutorial](#tutorial), or live [demo](#demo)

“D-J-A-N-G-O. The D is silent.”

Man, I love that line. So badass.

But the eponymous character from Quentin Tarantino’s masterpiece isn’t the only badass *Django* in town.

So is the popular Python framework of the same name.

![Django & Django in town](https://snipcart.com/media/205952/django.gif)

Today, we’re leaving the realm of [JavaScript](https://snipcart.com/blog/react-seo-nextjs-tutorial) [frameworks](https://snipcart.com/blog/vuejs-ecommerce-headless-buttercms) for a quick venture into **Django e-commerce.**

In this post, I’ll answer legitimate questions you might have when starting a new e-commerce project, such as:

> Is Python the right language for my project? What about Django? Is it the right framework? Which tools or plugins should I use?

Then, I’ll show you our homemade recipe for Django-powered e-commerce success with a step-by-step **Wagtail CMS and Tailwind CSS** tutorial:

- Creating a new Wagtail site.
- Adding Snipcart configuration settings.
- Generating database migrations.
- Creating new products for your Django store.
- Crafting a Tailwind CSS e-commerce template.

Let’s start with the basics.

## The State of Python

One of the main reasons to pick the Django framework is its **[Python](https://www.python.org/)** foundation.

A general-purpose, dynamic programming language, Python was developed by ex-Googler Guido van Rossum in the late ’80s. A fan of Monthy Python, he took one-half of the name to baptize his programming project.

How can you not be fans?

He wasn’t joking, though. To say that Python has become “popular” is an understatement.

Today, it’s used by hundreds of thousands of developers all over the world. [As StackOverflow puts it](https://stackoverflow.blog/2017/09/06/incredible-growth-python/):

> The term “fastest-growing” can be hard to define precisely, but we make the case that Python has a solid claim to being the fastest-growing major programming language.

A few reasons explain the Python love:

- Its grammatical readability is awesome.
- It has a fast learning curve for newcomers.
- It boasts a long-lasting, [solid ecosystem of libraries](https://pypi.org/) & community
- It’s now the standard language for [data science](https://jakevdp.github.io/PythonDataScienceHandbook/) & [machine learning](https://machinelearningmastery.com/machine-learning-in-python-step-by-step/).
- It powers great dev tools like [Pelican](https://snipcart.com/blog/pelican-blog-tutorial-search-comments), a neat static blog generator.
- Reddit is written in Python. :wink:

## What about the Django framework?

![django logo](https://snipcart.com/media/205964/django-logo-positive.png)

**[Django](https://www.djangoproject.com/)** is an open-source, **high-level Python web framework**. Its emphasis on reusable components makes it faster for developers to build web apps on top of Python. It presents itself as a *web framework for perfectionists with deadlines.*

Now maintained by the Django Software Foundation, it was originally written by two brilliant Lawrence Journal-World developers. Oh, and while Python draws its name from comedy icons, Django got his from a versatile guitar legend: [Django Reinhardt](https://www.youtube.com/watch?v=aZ308aOOX04)!

As a full-stack framework, it overshadows pretty much any alternative tool out there. It’s fast, fully loaded, secure, scalable & versatile. All characteristics you’ll probably want to apply to your e-commerce setup!

## Why use Django for e-commerce?

While you can do **a lot** with Django, let’s keep the focus on what it brings to e-commerce and the different tools available to put together an online store.

First, here are some Django features to consider if you’re looking for the right framework to build a shop.

→ **Scalability**

Django is perfect for e-commerce startups, as it’s a good fit for small websites and can scale perfectly with business growth. You can rely on Django to handle hundreds/thousands of visitors at a time. It’s built with independent components you can unplug or replace depending on your needs at any specific time.

→ **Security**

With e-commerce, you want to make sure merchants and clients alike feel safe through your shopping experience. Django prevents a whole lot of common security mistakes, often weakening traditional PHP CMSs. For instance, Django hides your site’s source code from direct viewing on the web by dynamically generating web pages.

→ **Feature-rich**

Compared to most frameworks, Django comes with way more features out-of-the-box. It allows you to build an app right off the bat. Perfect for supporting your online store with functionalities such as user authentification, content management, or RSS feed. If something seems to be missing, you can count on Django’s community and plugins ecosystem to extend your app!

→ **SEO-friendly**

SEO is paramount for any online business. Django advocates best practices for SEO. Human-readable URLs and sitemap features are sure to please any marketing team.

Oh, and also, **it’s fast,** which is always great for both customer experience and SEO.

→ **Reliable**

It has been crowd-tested for a while now, and the community surrounding it is widely supportive. Active developers continuously update it; maybe you’ll even find yourself [contributing](https://github.com/django/django).

### Django e-commerce tools

There are a few noteworthy e-commerce solutions in the Python/Django ecosystem:

- **[Oscar](http://oscarcommerce.com/)** — Domain-driven e-commerce for Django, open-source.
- **[Saleor](https://saleor.io/)** — An e-commerce storefront written in Python, open-source.
- **[Django-SHOP](https://django-shop.readthedocs.io/en/latest/index.html)** — A Django-based shop system.
- **[Shuup](https://www.shuup.com/)** — A single and multi-vendor application.

You can explore more options through this extensive list of the best [e-commerce packages](https://djangopackages.org/grids/g/ecommerce/).

Now let me present you with another cool stack for a complete and custom e-commerce setup with Django.

### Wagtail CMS & e-commerce

![wagtail](https://snipcart.com/media/205953/wagtail.svg)

[Wagtail](https://wagtail.io/) is a developer-first Django content management system. Free and open-source, the good-hearted folks at [Torchbox](https://torchbox.com/) developed it. It’s elegant, flexible, and, IMHO, kicks ass.

In the following Wagtail tutorial, the CMS will be in charge of creating and managing products that users will then be able to buy through a shopping cart.

By the end of it, you’ll have a solid Django-powered e-commerce site up and running.

Let’s get practical!

<h2 id=“tutorial”>Django e-commerce tutorial with Wagtail CMS</h2>

![Django + Wagtail CMS + Snipcart ](https://snipcart.com/media/205955/django-wagtail-snipcart.png)

> Over my time at Snipcart, I’ve kind of developed expertise in shopping carts. Why not push it a bit further by creating a shopping cart shop? Let’s see how it goes.

**Pre-requisites**

- A [Snipcart account](https://app.snipcart.com/register) (forever free in test mode)
- [Python](https://www.python.org/downloads/) and basic knowledge of it

### 1. Creating a new Wagtail e-commerce site

Make sure you have Wagtail installed. If not, refer to their [installation documentation](http://docs.wagtail.io/en/latest/getting_started/index.html).

Open a terminal and launch a new Wagtail site:

```bash
wagtail start snipcartwagtaildemo
cd snipcartwagtaildemo
```

We have an extra step to complete the Wagtail setup, and it’s to install the `wagtail.contrib.settings` plugin that we’ll require later on.

In your new Wagtail project, open the `base.py` file located in `snipcartwaigtaildemo/settings` folder. Then, add `wagtail.contrib.settings` to the `INSTALLED_APPS` array.

```python
# ./setting/base.py
INSTALLED_APPS = [
    ...,
    ‘wagtail.contrib.settings’
]
```

### 1.1 Models definition

The first thing you need to do is create your Page models. Wagtail uses these Django models to generate a page type.

Open the `models.py` file located in the `home` folder of your product. This is where you’ll define all your custom models.

Create two different models:

- `Product`: defines the product you’re selling.
- `ProductCustomField`: defines a single product custom field.

Let’s begin by importing the required modules:

```python
# ./home/models.py
from django.db import models

from modelcluster.fields import ParentalKey

from wagtail.core.models import Page, Orderable
from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel, InlinePanel
from wagtail.images.edit_handlers import ImageChooserPanel
```

Now add the `Product` model:

```python
# ./home/models.py

class Product(Page):
    sku = models.CharField(max_length=255)
    short_description = models.TextField(blank=True, null=True)
    price = models.DecimalField(decimal_places=2, max_digits=10)
    image = models.ForeignKey(
        ‘wagtailimages.Image’,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name=‘+’
    )

    content_panels = Page.content_panels + [
        FieldPanel(‘sku’),
        FieldPanel(‘price’),
        ImageChooserPanel(‘image’),
        FieldPanel(‘short_description’),
        InlinePanel(‘custom_fields’, label=‘Custom fields’),
    ]
```

And `ProductCustomField`:

```python
# ./home/models.py

class ProductCustomField(Orderable):
    product = ParentalKey(Product, on_delete=models.CASCADE, related_name=‘custom_fields’)
    name = models.CharField(max_length=255)
    options = models.CharField(max_length=500, null=True, blank=True)

    panels = [
        FieldPanel(‘name’),
        FieldPanel(‘options’)
    ]
```

### 2. Adding Snipcart configuration settings

> If you need more help with this part, refer to our documentation here and here.

Let’s make sure you can update the Snipcart API key directly from Wagtail’s dashboard.

You’ll need to add site settings to do so.

Site settings are special fields that you can add to your models.py file. They’ll appear in the *Wagtail Settings* section of the dashboard.

Import this module:

```python
# ./home/models.py

from wagtail.contrib.settings.models import BaseSetting, register_setting
```

Then add these:

```python
# ./home/models.py

@register_setting
class SnipcartSettings(BaseSetting):
    api_key = models.CharField(
        max_length=255,
        help_text=‘Your Snipcart public API key’
    )
```

### 3. Database migrations

Now that your models are created, you’ll need to generate database migrations and run them.

In your terminal, use the `makemigrations` command:

```python
manage.py makemigrations
```

You should see the following output:

```python
Migrations for ‘home’:  home/migrations/0003_product_productcustomfield_snipcartsettings.py    - Create model Product    - Create model SnipcartSettings    - Create model ProductCustomField
```

Once the migrations are generated, apply them to your database with the `migrate` command:

```python
manage.py migrate
```

It will take a couple of seconds; Wagtail will set up its own database schema along with the models you just defined.

Finally, create your first CMS user with the `createsuperuser` command:

```python
manage.py createsuperuser
```

It will ask for an email address, a username, and to create a password. Don’t forget the username and password you picked; you will need them to log into Wagtail’s dashboard.

### 4. Creating products

Start by firing up your dev server with the Django dev server command:

```python
manage.py runserver
```

Now open your browser and navigate to `http://127.0.0.1:8000/admin`. Use the credentials you set up earlier to log in.

Select the `Home` page in Wagtail’s menu. Then click on the `Add child page button`.

You’ll be asked to pick a type of page, select `Home page` and give it a name.

Go back to the `Home` page menu and click `Add child page button` under your newly created page.

![add child product](https://snipcart.com/media/205963/addchildproduct.png)

 Once again, you’ll be asked to pick a type of page, this time select `Product`.

![wagtail create product](https://snipcart.com/media/205965/wagtailcreateproduct.png)

Enter the product details, then publish your new product:

![publish product wagtail](https://snipcart.com/media/205959/product_publish_wagtail.png)

You can create as many products as you wish.

### 4.1 Adding Snipcart API key

Remember the `SnipcartSettings` class you created? You’ll be able to configure your API key by expanding the *Settings* menu and going to *Snipcart settings*.

![snipcart setting in wagtail](https://snipcart.com/media/205960/snipcart-setting-wagtail.png)

Open Snipcart’s dashboard and get your public API key (Test or Live), go back to Wagtail, and paste it in the API key field.

Save your settings.

### 5. Templating

Your backend is now ready, your API key is configured, and your first products are created—time to start building the site.

For this demo, I decided to use the [Tailwind CSS](https://tailwindcss.com/) framework. It enables you to build any design directly in your HTML rapidly.

#### **1 - The Tailwind CSS build process**

Create a new directory within your Django project, in which you’ll install Tailwind CSS like in any [Vanilla.js](https://snipcart.com/blog/learn-vanilla-javascript-before-using-js-frameworks) project setup:

```bash
cd your-django-folder
mkdir jstoolchaincd jstoolchain
npm install tailwindcss postcss-cli autoprefixer
npx tailwind init
npx touch postcss.config.js
```

In this `postcss.config.js` file located in `jstoolchain`, add:

```jsx
module.exports = {
    plugins: [
        require(‘tailwindcss’),
        require(‘autoprefixer’)
    ]
}
```

Then, run:
`mkdir css`
`npx touch css/tailwind.css`

In this tailwind.css file, add this:

```python
@tailwind base;
@tailwind components;
@tailwind utilities;
```

Now, add a script in your jstoolchain/package.json (create a package.json if it’s not already there) file to create the build process and specify the output file, such as:

```python
{
  “scripts”: {
    “build”: “postcss css/tailwind.css -o ../your-django-folder/your-path-to-static/css/tailwind-output.css”
  }
}
```

Now, run: 

`npm run-script build`

This should run without error, and tailwind-output.css should now be filled with thousands of lines. 

Now you can actually use Tailwind CSS classes by including the outputted CSS file into a Django template file along with Django’s call to load the static files:

```xml
{% load static %}
<head>
  <link rel=“stylesheet” href=“{% static “css/tailwind-output.css” %}“>
</head>
```

#### **2 - Handling auto-reload locally**

To ease development, what’s missing now is to auto-reload the Django development server when an HTML file is changed and saved. For this, I installed [django-livereload-server](https://github.com/tjwalch/django-livereload-server). Just follow setup instructions. This will work as expected out of the box, without any special configuration.

#### **3 - The purgeCSS process**

When you’re ready to deploy, to ensure the CSS output file is not bloated with useless classes, go to jstoolchain/tailwind.config.js file, and add:

```jsx
  purge: {
    enabled: true,
    content: [‘../your-django-folder/path-to-your-templates/**/*.html’],
  },
```

Your backend is now ready, your API key is configured, and your first products are created—time to start building the site.

Open the `base.html` file located in `snipcartwaigtaildemo/templates`

You’ll need to add references for tailwind-output.css and Snipcart. Add these lines in the `head` of your document :

```python
<!-- //snipcartwaigtaildemo/templates/base.html -->
{% load static %}
<link rel=“stylesheet” href=“{% static “css/tailwind-output.css” %}“>
{% load static wagtailsettings_tags %}
{% get_settings %}
{# Global stylesheets #}
{# Snipcart #}
{% if settings.home.SnipcartSettings.api_key %}
<script async src=“https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js”></script>
<div hidden id=“snipcart” data-api-key=“{{ settings.home.SnipcartSettings.api_key }}“></div>
<link rel=“stylesheet” href=“https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css” />
{% endif %}
```

The Snipcart API key that you configured previously is available via:

```
settings.home.SnipcartSettings.api_key
```

Then, add the navbar and some other `tailwind-output.css` layout elements.

Replace the `body` with these lines:

```python
<body class=“{% block body_class %}{% endblock %}“>
    {% wagtailuserbar %}

    <nav class=“bg-white”>
        <div class=“max-w-7xl mx-auto px-2 sm:px-2 lg:px-2">
          <div class=“relative flex items-center justify-between h-16”>
            <div class=“absolute inset-y-0 left-0 flex items-center sm:hidden”>
              <!-- Mobile menu button-->
            <!--
              <button type=“button” class=“inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white” aria-controls=“mobile-menu” aria-expanded=“false”>
                <span class=“sr-only”>Open main menu</span>
            -->
                <!--
                  Icon when menu is closed.
      
                  Heroicon name: outline/menu
      
                  Menu open: “hidden”, Menu closed: “block”
                -->
                <svg class=“block h-6 w-6" xmlns=“http://www.w3.org/2000/svg” fill=“none” viewBox=“0 0 24 24” stroke=“currentColor” aria-hidden=“true”>
                  <path stroke-linecap=“round” stroke-linejoin=“round” stroke-width=“2" d=“M4 6h16M4 12h16M4 18h16” />
                </svg>
                <!--
                  Icon when menu is open.
      
                  Heroicon name: outline/x
      
                  Menu open: “block”, Menu closed: “hidden”
                -->
                <svg class=“hidden h-6 w-6" xmlns=“http://www.w3.org/2000/svg” fill=“none” viewBox=“0 0 24 24” stroke=“currentColor” aria-hidden=“true”>
                  <path stroke-linecap=“round” stroke-linejoin=“round” stroke-width=“2" d=“M6 18L18 6M6 6l12 12” />
                </svg>
              </button>
            </div>
            <div class=“absolute inset-y-0 right-0 flex items-center pl-0 sm:static sm:inset-auto sm:ml-6 sm:pl-0">
              <div class=“hidden sm:block sm:ml-6”>
                <div class=“flex space-x-4">
                  <!-- Current: “bg-gray-900 text-white”, Default: “text-gray-300 hover:bg-gray-700 hover:text-white” -->
                  <a href=“/shop” class=“hover:bg-gray-200 text-black underline px-3 py-2 rounded-md text-sm font-medium object-left items-left”>Shop</a>
                </div>
              </div>
            </div>
            <div class=“flex-1 flex object-center items-center justify-center sm:items-stretch sm:justify-center px-4”>
              <div class=“hidden sm:block sm:ml-6">
                <div class=“flex-shrink-0 flex object-center items-center”>
                  <a href=“/”><img class=“block lg:hidden h-8 w-auto” src=“/media/original_images/The Shopping Cart Shop nobg Small.svg” alt=“the shopping cart shop”></a>
                  <a href=“/”><img class=“hidden lg:block h-8 w-auto” src=“/media/original_images/The Shopping Cart Shop nobg.svg” alt=“the shopping cart shop”></a>
                </div>
              </div>
            </div>
            <div class=“absolute inset-y-0 right-0 flex items-center pr-0 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
      
              <!-- Profile dropdown -->
              <div class=“ml-3 relative”>
                <div>
                    <a href=“” class=“hover:bg-gray-200 underline btn btn-primary snipcart-checkout text-black px-3 py-2 rounded-md text-sm font-medium”>
                        <i class=“icon icon-apps”></i>
                        View cart :shopping_trolley: (<span class=“snipcart-items-count”></span>)
                    </a>
                </div>
  
    
              </div>
            </div>
          </div>
        </div>
      
        <!-- Mobile menu, show/hide based on menu state. -->
        <div class=“sm:hidden” id=“mobile-menu”>
          <div class=“px-2 pt-2 pb-3 space-y-1">
            <!-- Current: “bg-gray-900 text-white”, Default: “text-gray-300 hover:bg-gray-700 hover:text-white” -->
            <a href=“/” class=“hover:bg-gray-700 text-white block px-3 py-2 rounded-md text-base font-medium”>Products</a>
    
          </div>
        </div>
      </nav>

    <div class=“”>
        {% block content %}{% endblock %}
    </div>

    {# Global javascript #}
    <script type=“text/javascript” src=“{% static ‘js/snipcartwagtaildemo.js’ %}“></script>

    {% block extra_js %}
    {# Override this in templates to add extra javascript #}
    {% endblock %}
</body>
```

### 5.1 Listing products

The first template you need is your index, where products will be listed.

You’ll have to make your products available in your home page context. In any Wagtail Page, you can override a method name `get_context`.

You can add the data that the view will receive in parameters. In my case, I want to set the `products` context variable.

Open the `models.py` file in the `home` folder and update the `HomePage` class:

```python
# ./home/models.py

class HomePage(Page):
    def get_context(self, request):
        context = super().get_context(request)

        context[‘products’] = Product.objects.child_of(self).live()

        return context
```

Then, open the `home_page.html` file located in the `home/templates/home` folder.

Let’s create a simple home page with a button linking to our shop page.

```python
<!-- /.home/templates/home/home_page.html -->
{% extends “base.html” %}
{% load wagtailimages_tags %}

{% block content %}
   <div class=“relative bg-local bg-cover bg-center h-screen”
  style=“background-image: url(media/YOUR_IMAGE)“>
  <div
    class=“justify-center max-w-2xl mx-auto py-8 px-4 sm:py-8 sm:px-6 lg:px-8”>
    <h1 class=“absolute bottom-80 inset-x-0 text-center text-6xl font-normal tracking-tight text-white”>
      The Shopping Cart Shop</h1>
    {% if request.path == ‘/’ %}
    <div class=“absolute bottom-60 left-1/2 transform -translate-x-1/2 -translate-y-1/2”>
      <a href=“/shop”>
        <button class=“bg-white hover:bg-gray-200 text-black font-bold py-2 px-4 rounded”>
          Shop Now
        </button>
      </a>
    </div>
    {% endif %}
{% endblock %}
```

Now we want to display our products on the shop page with a button linked to the product details. Add this to the `home_page.html` before `{% endlock %}` :

```python
<!-- /.home/templates/home/home_page.html -->
{% extends “base.html” %}
{% load wagtailimages_tags %}

{% block content %}

<div class=“relative bg-local bg-cover bg-center h-screen”
  style=“background-image: url(media/original_images/CartCoverLudo-Charles.jpg)“>

  <div
    class=“justify-center max-w-2xl mx-auto py-8 px-4 sm:py-8 sm:px-6 lg:px-8”>
    <h1 class=“absolute bottom-80 inset-x-0 text-center text-6xl font-normal tracking-tight text-white”>
      The Shopping Cart Shop</h1>
    {% if request.path == ‘/’ %}
    <div class=“absolute bottom-60 left-1/2 transform -translate-x-1/2 -translate-y-1/2”>
      <a href=“/shop”>
        <button class=“bg-white hover:bg-gray-200 text-black font-bold py-2 px-4 rounded”>
          Shop Now
        </button>
      </a>
    </div>
    {% endif %}
    <div
      class=“justify-items-center mt-6 grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-2 xl:gap-x-8">
      {% for product in products %}
      <div class=“group relative”>
        <div
          class=“w-full min-h-80 bg-gray-200 aspect-w-1 aspect-h-1 rounded-md overflow-hidden lg:h-80 lg:aspect-none”>
          {% image product.image fill-800x1000 as tmp_image %}
          <img src=“{{ tmp_image.url }}” alt=“” class=“img-responsive object-contain w-full h-full”>
        </div>
        <div class=“mt-4 flex justify-between”>
          <div>
            <h3 class=“text-sm text-gray-700 font-bold”>

              <span aria-hidden=“true” class=“absolute inset-0”></span>
              {{ product.title }}
              
            </h3>
            <p class=“mt-1 text-sm text-gray-800">{{ product.description }}</p>
          </div>
          <p class=“text-sm font-medium text-gray-900”>{{ product.price }}$</p>
        </div>
        <div class=“group-hover:bg-opacity-75 relative left-1/2 transform -translate-x-1/2 -translate-y-1/2 pt-14">
          <a href=“{{ product.get_url }}“>
            <button class=“bg-black hover:bg-gray-800 text-white py-2 px-4 rounded”>
              More Details
            </button>
          </a>
        </div>
      </div>
      {% endfor %}
    </div>
  </div>
</div>
{% endblock %}
```

### 5.2 Product details

The last template is the one showing individual product details along with the Snipcart buy button.

Also, it would be nice to be able to select product options directly on this page before adding them to the cart. So I’ll add a way to choose all custom fields with options directly in the template.

Before writing some HTML, you have to update the view context. Django templates don’t give us 100% access to all Python methods and objects, so things like splitting a string do not work very well.

I decided to override the `get_context` method again. Maybe there’s a better way to do that—let me know in the comments below! :wink:

Also, it would be nice to select product options directly on this page before adding them to the cart. So I’ll add a way to choose all custom fields with options directly in the template.

Before writing some HTML, you have to update the view context. Django templates don’t give us 100% access to all Python methods and objects, so things like splitting a string do not work very well.

I decided to override the `get_context` method again. Maybe there’s a better way to do that—let me know in the comments below! ;)

Open `models.py` from the `home folder` and add this method in the `Product` class:

```python
# ./home/models.py

class Product(Page):

    def get_context(self, request):
        context = super().get_context(request)
        fields = []
        for f in self.custom_fields.get_object_list():
            if f.options:
                f.options_array = f.options.split(‘|')
                fields.append(f)
            else:
                fields.append(f)

        context[‘custom_fields’] = fields

        return context
```

A `custom_fields` array will be available in the `product.html` template.

Create a file named `product.html` in the `home/templates/home` folder. This is the template that will be associated with the `Product` page model.

```python
# ./home/models.py

class Product(Page):

    def get_context(self, request):
        context = super().get_context(request)
        fields = []
        for f in self.custom_fields.get_object_list():
            if f.options:
                f.options_array = f.options.split(‘|')
                fields.append(f)
            else:
                fields.append(f)

        context[‘custom_fields’] = fields

        return context
```

Then, add some JavaScript to update the Snipcart buy button when a custom field selection is made on the page.

Add the following `script` snippet before the `endblock` statement:

```python
<script>
    document.addEventListener(‘DOMContentLoaded’, function () {
        document.querySelector(‘.custom-field-select’).onchange = function (event) {
            if (event.target.dataset.field) {
                document.querySelector(‘.snipcart-add-item’)
                    .dataset[‘itemCustom’ + event.target.dataset.field + ‘Value’] = event.target.value;
            }
        };
    }, false);
</script>
```

This code updates the button data attributes when the `select` value changes.

You now have pretty strong foundations to start your e-commerce project using Django and Wagtail!

These frameworks are very powerful; you could quickly add search functionalities, product suggestions, reviews, etc.

<h2 id=“demo”>Live demo & GitHub repo</h2>

![Django e-commerce homepage](https://snipcart.com/media/205954/django-e-commerce-homepage.png)

> See the [live demo](https://agile-caverns-14237.herokuapp.com/)

> See the [GitHub repo](https://github.com/snipcart/django-ecommerce-wagtail-v3)

## Closing thoughts

I really enjoyed working with Wagtail; it’s simple and intuitive. I have to say that their documentation feels incomplete at times, though. At first, I wanted to change how routing would work and haven’t found anything in their docs.

On the other hand, Tailwind CSS was a joy to work with. Their documentation is beautiful, intuitive, and well documented.

I didn’t have the latest version of Python installed on my laptop (a Mac) at the start, so setting up everything and having this demo up and running took me some time. The hosting of the demo was also a bit of a hurdle. I figure it would be way faster for avid Python developers!

For further exploration, I think Wagtail could be a great headless CMS, especially with [its built-in API](http://docs.wagtail.io/en/latest/advanced_topics/api/).

Overall I enjoyed my little venture with Django, Wagtail CMS, and Tailwind CSS. I think it could be cool to leverage it and strapping it to tools like Next.js or Gatsby to handle the front end.

Let me know in the comments your experience using these tools.