Monday, 1 August 2016

Multi Tenancy in Django


  Multi Tenancy is an architecture in which a single instance of software runs on multiple tenants. This means that, you will have same code running for multiple tenants or "Users", each of which can access the data as if the only data in the database is the one that they can see.

For instance, we created a simple Student Record Management project in our previous tutorial about datatables. Let's say, we want that software for all the classes in our school. Or say, we want to implement it for all schools in a city. What will we do? This is where multi tenancy comes in.

One thing we can do is: create multiple instances of the code and use different databases for each of them. The data of different "tenants" or schools will be isolated. But the number of databases on a system is limited. Also, if you want to make changes to the code, you'll have to make changes in each one of those instances. Not cool, right?

Another way to solve this problem is by storing the data in single database, with records of multiple tenants (schools) distinguishable by a column. So, each student will have a "school" attribute and we can filter the query data by that field. But then, the performance and security of data will be affected. It takes only one bad line of code to ruin it all!

A third method is to have same database with different schemas. A schema is like the definition of your database. This approach relies on the fact that if you create different schemas, the data can be isolated more efficiently. You will have different tables for each schema, and one tenant can access only it's own schema. You can have a larger number of tenants with ease and can set up separate subdomains for each tenant.

There's a project called django-tenants, that allows you to implement multi tenancy in your Django app using separate schemas. You must use postgresql as your database backend. So let's begin!

We're using a project same as the dataTableExample as our starting point. I have renamed it to multiTenancyExample.


CREATE POSTGRES DATABASE



Open your terminal and log in to postgrsql shell:
psql -U root

Enter your password and press enter. Then type:
CREATE DATABASE multitenancy;

The shell will display:
CREATE DATABASE



DJANGO POSTGRES CONFIGURATION



Open settings.py and replace the DATABASES dictionary by the following code:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'multitenancy',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': '127.0.0.1',
        'PORT':'',
    }
}

Replace the USER and PASSWORD with your own postgres credentials.

Now create the required migrations by typing:
python manage.py migrate



MULTI TENANCY CONFIGURATION



Install django-tenants app by typing:
sudo pip install django-tenants

Now in your settings.py, inside the DATABASES dictionary, change the ENGINE field:
'ENGINE': 'django_tenants.postgresql_backend',

Add DATABASE_ROUTERS setting:
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)

Inside the MIDDLEWARE_CLASSES, add 'django_tenants.middleware.TenantMiddleware',
before any other middleware:
MIDDLEWARE_CLASSES = (
'django_tenants.middleware.TenantMiddleware',
...

Finally, in the 'context_processors' option in TEMPLATES, make sure that 'django.core.context_processors.request' is present.

CREATING TENANT AND DOMAIN MODELS



We need to create two more models: One for specifying a Tenant and another to specify the Domain at which the schema of a particular tenant is to be used. So, for instance, tenant1 will access the site at tenant1.xyzdomainname.com and tenant2 will access it at tenant2.xyzdomainname.com. You can do it inside the same app or create another app specifically for tenant and domain models. We'll use the later method.
Create a new app named tenants:
python manage.py startapp tenants

Open tenants/models.py and add the following code:
from django_tenants.models import TenantMixin, DomainMixin
class Client(TenantMixin):
    name = models.CharField(max_length=100)
    auto_create_schema = True

class Domain(DomainMixin):
    pass



MORE CONFIGURATION



Now, we need to specify which apps are shared and which apps are tenant-specific. Here, since we have only one user defined app named myApp, we will make it tenant-specific. The tenants app must be shared. So, remove the INSTALLED_APPS dict and add the following code:
SHARED_APPS = (
    'django_tenants', # mandatory
    'tenants', # you must list the app where your tenant model resides in
    'django.contrib.contenttypes',
    'django.contrib.auth',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.admin',
    'django.contrib.staticfiles',
)

TENANT_APPS = (
    # The following Django contrib apps must be in TENANT_APPS
    'django.contrib.contenttypes',
    # your tenant-specific apps
    'myApp',
)

INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]

Set the tenant and domain models:
TENANT_MODEL = "tenants.Client" # app.Model

TENANT_DOMAIN_MODEL = "tenants.Domain" # app.Model

It's time to apply migrations:
python manage.py makemigrations
python manage.py migrate

You will see an output similar to this:







This means that the public schema has been created. This is our default schema.

CREATING TENANTS



Now, if we try to access http://localhost:8000/details/, we won't be able to access it. We need to create a tenant object. Open django shell by typing:
python manage.py shell

Now, we will create a tenant and associate it with a domain. We are using localhost for testing purposes:
from tenants.models import *
tenant = Client(schema_name='public',name='publicSchema')
tenant.save()
domain = Domain()
domain.domain = 'localhost'
domain.tenant = tenant
domain.is_primary = True
domain.save()

Again try to access http://localhost:8000/details. It's working the same as before now. Try adding some rows to it.




Now, we will create another tenant. In the shell, type:
tenant = Client(schema_name='tenant1',name='first tenant')
tenant.save()

It will create another schema named tenant1 after the last command. Bind it to the url test.localhost as:
domain = Domain()
domain.domain = 'test.localhost'
domain.tenant = tenant
domain.is_primary = True
domain.save()

Exit the shell and browse to the url http://test.localhost:8000/details/. No rows will be shown in the table this time.







This is because, the data for public schema and tenant1 schema will be different and one cannot access the other's database. You can add records to this schema and they will be separate from the public schema.

And that's it! You just implemented multi tenancy in a django project!

Click here to read more about multi tenancy.

No comments:

Post a Comment