How to manage tenants in the multitenant app based on django_tenants and saleor.io platform

Saleor is a great e-commerce open-source platform for building modern online stores. Uses Django framework and GraphQL to deliver a robust backend for creating PWA stores. Enhancing Saleor.io with multi-tenant abilities allows developing a SaaS platform that can manage multiple online stores from one place.

Michał Dziadowicz
Michał Dziadowicz
Aug. 4, 20207 min read
How to manage tenants in the multitenant app based on django_tenants and saleor.io platform

Evaluating multi-tenant architecture

Django framework is a mature framework with many packages that can add multitenancy. There are two main points to evaluate when building such a solution: how to store tenants’ data and which package to use.

Storing data for multiple tenants can be achieved in three different ways:

  • create one database per tenant
  • create one schema per tenant
  • have all tenants share the same table(s)

Each solution has its pros and cons. There are many publications that discuss this in detail. Depending on how many tenants we are expecting or how much customization tenants need, we can choose one that suits us best. In our case, we have chosen “one schema per tenant”, which is a sensible middle ground.

Choosing a package requires evaluating:

  • does it support chosen architecture
  • is it production-ready
  • does it have documentation

We have chosen django_tenants package as it meets all our needs. It’s using database schemas to save tenants’ data, is under active development with a stable release cycle, and has comprehensive documentation.

How does it work?

Every tenant has its own schema in the database. There is also one public schema for storing admin and shared apps. Tenants are identified by their hostnames. Multi-tenant middleware analyzes every request and matches the tenant's hostname with their schema. Then it sets a connection to use the tenant’s schema and all queries are run  in the tenant context. If no tenant is found, a 404 error is raised.

How to build an admin panel

Writing a custom admin panel can be a daunting task requiring writing a lot of boilerplate code for CRUD operations. Instead, we can use Django to build an admin interface from models. It allows us to concentrate on adding additional functionality. The default tenant model contains only information about schema and domains associated with it. First, we can add fields for storing some more useful information about our tenants like their names, contact information, and activation status.
 

class Tenant(TenantMixin):
    full_name = models.CharField(max_length=255, default="")
    is_active = models.BooleanField(default=True, blank=True)
    created_on = models.DateField(auto_now_add=True)
    is_ready = models.BooleanField(default=False, blank=True)
    email = models.EmailField(blank=True)
    note = models.TextField(blank=True)

 

Field is_active can be used to temporary disable tenant and block access to their site. Modifying get_tenant method from TenantMiddleware allows us to check if tenant should be available.

 

from django_tenants.middleware.main import TenantMainMiddleware

class TenantMiddleware(TenantMainMiddleware):
    def get_tenant(self, domain_model, hostname):
        tenant = super().get_tenant(domain_model, hostname)
        if not tenant.is_active:
            raise self.TENANT_NOT_FOUND_EXCEPTION("Tenant is inactive")
        if not tenant.is_ready:
            raise self.TENANT_NOT_FOUND_EXCEPTION("Tenant is not ready")
        return tenant

Displaying a list of tenants in the admin panel requires adding two more classes so that domains associated with the tenant appear inside its change form. It is also a good idea to add protection against accidentally removing a public schema that holds all information.

 

class DomainInline(admin.TabularInline):
    model = Domain
    extra = 0

@admin.register(Tenant)
class TenantAdmin(TenantAdminMixin, admin.ModelAdmin):
list_display = (
        "name",
        "is_active",
        "is_ready",
        "created_on",
    )
inlines = [DomainInline]

def has_delete_permission(self, request, obj=None):
        if obj is not None and obj.schema_name == get_public_schema_name():
            return False
        return super().has_delete_permission(request, obj)

With the working admin panel, we can start adding fields that query data from tenant schema. To make it work, we need a way to change schema_name in the connection object. Using context manager from django_tenants.utils allows us to query data for every tenant independently by just passing tenant object, in this case from admin form:

 

def product_count(self, obj):
    with tenant_context(obj):
        return Product.objects.all().count()

The admin panel can be used to show all sorts of information about the performance of every shop. We can query and display the number of available products, orders placed in a given timespan, compute average order amount and spedition costs and so on.

Conclusion

Adding multitenancy to Saleor.io platform is not a complicated task. By choosing the right tools and utilizing the power of the Django admin panel, we are able to create a good starting point for building a multi-tenant ecommerce platform.

 

Michał Dziadowicz
written by
Michał Dziadowicz

The high-skilled software engineer who is constantly developing his skills and competency within the challenging projects.

Let’s get the cooperation started!

See how we can help your business become more efficient

Get started!

Find us on