CREATE SCHEMA myschema;
CREATE TABLE myschema.mytable (
...
);
SELECT * FROM myschema.mytable;
SHOW search_path;
search_path
------------
public
SET search_path TO myschema, public;
architecture in which single instance of application serves multiple customers called tenants.
tenants can have some configuration capabilities such as influencing business logic, introducing some small interface changes as colours, images etc.
architecture in which single instance of application serves only one customer.
the customer has therefore wide range of customisation options including possible access to source code.
DATABASES = {
'default': {
'ENGINE': 'django_tenants.postgresql_backend',
# ..
}
}
DATABASE_ROUTERS = (
'django_tenants.routers.TenantSyncRouter',
)
MIDDLEWARE = (
'django_tenants.middleware.main.TenantMainMiddleware',
#...
)
TEMPLATES = [
{
#...
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
#...
],
},
},
]
from django.db import models
from django_tenants.models import TenantMixin, DomainMixin
class Store(TenantMixin):
name = models.CharField(max_length=100)
street = models.CharField(max_length=100)
city = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
# schema will be automatically created and synced when it is saved
auto_create_schema = True
class Domain(DomainMixin):
pass
SHARED_APPS = (
'django_tenants',
'stores',
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.admin',
)
TENANT_APPS = (
'django.contrib.contenttypes',
'django.contrib.auth',
'invoices',
'employees',
)
INSTALLED_APPS = list(SHARED_APPS) + [
app for app in TENANT_APPS if app not in SHARED_APPS
]
python manage.py makemigrations
python manage.py migrate_schemas
python manage.py syncdb
python manage.py migrate
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO public;
>>> from django.db import connection
>>> connection.tenant
<Store: Supermarket Python>
>>> connection.tenant.name
'Supermarket Python'
>>> connection.tenant.domains.all()
<QuerySet [<Domain: http://python.supermarket.com>]>
def has_permission(self, request, view):
...
tenant = request.tenant
...
>>> from django.db import connection
>>> from stores.models import Store
>>> from employees.models import Employee
>>> python_store = Store.objects.filter(short_name='python').first()
<Store: Supermarket Python>
>> ruby_store = Store.objects.filter(short_name='ruby').first()
<Store: Supermarket Ruby>
>>> connection.set_tenant(python_store)
>>> Employee.objects.all()
<Queryset [<Employee: Cobra>, <Employee: Viper>]
>>> connection.set_tenant(ruby_store)
>>> Employee.objects.all()
<Queryset [<Employee: Diamond>, <Employee: Emerald>]
from django_tenants.utils import schema_context
with schema_context(schema_name):
...
from django_tenants.utils import tenant_context
with tenant_context(tenant):
...
>>> from django_tenants.utils import get_tenant_model
>>> get_tenant_model().objects.all()
<Queryset: [<Store: Supermarket Python>, <Store: Supermarket Ruby>]
>>> from django_tenants.utils import (
get_public_schema_name,
get_tenant_model,
)
>>> get_tenant_model().objects.filter(
schema_name=get_public_schema_name(),
).first()
<Store: Supermarket Public>
...
@app.task
def trigger_actions():
for tenant in get_tenant_model().objects.exclude(
schema_name=get_public_schema_name(),
):
with tenant_context(tenant):
for employee in Employee.objects.all():
trigger_action(employee)
from django_tenants.middleware import TenantMainMiddleware
class TenantDomainHeaderMiddleware(TenantMainMiddleware):
@staticmethod
def hostname_from_request(request):
return request.META.get(
'TENANT_DOMAIN_HEADER',
settings.DEFAULT_TENANT_DOMAIN,
)
MIDDLEWARE_CLASSES = (
'stores_project.middlewares.TenantDomainHeaderMiddleware',
...
)