diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..3b2bec5
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{js,py}]
+charset = utf-8
+
+[*.py]
+indent_style = space
+indent_size = 4
+
+[*.{css,js,less}]
+indent_style = space
+indent_size = 2
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d83cf90
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,27 @@
+sudo: false
+language: python
+python:
+ - 2.7
+ - 3.5
+services:
+ - postgresql
+addons:
+ postgresql: '9.4'
+install: pip install -q tox-travis
+env:
+ - DJANGO=1.8
+ - DJANGO=1.10
+ - DJANGO=1.11
+matrix:
+ fast_finish: true
+script: tox
+before_script: psql -c "CREATE DATABASE dts_test_project;" -U postgres
+deploy:
+ provider: pypi
+ user: bcarneiro
+ password:
+ secure: IJ23U+zTqDmHB8aB8GEhAAZRzOx5CorPe4Mi6KEyafuALWsY5CB95sN24Il01rhPX+3a88krwu+XNhsjEOVjNn4FE1sEiqO15p7hvIh5sj8JxjVqFvU58rScFTsvYIw4DwvO7cZSOQhFihEHtQRHbWd8b95GGqwRjh2yN1HzMsgOrptQC0Op038S4pSeFRGINj1HJbj320ctbLJoP9GBROpMJ71GR2V2RnTunxcMmlx4yVYEIHS1JijcDQ2BlG8ZikIVfeKqoipzd592Sq2JUuhPgL2MMeI8onHFGRJqUJv3elXev4qcXSqclQtGyTOewB5YZBHrqj3Pg134RmiOxapNWe0HaLPjsrflviJuZepU1ETh6epjcbZi4vl32MCQF/v1nhezoP4BLijJWstKmgmwXuFoDra6niUPcU97e1o5OnFkySlV9IcH+vrAo+sNGt0ztRen3zU+cab8ff0CnH1Me23Tzf3H6gkZCaHlol0xB5u75+5QSpgNAlgQfcbpZVqYsRPpCf61qJixfy/QPBydAWQ5uwG6KzgGTffAmAj4WOa3/n+19jsPNS6h0+VNEgRnoGfIzckQ/hrOV9wPIJ5oJ99SNYLnQ4SLl3JqqttryEHyBSRdYQ7wn3pb9oPfq58/47f/NTPNZRAsemVEx+9udm25+AtEAtS3OB6sQgQ=
+ on:
+ distributions: sdist bdist_wheel
+ repo: bernardopires/django-tenant-schemas
+ branch: master
diff --git a/README.rst b/README.rst
index 3d68782..e02f950 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
django-tenant-schemas
=====================
-|PyPi version| |PyPi downloads|
+|PyPi version| |PyPi downloads| |Python versions| |Travis CI| |PostgreSQL|
This application enables `django`_ powered websites to have multiple
tenants via `PostgreSQL schemas`_. A vital feature for every
@@ -9,7 +9,7 @@ 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
+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
@@ -20,10 +20,10 @@ 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
+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`_.
+`PostgreSQL's official documentation on schemas`_.
Why schemas
-----------
@@ -31,7 +31,7 @@ Why schemas
There are typically three solutions for solving the multitenancy
problem.
-1. Isolated Approach: Separate Databases. Each tenant has it’s own
+1. Isolated Approach: Separate Databases. Each tenant has it's own
database.
2. Semi Isolated Approach: Shared Database, Separate Schemas. One
@@ -48,8 +48,8 @@ represents the ideal compromise between simplicity and performance.
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
+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
@@ -58,11 +58,11 @@ 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
+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
+``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
@@ -94,7 +94,7 @@ the host name to identify which view to serve.
Magic
~~~~~
-Everyone loves magic! You’ll be able to have all this barely having to
+Everyone loves magic! You'll be able to have all this barely having to
change your code!
Setup & Documentation
@@ -102,7 +102,7 @@ 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`_.
+`django-tenant-schemas.readthedocs.io`_.
Your ``DATABASE_ENGINE`` setting needs to be changed to
@@ -113,7 +113,7 @@ Your ``DATABASE_ENGINE`` setting needs to be changed to
'ENGINE': 'tenant_schemas.postgresql_backend',
# ..
}
- }
+ }
Add the middleware ``tenant_schemas.middleware.TenantMiddleware`` to the
top of ``MIDDLEWARE_CLASSES``, so that each request can be set to use
@@ -125,9 +125,9 @@ the correct schema.
'tenant_schemas.middleware.TenantMiddleware',
#...
)
-
-Add ``tenant_schemas.routers.TenantSyncRouter`` to your `DATABASE_ROUTERS`
-setting, so that the correct apps can be synced, depending on what's
+
+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).
.. code-block:: python
@@ -160,8 +160,7 @@ created ``Client`` inside an app named ``customers``, your
TENANT_MODEL = "customers.Client" # app.Model
-Now run ``migrate_schemas`` (``sync_schemas`` if you're on Django 1.6 and older),
-this will sync your apps to the ``public`` schema.
+Now run ``migrate_schemas`` to sync your apps to the ``public`` schema.
::
@@ -183,27 +182,31 @@ will automatically create and sync/migrate the schema.
tenant.save()
Any request made to ``tenant.my-domain.com`` will now automatically set
-your PostgreSQL’s ``search_path`` to ``tenant1`` and ``public``, making
+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.
+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
+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-tenant-schemas.readthedocs.io`_.
.. _django: https://www.djangoproject.com/
-.. _PostgreSQL schemas: http://www.postgresql.org/docs/9.5/static/ddl-schemas.html
-.. _PostgreSQL’s official documentation on schemas: http://www.postgresql.org/docs/9.1/static/ddl-schemas.html
+.. _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://img.shields.io/pypi/v/django-tenant-schemas.svg
:target: https://pypi.python.org/pypi/django-tenant-schemas
.. |PyPi downloads| image:: https://img.shields.io/pypi/dm/django-tenant-schemas.svg
:target: https://pypi.python.org/pypi/django-tenant-schemas
-.. _setup: https://django-tenant-schemas.readthedocs.org/en/latest/install.html
-.. _django-tenant-schemas.readthedocs.org: https://django-tenant-schemas.readthedocs.org/en/latest/
+.. |Python versions| image:: https://img.shields.io/pypi/pyversions/django-tenant-schemas.svg
+.. |Travis CI| image:: https://travis-ci.org/bernardopires/django-tenant-schemas.svg?branch=master
+ :target: https://travis-ci.org/bernardopires/django-tenant-schemas
+.. |PostgreSQL| image:: https://img.shields.io/badge/PostgreSQL-9.2%2C%209.3%2C%209.4%2C%209.5%2C%209.6-blue.svg
+.. _setup: https://django-tenant-schemas.readthedocs.io/en/latest/install.html
+.. _django-tenant-schemas.readthedocs.io: https://django-tenant-schemas.readthedocs.io/en/latest/
diff --git a/changelog b/changelog
new file mode 100644
index 0000000..5140116
--- /dev/null
+++ b/changelog
@@ -0,0 +1,5 @@
+django-tenant-schemas (1.4.8-1) unstable; urgency=low
+
+ * source package automatically created by stdeb 0.8.2
+
+ -- Jérôme Schneider Tue, 14 Oct 2014 11:39:16 +0200
diff --git a/compat b/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/compat
@@ -0,0 +1 @@
+7
diff --git a/control b/control
new file mode 100644
index 0000000..96664ed
--- /dev/null
+++ b/control
@@ -0,0 +1,11 @@
+Source: django-tenant-schemas
+Maintainer: Jérôme Schneider
+Section: python
+Priority: optional
+Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 7)
+Standards-Version: 3.9.1
+
+Package: python-django-tenant-schemas
+Architecture: all
+Depends: ${misc:Depends}, ${python:Depends}
+Description: Tenant support for Django using PostgreSQL schemas.
diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst
new file mode 100644
index 0000000..85be259
--- /dev/null
+++ b/docs/advanced_usage.rst
@@ -0,0 +1,61 @@
+==============
+Advanced Usage
+==============
+
+Custom tenant strategies (custom middleware support)
+====================================================
+By default, ``django-tenant-schemas``'s strategies for determining the correct tenant involve extracting it from the URL (e.g. ``mytenant.mydomain.com``). This is done through a middleware, typically ``TenantMiddleware``.
+
+In some situations, it might be useful to use **alternative tenant selection strategies**. For example, consider a website with a fixed URL. An approach for this website might be to pass the tenant through a special header, or to determine it in some other manner based on the request (e.g. using an OAuth token mapped to a tenant). ``django-tenant-schemas`` offer an **easily extensible way to provide your own middleware** with minimal code changes.
+
+To add custom tenant selection strategies, you need to **subclass the** ``BaseTenantMiddleware`` **class and implement its** ``get_tenant`` **method**. This method accepts the current ``request`` object through which you can determine the tenant to use. In addition, for backwards-compatibility reasons, the method also accepts the tenant model class (``TENANT_MODEL``) and the ``hostname`` of the current request. **You should return an instance of your** ``TENANT_MODEL`` **class** from this function.
+After creating your middleware, you should make it the top-most middleware in your list. You should only have one subclass of ``BaseTenantMiddleware`` per project.
+
+Note that you might also wish to extend the other provided middleware classes, such as ``TenantMiddleware``. For example, you might want to chain several strategies together, and you could do so by subclassing the original strategies and manipulating the call to ``super``'s ``get_tenant``.
+
+
+Example: Determine tenant from HTTP header
+------------------------------------------
+Suppose you wanted to determine the current tenant based on a request header (``X-DTS-SCHEMA``). You might implement a simple middleware such as:
+
+.. code-block:: python
+
+ class XHeaderTenantMiddleware(BaseTenantMiddleware):
+ """
+ Determines tenant by the value of the ``X-DTS-SCHEMA`` HTTP header.
+ """
+ def get_tenant(self, model, hostname, request):
+ schema_name = request.META.get('HTTP_X_DTS_SCHEMA', get_public_schema_name())
+ return model.objects.get(schema_name=schema_name)
+
+Your application could now specify the tenant with the ``X-DTS-SCHEMA`` HTTP header. In scenarios where you are configuring individual tenant websites by yourself, each with its own ``nginx`` configuration to redirect to the right tenant, you could use a configuration such as the one below:
+
+
+.. code-block:: nginx
+
+ # /etc/nginx/conf.d/multitenant.conf
+
+ upstream web {
+ server localhost:8000;
+ }
+
+ server {
+ listen 80 default_server;
+ server_name _;
+
+ location / {
+ proxy_pass http://web;
+ proxy_set_header Host $host;
+ }
+ }
+
+ server {
+ listen 80;
+ server_name example.com www.example.com;
+
+ location / {
+ proxy_pass http://web;
+ proxy_set_header Host $host;
+ proxy_set_header X-DTS-SCHEMA example; # triggers XHeaderTenantMiddleware
+ }
+ }
diff --git a/docs/conf.py b/docs/conf.py
index b07c2d4..222c47d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -11,8 +11,6 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys
-import os
import datetime
# If extensions (or modules to document with autodoc) are in another directory,
@@ -24,7 +22,7 @@ import datetime
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc']
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.autosectionlabel',]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -92,6 +90,11 @@ pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
+intersphinx_mapping = {
+ 'django': (
+ 'https://docs.djangoproject.com/en/1.11/',
+ 'https://docs.djangoproject.com/en/1.11/_objects/'),
+}
# -- Options for HTML output ---------------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index 5d634d9..e890259 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,6 +1,6 @@
Welcome to django-tenant-schemas documentation!
===============================================
-This application enables `Django `_ powered websites to have multiple tenants via `PostgreSQL schemas `_. A vital feature for every Software-as-a-Service website.
+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:
@@ -10,7 +10,7 @@ Django provides currently no simple way to support multiple tenants using the sa
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 `_.
+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?
------------
@@ -48,9 +48,10 @@ Contents
.. toctree::
:maxdepth: 2
-
+
install
use
+ advanced_usage
examples
templates
test
diff --git a/docs/install.rst b/docs/install.rst
index ffe586b..ed2b05f 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -1,6 +1,7 @@
-==================
+============
Installation
-==================
+============
+
Assuming you have django installed, the first step is to install ``django-tenant-schemas``.
.. code-block:: bash
@@ -32,17 +33,47 @@ Add `tenant_schemas.routers.TenantSyncRouter` to your `DATABASE_ROUTERS` setting
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.
-If the hostname in the request does not match a valid tenant ``domain_url``, a HTTP 404 Not Found will be returned. If you'd like to raise ``DisallowedHost`` and a HTTP 400 response instead, use the ``tenant_schemas.middleware.SuspiciousTenantMiddleware``.
+If the hostname in the request does not match a valid tenant ``domain_url``, a HTTP 404 Not Found will be returned.
+
+If you'd like to raise ``DisallowedHost`` and a HTTP 400 response instead, use the ``tenant_schemas.middleware.SuspiciousTenantMiddleware``.
+
+If you'd like to serve the public tenant for unrecognised hostnames instead, use ``tenant_schemas.middleware.DefaultTenantMiddleware``. To use a tenant other than the public tenant, create a subclass and register it instead.
+
+If you'd like a different tenant selection technique (e.g. using an HTTP Header), you can define a custom middleware. See :ref:`Advanced Usage`.
.. code-block:: python
-
+
+ from tenant_schemas.middleware import DefaultTenantMiddleware
+
+ class MyDefaultTenantMiddleware(DefaultTenantMiddleware):
+ DEFAULT_SCHEMA_NAME = 'default'
+
+.. code-block:: python
+
MIDDLEWARE_CLASSES = (
'tenant_schemas.middleware.TenantMiddleware',
# 'tenant_schemas.middleware.SuspiciousTenantMiddleware',
+ # 'tenant_schemas.middleware.DefaultTenantMiddleware',
+ # 'myproject.middleware.MyDefaultTenantMiddleware',
#...
)
-
-Make sure you have ``django.core.context_processors.request`` listed under ``TEMPLATE_CONTEXT_PROCESSORS`` else the tenant will not be available on ``request``.
+
+.. code-block:: python
+
+ TEMPLATES = [
+ {
+ 'BACKEND': # ...
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ # ...
+ 'django.template.context_processors.request',
+ # ...
+ ]
+ }
+ }
+ ]
.. code-block:: python
@@ -50,7 +81,7 @@ Make sure you have ``django.core.context_processors.request`` listed under ``TEM
'django.core.context_processors.request',
#...
)
-
+
The Tenant Model
================
Now we have to create your tenant model. Your tenant model can contain whichever fields you want, however, you **must** inherit from ``TenantMixin``. This Mixin only has two fields (``domain_url`` and ``schema_name``) and both are required. Here's an example, suppose we have an app named ``customers`` and we want to create a model called ``Client``.
@@ -59,74 +90,82 @@ Now we have to create your tenant model. Your tenant model can contain whichever
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)
-
+
# default true, schema will be automatically created and synced when it is saved
- auto_create_schema = True
+ auto_create_schema = True
-Once you have defined your model, don't forget to create the migrations for it or otherwise Django >= 1.9 will not create its table. Replace ``customers`` with your app name.
-
-.. code-block:: bash
-
- # Django >= 1.7
- python manage.py makemigrations customers
+Before creating the migrations, we must configure a few specific settings.
Configure Tenant and Shared Applications
========================================
To make use of shared and tenant-specific applications, there are two settings called ``SHARED_APPS`` and ``TENANT_APPS``. ``SHARED_APPS`` is a tuple of strings just like ``INSTALLED_APPS`` and should contain all apps that you want to be synced to ``public``. If ``SHARED_APPS`` is set, then these are the only apps that will be synced to your ``public`` schema! The same applies for ``TENANT_APPS``, it expects a tuple of strings where each string is an app. If set, only those applications will be synced to all your tenants. Here's a sample setting
.. code-block:: python
-
+
SHARED_APPS = (
- 'tenant_schemas', # mandatory
+ 'tenant_schemas', # mandatory, should always be before any django app
'customers', # you must list the app where your tenant model resides in
-
+
'django.contrib.contenttypes',
-
+
# everything below here is optional
- 'django.contrib.auth',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.messages',
- 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
)
-
+
TENANT_APPS = (
- # The following Django contrib apps must be in TENANT_APPS
'django.contrib.contenttypes',
# your tenant-specific apps
'myapp.hotels',
- 'myapp.houses',
+ 'myapp.houses',
)
- INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]
+ INSTALLED_APPS = (
+ 'tenant_schemas', # mandatory, should always be before any django app
+
+ 'customers',
+ 'django.contrib.contenttypes',
+ 'django.contrib.auth',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
+ 'myapp.hotels',
+ 'myapp.houses',
+ )
You also have to set where your tenant model is.
.. code-block:: python
TENANT_MODEL = "customers.Client" # app.Model
-
-Now run ``migrate_schemas --shared`` (``sync_schemas --shared`` if you're on Django 1.6 or older), this will create the shared apps on the ``public`` schema. Note: your database should be empty if this is the first time you're running this command.
+
+Now you must create your app migrations for ``customers``:
+
+.. code-block:: bash
+
+ python manage.py makemigrations customers
+
+The command ``migrate_schemas --shared`` will create the shared apps on the ``public`` schema. Note: your database should be empty if this is the first time you're running this command.
.. code-block:: bash
- # Django >= 1.7
python manage.py migrate_schemas --shared
- # Django < 1.7
- python manage.py sync_schemas --shared
-
.. warning::
- Never use ``migrate`` or ``syncdb`` as it would sync *all* your apps to ``public``!
-
+ Never use ``migrate`` as it would sync *all* your apps to ``public``!
+
Lastly, you need to create a tenant whose schema is ``public`` and it's address is your domain URL. Please see the section on :doc:`use
- django-tenant-schemas · Documentation
+ django-tenant-schemas · Documentation