White label with Django made easy

A small screen with an arrow going from it to two other screens much bigger.

Django Framework has great out-of-the-box features that when used wisely can become powerful allies to create quick scalable solutions with less code. The Sites Framework is one of these great hidden gems, especially if you are trying to create a solution that handles multiple websites on the same application, such as White Label platforms.

In this tutorial I will show you how Django can make your life much easier for creating a white label using the sites framework. But first things first: what is a white label application?

In short, it is a solution or product that can be customized by easily inserting a new brand and still make it look like it was tailor-made.

During the tutorial, we will create a white label for an e-commerce website. The structure consists of multiple stores, each one with its own domain. All with the same code base.

If you are already familiar with Django you can skip the “Setup the project” part of this tutorial. If not, I will show you how to create a basic structure, so you can test our new white label with it. Let’s start it!

Setup the project 

First, let’s structure our project by creating a virtual environment and installing Django.

# Create the site folder
mkdir -p white_label_tutorial/src/templates
cd white_label_tutorial/

# Create and activate the virtual env
python3 -m venv venv
source venv/bin/activate

# Install django and create our app
cd src/
pip install django
django-admin startproject app .
python manage.py startapp store

Don’t forget to add our app and template directory to the app/settings.py file.


INSTALLED_APPS = [
    ...,
    "store",
]

TEMPLATES = [
    ...
    "DIRS": [os.path.join(BASE_DIR, "templates")],
    ...
]


Now, let’s create our models. Inside
store/models.py add:


class Store(models.Model):
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class Product(models.Model):
    store = models.ForeignKey(Store, on_delete=models.CASCADE)
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.title

Apply migrations

python manage.py makemigrations
python manage.py migrate

 and register our new models at store/admin.py

from django.contrib import admin
from .models import Store, Product

admin.site.register(Store)
admin.site.register(Product)

Great! To see our progress so far, create a super user

python manage.py createsuperuser

and start the development server.

python manage.py runserver

You can login with your new user and see our templates at the Django admin page:

http://localhost:8000/admin

You can now create stores and products using the Django admin website.

Now, let’s create a view, a template and the url to show the data outside Django admin. Change the following files:

from django.views.generic.list import ListView
from .models import Product


class ProductListView(ListView):
    model = Product
    context_object_name = 'products'

app/urls.py

from store import views

urlpatterns = [
    …,
    path("", views.ProductListView.as_view(), name="product-list"),
]

For brevity, we’re creating the whole document structure in this file and also used inline styles. Read more about template organization and hierarchy here:  https://tutorial.djangogirls.org/en/template_extending/

Create a new file in templates/store/product_list.html

templates/store/product_list.html


<!doctype html>
<html lang="en">
  <head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

	<title>White label - Ecommerce</title>
  </head>
  <body>
	<div class="container" style="display:flex; flex-wrap:wrap">
  	{% for product in products %}
    	<div class="card" style="width:18rem; margin:10px">
          <div class="card-body">
              <h5 class="card-title">{{ product.title }}</h5>
              <p class="card-text">{{ product.description }}</p>
              <a href="#" class="btn btn-primary">Buy now</a>
          </div>
    	</div>
  	{% endfor %}
	</div>
  </body>
</html>

Run the server again with the runserver command if you have stopped it, and open localhost:8000/


All set! We already have what we need to add the Django sites framework to the project.

Adding Sites to the Project

To add the sites framework to the project, let’s change our app/setting.py file by changing INSTALLED_APPS.


INSTALLED_APPS = [
    ...,
    "django.contrib.sites",
    "store",
]

Let’s add the relationship between website and Store model, so each store will have its own Site model.


from django.contrib.sites.models import Site
from django.db import models


class Store(models.Model):
    name = models.CharField(max_length=255)
    site = models.OneToOneField(Site, related_name="store", on_delete=models.CASCADE, null=True)

    def __str__(self):
        return self.name

Then apply the migrations.


python manage.py makemigrations
python manage.py migrate

Now when we access admin we will see a new Site model.

By running the migrations, Django already creates a first example.com site, but we are still able to access our app through the old localhost address.

We will add a new middleware that will only allow registered sites to access our application.

Before that, change the site domain added by Django exemple.com to localhost.com, so our application will continue to work locally. Add a new site myecommerce.com’ to be possible to test the application with a different domain, I will show how to test it locally. The new Site admin should look like this:

Print Screen of a table with two columns (Domain Name and Display Name) and two rows (Localhost.com and myecommerce.com) 

Add in app/settings.py the following middleware


MIDDLEWARE = ​​[
    ...,
    'django.contrib.sites.middleware.CurrentSiteMiddleware',
]

This middleware adds in every request a new site attribute.

For everything to work we need to update the ALLOWED_HOSTS in app/settings.py.  For testing purpose you can use:


ALLOWED_HOSTS = ['*',]

It will accept requests from every host, but it is not a good practice. 

We recommend using a lib to handle environment variables for you, so you don’t need to change the settings file and just change the environment variable. Read more about environment variables here.

To test our application with different domains let’s add a new record to the file etc/hosts (for UNIX-like systems):


127.0.0.1 myecommerce.com
127.0.0.1 localhost.com

Now with the server running we will access the admin interface and link the site we created to our store.

You can now access our site either through localhost.com or myecommerce.com.

We will change our template to show the name of the store that belongs to that site, so just above our div container in templates/store/product_list.html add the following code:

<div class="container">
   <h1>{{request.site.store}}</h1>
</div>

Going to myecommerce.com:8000/ you should see the store name linked to that site.

Finally we will show only the products for that store, so let’s overwrite the get_context_data method on the ProductListView class on store/views.py file.


def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['products'] = Product.objects.filter(store__site=self.request.site)
    return context

This way, every site accessed will only show products filtered for a store associated with the respective domain.

Quick and easy. We set up a white label application using Django’s own tools.
I hope you enjoyed this tutorial. The source code is on GITHUB LINK, if you have questions or suggestions feel free to ping me on matheusfernandes@ckl.io

About the author.

Matheus Fernandes
Matheus Fernandes

A computer engineer who doesn't drink coffee and treats coding as an art.