Usage

In order to use this library you can either use Mixins or have your models inherit from our custom model class.

Changes in Models:

  1. In whichever files you want to use the library import it:

    from django_multitenant.fields import *
    from django_multitenant.models import *
    
  2. All models should inherit the TenantModel class. Ex: class Product(TenantModel):

  3. Define a static variable named tenant_id and specify the tenant column using this variable. Ex: tenant_id='store_id'

  4. All foreign keys to TenantModel subclasses should use TenantForeignKey in place of models.ForeignKey

  5. A sample model implementing the above 2 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)
    

Reserved tenant_id keyword

tenant_id column name should not be ‘tenant_id’. ‘tenant_id’ is a reserved keyword across the library.

Example model with correct tenant_id column name:

class Tenant
   tenant_id = 'id'

class Business(TenantModel):
   ten = models.ForeignKey(Tenant, blank=True, null=True, on_delete=models.SET_NULL)
   tenant_id = 'tenant_id' # This is wrong
   tenant_id = 'ten_id' # This is correct

Changes in Models using mixins

  1. In whichever files you want to use the library import it by just saying

    from django_multitenant.mixins import *
    
  2. All models should use the TenantModelMixin and the django models.Model or your customer Model class Ex: class Product(TenantModelMixin, models.Model):

  3. Define a static variable named tenant_id and specify the tenant column using this variable. Ex: tenant_id='store_id'

  4. All foreign keys to TenantModel subclasses should use TenantForeignKey in place of models.ForeignKey

  5. Referenced table in TenenatForeignKey should include a unique key including tenant_id and primary key

    Ex:
    class Meta(object):
         unique_together = ["id", "store"]
    
  6. A sample model implementing the above 3 steps:

    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()
    

Automating composite foreign keys at db layer

  1. Creating foreign keys between tenant related models using TenantForeignKey would automate adding tenant_id to reference queries (ex. product.purchases) and join queries (ex. product__name). If you want to ensure to create composite foreign keys (with tenant_id) at the db layer, you should change the database ENGINE in the settings.py to django_multitenant.backends.postgresql.

    'default': {
          'ENGINE': 'django_multitenant.backends.postgresql',
          ......
          ......
          ......
    }
    

Where to Set the Tenant?

  1. Write authentication logic using a middleware which also sets/unsets a tenant for each session/request. This way developers need not worry about setting a tenant on a per view basis. Just set it while authentication and the library would ensure the rest (adding tenant_id filters to the queries). A sample implementation of the above is 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)
    

    In your settings, you will need to update the MIDDLEWARE setting to include the one you created.

    MIDDLEWARE = [
        # ...
        # existing items
        # ...
        'appname.middleware.MultitenantMiddleware'
    ]
    
  2. Set the tenant using set_current_tenant(t) api in all the views which you want to be scoped based on tenant. This would scope all the django API calls automatically(without specifying explicit filters) to a single tenant. If the current_tenant is not set, then the default/native API without tenant scoping is used.

    def application_function:
      # current_tenant can be stored as a SESSION variable when a user logs in.
      # This should be done by the app
      t = current_tenant
      #set the tenant
      set_current_tenant(t);
      #Django ORM API calls;
      #Command 1;
      #Command 2;
      #Command 3;
      #Command 4;
      #Command 5;
    

Supported APIs

  1. Most of the APIs under Model.objects.*.

  2. Model.save() injects tenant_id for tenant inherited models.

s=Store.objects.all()[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);

Credits

This library uses similar logic of setting/getting tenant object as in django-simple-multitenant. We thank the authors for their efforts.