Skip to main content

easy-tenants

Tests codecov PyPI Version PyPI downloads

This is a Django app for managing multiple tenants on the same project instance using a shared approach.

Background

There are typically three solutions for solving the multitenancy problem:

  1. Isolated Approach: Separate Databases. Each tenant has it’s own database.
  2. Semi Isolated Approach: Shared Database, Separate Schemas. One database for all tenants, but one schema per tenant.
  3. Shared Approach: Shared Database, Shared Schema. All tenants share the same database and schema. There is a main tenant-table, where all other tables have a foreign key pointing to.

This application implements the third approach, which in our opinion, is the best solution for a large amount of tenants.

For more information: Building Multi Tenant Applications with Django

Below is a demonstration of the features in each approach for an application with 5000 tenants.

Approach Number of DB Number of Schemas Django migration time Public access
Isolated 5000 5000 slow (1/DB) No
Semi Isolated 1 5000 slow (1/Schema) Yes
Shared 1 1 fast (1) Yes

Installation

Assuming you have django installed, the first step is to install django-easy-tenants.

python -m pip install django-easy-tenants

Now you can import the tenancy module in your Django project.

Setup

It is recommended to install this app at the beginning of a project. In an existing project, depending on the structure of the models, the data migration can be hard.

Add easy_tenants to your INSTALLED_APPS on settings.py.

settings.py

INSTALLED_APPS = [
    ...,
    'easy_tenants',
]

Create a model which will be the tenant of the application.

yourapp/models.py

from django.db import models

class Customer(models.Model):
    ...

settings.py

EASY_TENANTS_TENANT_MODEL = "yourapp.Customer"

Your models, which must have isolated data per tenant, we need to add the foreign field from the Customer model. and objects need to be replaced with TenantManager().

from django.db import models
from easy_tenants.models import TenantManager

class Product(models.Model):
    tenant = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)
    name = models.CharField(max_length=10)

    objects = TenantManager()

If you prefer you can use TenantAwareAbstract to implement the save method for you, so when saving an object the tenant will be automatically defined.

class Product(TenantAwareAbstract):
    tenant = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)
    name = models.CharField(max_length=10)

    objects = TenantManager()

If your foreign field has a name other than tenant you can change it with a settings. (default is "tenant")

# models.py
class Product(TenantAwareAbstract):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, editable=False)
    name = models.CharField(max_length=10)

    objects = TenantManager()

# settings.py
EASY_TENANTS_TENANT_FIELD = "customer"

To obtain the data for each tenant, it is necessary to define which tenant will be used:

from easy_tenants import tenant_context

with tenant_context(customer):
    Product.objects.all()  # filter by customer

To define the tenant to be used, this will depend on the business rule used. Here is an example for creating middleware that defines a tenant:

from django.http import HttpResponse
from easy_tenants import tenant_context

class TenantMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        customer = get_customer_by_request(request)

        if not customer:
            return HttpResponse("Select tenant")

        with tenant_context(customer):
            return self.get_response(request)

If you want to separate the upload files by tenant, you need to change the DEFAULT_FILE_STORAGE configuration (only available for local files).

DEFAULT_FILE_STORAGE = 'easy_tenants.storage.TenantFileSystemStorage'

UniqueTenantConstraint

UniqueTenantConstraint is a custom Django constraint that ensures uniqueness of fields within the context of the current tenant. This is especially useful for multi-tenant applications, where you want to allow the same values to exist across different tenants, but enforce uniqueness within each tenant.

How it works

This constraint automatically adds the tenant field to the list of fields being checked for uniqueness. That means, for example, two tenants can have products with the same name and SKU, but a single tenant cannot have duplicate products with the same name and SKU.

Usage Example

Suppose you have a Product model and a Tenant model. You want to make sure that each product's name and sku combination is unique per tenant.

from django.db import models

from easy_tenants.models import TenantAwareAbstract, TenantManager, UniqueTenantConstraint


class Product(TenantAwareAbstract):
    name = models.CharField(max_length=100)
    sku = models.CharField(max_length=50)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    objects = TenantManager()

    class Meta:
        constraints = [
            UniqueTenantConstraint(
                fields=["name", "sku"],
                name="unique_product_name_sku_per_tenant"
            )
        ]

With this constraint, the following is possible:

  • Tenant A can have a product with name "Shirt" and SKU "123".
  • Tenant B can also have a product with name "Shirt" and SKU "123".
  • But Tenant A cannot have two products with the same name "Shirt" and SKU "123".

Notes

  • The tenant field is automatically added to the uniqueness check, so you don't need to include it in the fields list.
  • If the uniqueness constraint is violated within a tenant, a ValidationError will be raised.

Running the example project

python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Access the page /admin/, create a Customer.

Motivation

django-tenant-schemas

django-tenants

django-scopes

Release files for django-easy-tenants 0.9.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-easy-tenants 0.9.7
File Size Uploaded
django_easy_tenants-0.9.7.tar.gz 10.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-easy-tenants 0.9.7
File Interpreter ABI Platform
django_easy_tenants-0.9.7-py3-none-any.whl Python 3 none any Details

Total release size: 19.7 kB

Release files / django_easy_tenants-0.9.7.tar.gz

Download URL django_easy_tenants-0.9.7.tar.gz
Size 10.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5accae238e08a5b28058674b9c1d5c4f2ab3ef6dc3114f777237616ca6cd6bd9
BLAKE2b-256 checksum
How to use checksums
e2072b1074227196bffa035b4ffa19f2e8d29207b92edfa60887488c30adb1c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / django_easy_tenants-0.9.7-py3-none-any.whl

Download URL django_easy_tenants-0.9.7-py3-none-any.whl
Size 9.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d55dbc4fc42a72f6b7388a7c9ea97a3e996c87e0d8b7fbf86d97e340a2ddc99d
BLAKE2b-256 checksum
How to use checksums
4a527346c77f372cc93e43098421c68980e995279b16ef951fb6be451c7de363
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.9.7 This release

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.1

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page