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:

  1. www.citusdata.com/blog/2017/0…
  2. 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

  1. Import it in any file you want to use the library:
    from django_multitenant.fields import *
    from django_multitenant.models import *
    Copy the code
  2. All models should inheritTenantModelClass.Ex: class Product(TenantModel):
  3. Define a file namedtenant_idAnd use it to specify the tenant column.Ex: tenant_id='store_id'
  4. TenantModelAll foreign keys of subclasses should be usedTenantForeignKeyInstead ofmodels.ForeignKey
  5. Achieve the above2An 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

  1. In any file you want to use the library, simply:
    from django_multitenant.mixins import *
    Copy the code
  2. All models should be usedTenantModelMixinAnd djangomodels.ModelOr your customer model classEx: class Product(TenantModelMixin, models.Model):
  3. Define a file namedtenant_idAnd use it to specify the tenant column.Ex: tenant_id='store_id'
  4. TenantModelAll foreign keys of subclasses should be usedTenantForeignKeyInstead ofmodels.ForeignKey
  5. 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

indbLayer automated composite foreign key:

  1. useTenantForeignKeyCreating foreign keys between tenant – related models will automaticallytenant_idAdd to reference queries (for exampleproduct.purchases) and join queries (for exampleproduct__name). If you want to make sure thatdbLayer creates compound foreign keys (withtenant_id), should besettings.pyDatabase inENGINEChange todjango_multitenant.backends.postgresql.
      'default': {
          'ENGINE': 'django_multitenant.backends.postgresql'. . . }Copy the code

Where are tenants set up?

  1. 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
  2. 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

  1. Model.objects.*Most of theAPI.
  2. 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