Python/Django supports distributed multi-tenant databases such as Postgres+Citus.
Easily scale out by adding tenant context to your queries, enabling databases (such as Citus) to efficiently route queries to the correct database nodes.
The architecture for building a multi-tenant database includes creating a database for each tenant, creating a schema for each tenant, and having all tenants share the same table. The library is based on a third design that all tenants share the same table, which assumes that all tenant related models/tables have a tenant_ID column to represent the tenant.
The following links discuss more about the trade-offs of when and how to choose the right schema for your multi-tenant database:
- www.citusdata.com/blog/2016/1…
Other useful links about multi-tenancy:
- www.citusdata.com/blog/2017/0…
- www.citusdata.com/blog/2017/0…
Program source code
Github.com/citusdata/d…
The installation
pip install --no-cache-dir django_multitenant
Copy the code
Supported Django versions/prerequisites.
Python | Django |
---|---|
3.X | 2.2 |
3.X | 3.2 |
3.X | 4.0 |
usage
To use this library, you can use Mixins or have your models inherit from our custom model classes.
Model change
- Import it in any file you want to use the library:
from django_multitenant.fields import * from django_multitenant.models import * Copy the code
- All models should inherit
TenantModel
Class.Ex: class Product(TenantModel):
- Define a file named
tenant_id
And use it to specify the tenant column.Ex: tenant_id='store_id'
TenantModel
All foreign keys of subclasses should be usedTenantForeignKey
Instead ofmodels.ForeignKey
- Achieve the above
2
An example model of the following steps:class Store(TenantModel) : tenant_id = 'id' name = models.CharField(max_length=50) address = models.CharField(max_length=255) email = models.CharField(max_length=50) class Product(TenantModel) : store = models.ForeignKey(Store) tenant_id='store_id' name = models.CharField(max_length=255) description = models.TextField() class Meta(object) : unique_together = ["id"."store"] class Purchase(TenantModel) : store = models.ForeignKey(Store) tenant_id='store_id' product_purchased = TenantForeignKey(Product) Copy the code
Change the model using mixins
- In any file you want to use the library, simply:
from django_multitenant.mixins import * Copy the code
- All models should be used
TenantModelMixin
And djangomodels.Model
Or your customer model classEx: class Product(TenantModelMixin, models.Model):
- Define a file named
tenant_id
And use it to specify the tenant column.Ex: tenant_id='store_id'
TenantModel
All foreign keys of subclasses should be usedTenantForeignKey
Instead ofmodels.ForeignKey
- An example model that implements the two steps above:
class ProductManager(TenantManagerMixin, models.Manager) : pass class Product(TenantModelMixin, models.Model) : store = models.ForeignKey(Store) tenant_id='store_id' name = models.CharField(max_length=255) description = models.TextField() objects = ProductManager() class Meta(object) : unique_together = ["id"."store"] class PurchaseManager(TenantManagerMixin, models.Manager) : pass class Purchase(TenantModelMixin, models.Model) : store = models.ForeignKey(Store) tenant_id='store_id' product_purchased = TenantForeignKey(Product) objects = PurchaseManager() Copy the code
indb
Layer automated composite foreign key:
- use
TenantForeignKey
Creating foreign keys between tenant – related models will automaticallytenant_id
Add to reference queries (for exampleproduct.purchases
) and join queries (for exampleproduct__name
). If you want to make sure thatdb
Layer creates compound foreign keys (withtenant_id
), should besettings.py
Database inENGINE
Change todjango_multitenant.backends.postgresql
.'default': { 'ENGINE': 'django_multitenant.backends.postgresql'. . . }Copy the code
Where are tenants set up?
-
Authentication logic is written using middleware that also sets/unsets tenants for each session/request. This way, developers don’t have to worry about setting up tenants on a per-view basis. Just set it at authentication time, and the library will secure the rest (adding the tenant_ID filter to the query). The above example is implemented as follows:
from django_multitenant.utils import set_current_tenant class MultitenantMiddleware: def __init__(self, get_response) : self.get_response = get_response def __call__(self, request) : if request.user and not request.user.is_anonymous: set_current_tenant(request.user.employee.company) return self.get_response(request) Copy the code
In your Settings, you need to update the MIDDLEWARE Settings to include the Settings you created.
MIDDLEWARE = [ #... # existing items #... 'appname.middleware.MultitenantMiddleware' ] Copy the code
-
Use the SET_CURRENT_tenant (T) API to set tenants in all views that you want based on the tenant scope. This will automatically (without specifying an explicit filter) scope all Django API calls to a single tenant. If current_tenant is not set, the default/native API with no tenant scope is used.
Supported by the API
Model.objects.*
Most of theAPI
.Model.save()
Injection for tenant inherited modelstenant_id
.
s=Store.objects.all(to)0]
set_current_tenant(s)
#All the below API calls would add suitable tenant filters.
#Simple get_queryset()
Product.objects.get_queryset()
#Simple join
Purchase.objects.filter(id=1).filter(store__name='The Awesome Store').filter(product__description='All products are awesome')
#Update
Purchase.objects.filter(id=1).update(id=1)
#Save
p=Product(8.1.'Awesome Shoe'.'These shoes are awesome')
p.save()
#Simple aggregates
Product.objects.count()
Product.objects.filter(store__name='The Awesome Store').count()
#Subqueries
Product.objects.filter(name='Awesome Shoe');
Purchase.objects.filter(product__in=p);
Copy the code
More and more
- Django-multitenant database Project (Python/Django+Postgres+Citus)
- Official example of distributed PostgreSQL Cluster (Citus) – Time series data