2014-08-29 17:55:06 +02:00
|
|
|
|
django-tenant-schemas
|
|
|
|
|
=====================
|
|
|
|
|
|
|
|
|
|
|PyPi version| |PyPi downloads|
|
|
|
|
|
|
|
|
|
|
This application enables `django`_ powered websites to have multiple
|
|
|
|
|
tenants via `PostgreSQL schemas`_. A vital feature for every
|
|
|
|
|
Software-as-a-Service website.
|
|
|
|
|
|
|
|
|
|
Django provides currently no simple way to support multiple tenants
|
|
|
|
|
using the same project instance, even when only the data is different.
|
|
|
|
|
Because we don’t want you running many copies of your project, you’ll be
|
|
|
|
|
able to have:
|
|
|
|
|
|
|
|
|
|
- Multiple customers running on the same instance
|
|
|
|
|
- Shared and Tenant-Specific data
|
|
|
|
|
- Tenant View-Routing
|
|
|
|
|
|
|
|
|
|
What are schemas
|
|
|
|
|
----------------
|
|
|
|
|
|
|
|
|
|
A schema can be seen as a directory in an operating system, each
|
|
|
|
|
directory (schema) with it’s own set of files (tables and objects). This
|
|
|
|
|
allows the same table name and objects to be used in different schemas
|
|
|
|
|
without conflict. For an accurate description on schemas, see
|
|
|
|
|
`PostgreSQL’s official documentation on schemas`_.
|
|
|
|
|
|
|
|
|
|
Why schemas
|
|
|
|
|
-----------
|
|
|
|
|
|
|
|
|
|
There are typically three solutions for solving the multitenancy
|
|
|
|
|
problem.
|
|
|
|
|
|
|
|
|
|
1. Isolated Approach: Separate Databases. Each tenant has it’s own
|
|
|
|
|
database.
|
|
|
|
|
|
|
|
|
|
2. Semi Isolated Approach: Shared Database, Separate Schemas. One
|
|
|
|
|
database for all tenants, but one schema per tenant.
|
|
|
|
|
|
|
|
|
|
3. Shared Approach: Shared Database, Shared Schema. All tenants share
|
|
|
|
|
the same database and schema. There is a main tenant-table, where all
|
|
|
|
|
other tables have a foreign key pointing to.
|
|
|
|
|
|
|
|
|
|
This application implements the second approach, which in our opinion,
|
|
|
|
|
represents the ideal compromise between simplicity and performance.
|
|
|
|
|
|
|
|
|
|
- Simplicity: barely make any changes to your current code to support
|
|
|
|
|
multitenancy. Plus, you only manage one database.
|
|
|
|
|
- Performance: make use of shared connections, buffers and memory.
|
|
|
|
|
|
|
|
|
|
Each solution has it’s up and down sides, for a more in-depth
|
|
|
|
|
discussion, see Microsoft’s excellent article on `Multi-Tenant Data
|
|
|
|
|
Architecture`_.
|
|
|
|
|
|
|
|
|
|
How it works
|
|
|
|
|
------------
|
|
|
|
|
|
|
|
|
|
Tenants are identified via their host name (i.e tenant.domain.com). This
|
|
|
|
|
information is stored on a table on the ``public`` schema. Whenever a
|
|
|
|
|
request is made, the host name is used to match a tenant in the
|
|
|
|
|
database. If there’s a match, the search path is updated to use this
|
|
|
|
|
tenant’s schema. So from now on all queries will take place at the
|
|
|
|
|
tenant’s schema. For example, suppose you have a tenant ``customer`` at
|
|
|
|
|
http://customer.example.com. Any request incoming at
|
|
|
|
|
``customer.example.com`` will automatically use ``customer``\ ’s schema
|
|
|
|
|
and make the tenant available at the request. If no tenant is found, a
|
|
|
|
|
404 error is raised. This also means you should have a tenant for your
|
|
|
|
|
main domain, typically using the ``public`` schema. For more information
|
|
|
|
|
please read the `setup`_ section.
|
|
|
|
|
|
|
|
|
|
What can this app do?
|
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
|
|
As many tenants as you want
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
Each tenant has its data on a specific schema. Use a single project
|
|
|
|
|
instance to serve as many as you want.
|
|
|
|
|
|
|
|
|
|
Tenant-specific and shared apps
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
Tenant-specific apps do not share their data between tenants, but you
|
|
|
|
|
can also have shared apps where the information is always available and
|
|
|
|
|
shared between all.
|
|
|
|
|
|
|
|
|
|
Tenant View-Routing
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
You can have different views for ``http://customer.example.com/`` and
|
|
|
|
|
``http://example.com/``, even though Django only uses the string after
|
|
|
|
|
the host name to identify which view to serve.
|
|
|
|
|
|
|
|
|
|
Magic
|
|
|
|
|
~~~~~
|
|
|
|
|
|
|
|
|
|
Everyone loves magic! You’ll be able to have all this barely having to
|
|
|
|
|
change your code!
|
|
|
|
|
|
|
|
|
|
Setup & Documentation
|
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
|
|
**This is just a short setup guide**, it is **strongly** recommended
|
|
|
|
|
that you read the complete version at
|
|
|
|
|
`django-tenant-schemas.readthedocs.org`_.
|
|
|
|
|
|
|
|
|
|
Your ``DATABASE_ENGINE`` setting needs to be changed to
|
|
|
|
|
|
2015-01-30 17:54:08 +01:00
|
|
|
|
.. code-block:: python
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
DATABASES = {
|
|
|
|
|
'default': {
|
|
|
|
|
'ENGINE': 'tenant_schemas.postgresql_backend',
|
|
|
|
|
# ..
|
|
|
|
|
}
|
2014-12-29 12:44:41 +01:00
|
|
|
|
}
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
Add the middleware ``tenant_schemas.middleware.TenantMiddleware`` to the
|
|
|
|
|
top of ``MIDDLEWARE_CLASSES``, so that each request can be set to use
|
|
|
|
|
the correct schema.
|
|
|
|
|
|
2015-01-30 17:54:08 +01:00
|
|
|
|
.. code-block:: python
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
MIDDLEWARE_CLASSES = (
|
|
|
|
|
'tenant_schemas.middleware.TenantMiddleware',
|
|
|
|
|
#...
|
|
|
|
|
)
|
2014-12-29 12:44:41 +01:00
|
|
|
|
|
|
|
|
|
Add ``tenant_schemas.routers.TenantSyncRouter`` to your `DATABASE_ROUTERS`
|
|
|
|
|
setting, so that the correct apps can be synced, depending on what's
|
|
|
|
|
being synced (shared or tenant).
|
|
|
|
|
|
2015-01-30 17:54:08 +01:00
|
|
|
|
.. code-block:: python
|
2014-12-29 12:44:41 +01:00
|
|
|
|
|
|
|
|
|
DATABASE_ROUTERS = (
|
|
|
|
|
'tenant_schemas.routers.TenantSyncRouter',
|
|
|
|
|
)
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
Add ``tenant_schemas`` to your ``INSTALLED_APPS``.
|
|
|
|
|
|
|
|
|
|
Create your tenant model
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
2015-01-30 17:54:08 +01:00
|
|
|
|
.. code-block:: python
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
from django.db import models
|
|
|
|
|
from tenant_schemas.models import TenantMixin
|
|
|
|
|
|
|
|
|
|
class Client(TenantMixin):
|
|
|
|
|
name = models.CharField(max_length=100)
|
|
|
|
|
paid_until = models.DateField()
|
|
|
|
|
on_trial = models.BooleanField()
|
|
|
|
|
created_on = models.DateField(auto_now_add=True)
|
|
|
|
|
|
|
|
|
|
Define on ``settings.py`` which model is your tenant model. Assuming you
|
|
|
|
|
created ``Client`` inside an app named ``customers``, your
|
|
|
|
|
``TENANT_MODEL`` should look like this:
|
|
|
|
|
|
2015-01-30 17:54:08 +01:00
|
|
|
|
.. code-block:: python
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
TENANT_MODEL = "customers.Client" # app.Model
|
|
|
|
|
|
|
|
|
|
Now run ``sync_schemas``, this will sync your apps to the ``public``
|
|
|
|
|
schema.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
python manage.py sync_schemas --shared
|
|
|
|
|
|
|
|
|
|
Create your tenants just like a normal django model. Calling ``save``
|
|
|
|
|
will automatically create and sync the schema.
|
|
|
|
|
|
2015-01-30 17:54:08 +01:00
|
|
|
|
.. code-block:: python
|
2014-08-29 17:55:06 +02:00
|
|
|
|
|
|
|
|
|
from customers.models import Client
|
|
|
|
|
|
|
|
|
|
# create your public tenant
|
|
|
|
|
tenant = Client(domain_url='tenant.my-domain.com',
|
|
|
|
|
schema_name='tenant1',
|
|
|
|
|
name='My First Tenant',
|
|
|
|
|
paid_until='2014-12-05',
|
|
|
|
|
on_trial=True)
|
|
|
|
|
tenant.save()
|
|
|
|
|
|
|
|
|
|
Any request made to ``tenant.my-domain.com`` will now automatically set
|
|
|
|
|
your PostgreSQL’s ``search_path`` to ``tenant1`` and ``public``, making
|
|
|
|
|
shared apps available too. This means that any call to the methods
|
|
|
|
|
``filter``, ``get``, ``save``, ``delete`` or any other function
|
|
|
|
|
involving a database connection will now be done at the tenant’s schema,
|
|
|
|
|
so you shouldn’t need to change anything at your views.
|
|
|
|
|
|
|
|
|
|
You’re all set, but we have left key details outside of this short
|
|
|
|
|
tutorial, such as creating the public tenant and configuring shared and
|
|
|
|
|
tenant specific apps. Complete instructions can be found at
|
|
|
|
|
`django-tenant-schemas.readthedocs.org`_.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. _django: https://www.djangoproject.com/
|
|
|
|
|
.. _PostgreSQL schemas: http://www.postgresql.org/docs/9.1/static/ddl-schemas.html
|
|
|
|
|
.. _PostgreSQL’s official documentation on schemas: http://www.postgresql.org/docs/9.1/static/ddl-schemas.html
|
|
|
|
|
.. _Multi-Tenant Data Architecture: http://msdn.microsoft.com/en-us/library/aa479086.aspx
|
|
|
|
|
|
|
|
|
|
.. |PyPi version| image:: https://pypip.in/v/django-tenant-schemas/badge.png
|
|
|
|
|
:target: https://crate.io/packages/django-tenant-schemas/
|
|
|
|
|
.. |PyPi downloads| image:: https://pypip.in/d/django-tenant-schemas/badge.png
|
|
|
|
|
:target: https://crate.io/packages/django-tenant-schemas/
|
|
|
|
|
.. _setup: https://django-tenant-schemas.readthedocs.org/en/latest/install.html
|
2014-12-29 12:44:41 +01:00
|
|
|
|
.. _django-tenant-schemas.readthedocs.org: https://django-tenant-schemas.readthedocs.org/en/latest/
|