Resolved merge conflicts on docs regarding tenant and schema_name
This commit is contained in:
commit
36b22bc649
18
docs/use.rst
18
docs/use.rst
|
@ -1,37 +1,37 @@
|
|||
===========================
|
||||
Using django-tenant-schemas
|
||||
===========================
|
||||
Creating a Tenant
|
||||
Creating a Tenant
|
||||
-----------------
|
||||
This works just like any other model in django. The first thing we should do is to create the ``public`` tenant to make our main website available. We'll use the previous model we defined for ``Client``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from customers.models import Client
|
||||
|
||||
|
||||
# create your public tenant
|
||||
tenant = Client(domain_url='my-domain.com', # don't add your port or www here! on a local server you'll want to use localhost here
|
||||
schema_name='public',
|
||||
schema_name='public',
|
||||
name='Schemas Inc.',
|
||||
paid_until='2016-12-05',
|
||||
on_trial=False)
|
||||
tenant.save()
|
||||
|
||||
|
||||
Now we can create our first real tenant.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from customers.models import Client
|
||||
|
||||
|
||||
# create your first real tenant
|
||||
tenant = Client(domain_url='tenant.my-domain.com', # don't add your port or www here!
|
||||
schema_name='tenant1',
|
||||
schema_name='tenant1',
|
||||
name='Fonzy Tenant',
|
||||
paid_until='2014-12-05',
|
||||
on_trial=True)
|
||||
tenant.save() # sync_schemas automatically called, your tenant is ready to be used!
|
||||
|
||||
Because you have the tenant middleware installed, 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. The tenant will be made available at ``request.tenant``. By the way, the current schema is also available at ``connection.get_schema()``, which is useful, for example, if you want to hook to any of django's signals.
|
||||
Because you have the tenant middleware installed, 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. The tenant will be made available at ``request.tenant``. By the way, the current schema is also available at ``connection.schema_name``, which is useful, for example, if you want to hook to any of django's signals.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -43,7 +43,7 @@ Every command except tenant_command runs by default on all tenants. You can also
|
|||
|
||||
./manage.py sync_schemas --schema=customer1
|
||||
|
||||
The command ``sync_schemas`` is the most important command on this app. The way it works is that it calls Django's ``syncdb`` in two different ways. First, it calls ``syncdb`` for the ``public`` schema, only syncing the shared apps. Then it runs ``syncdb`` for every tenant in the database, this time only syncing the tenant apps.
|
||||
The command ``sync_schemas`` is the most important command on this app. The way it works is that it calls Django's ``syncdb`` in two different ways. First, it calls ``syncdb`` for the ``public`` schema, only syncing the shared apps. Then it runs ``syncdb`` for every tenant in the database, this time only syncing the tenant apps.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
@ -54,7 +54,7 @@ The options given to ``sync_schemas`` are passed to every ``syncdb``. So if you
|
|||
.. code-block:: bash
|
||||
|
||||
./manage.py sync_schemas --migrate
|
||||
|
||||
|
||||
You can also use the option ``--tenant`` to only sync tenant apps or ``--shared`` to only sync shared apps.
|
||||
|
||||
.. code-block:: bash
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import warnings
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import connection
|
||||
|
@ -24,8 +23,7 @@ class TenantMiddleware(object):
|
|||
connection.set_schema_to_public()
|
||||
hostname_without_port = remove_www_and_dev(request.get_host().split(':')[0])
|
||||
|
||||
TenantModel = get_tenant_model()
|
||||
request.tenant = get_object_or_404(TenantModel, domain_url=hostname_without_port)
|
||||
request.tenant = get_object_or_404(get_tenant_model(), domain_url=hostname_without_port)
|
||||
connection.set_tenant(request.tenant)
|
||||
|
||||
# content type can no longer be cached as public and tenant schemas have different
|
||||
|
|
|
@ -26,12 +26,12 @@ class TenantMixin(models.Model):
|
|||
def save(self, verbosity=1, *args, **kwargs):
|
||||
is_new = self.pk is None
|
||||
|
||||
if is_new and connection.get_schema() != get_public_schema_name():
|
||||
if is_new and connection.schema_name != get_public_schema_name():
|
||||
raise Exception("Can't create tenant outside the public schema. Current schema is %s."
|
||||
% connection.get_schema())
|
||||
elif not is_new and connection.get_schema() not in (self.schema_name, get_public_schema_name()):
|
||||
% connection.schema_name)
|
||||
elif not is_new and connection.schema_name not in (self.schema_name, get_public_schema_name()):
|
||||
raise Exception("Can't update tenant outside it's own schema or the public schema. Current schema is %s."
|
||||
% connection.get_schema())
|
||||
% connection.schema_name)
|
||||
|
||||
super(TenantMixin, self).save(*args, **kwargs)
|
||||
|
||||
|
@ -44,9 +44,9 @@ class TenantMixin(models.Model):
|
|||
Drops the schema related to the tenant instance. Just drop the schema if the parent
|
||||
class model has the attribute auto_drop_schema set to True.
|
||||
"""
|
||||
if connection.get_schema() not in (self.schema_name, get_public_schema_name()):
|
||||
if connection.schema_name not in (self.schema_name, get_public_schema_name()):
|
||||
raise Exception("Can't delete tenant outside it's own schema or the public schema. Current schema is %s."
|
||||
% connection.get_schema())
|
||||
% connection.schema_name)
|
||||
|
||||
if schema_exists(self.schema_name) and self.auto_drop_schema:
|
||||
cursor = connection.cursor()
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import re
|
||||
from django.conf import settings
|
||||
from threading import local
|
||||
from django.utils.importlib import import_module
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import utils
|
||||
from tenant_schemas.utils import get_public_schema_name
|
||||
|
||||
ORIGINAL_BACKEND = getattr(settings, 'ORIGINAL_BACKEND', 'django.db.backends.postgresql_psycopg2')
|
||||
|
@ -19,7 +17,7 @@ def _check_identifier(identifier):
|
|||
raise RuntimeError("Invalid string used for the schema name.")
|
||||
|
||||
|
||||
class PGThread(local):
|
||||
class DatabaseWrapper(original_backend.DatabaseWrapper):
|
||||
"""
|
||||
Indicates if the public schema should be included on the search path.
|
||||
When syncing the db for creating the tables, it's useful to exclude
|
||||
|
@ -30,20 +28,48 @@ class PGThread(local):
|
|||
"""
|
||||
include_public_schema = True
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.set_schema_to_public()
|
||||
|
||||
def set_search_path(self, cursor):
|
||||
def set_tenant(self, tenant, include_public=True):
|
||||
"""
|
||||
Actual search_path modification for the cursor. Database will
|
||||
search schemata from left to right when looking for the object
|
||||
(table, index, sequence, etc.).
|
||||
Main API method to current database schema,
|
||||
but it does not actually modify the db connection.
|
||||
"""
|
||||
self.tenant = tenant
|
||||
self.schema_name = tenant.schema_name
|
||||
self.include_public_schema = include_public
|
||||
|
||||
if self.schema_name is None:
|
||||
def set_schema(self, schema_name, include_public=True):
|
||||
"""
|
||||
Main API method to current database schema,
|
||||
but it does not actually modify the db connection.
|
||||
"""
|
||||
self.tenant = None
|
||||
self.schema_name = schema_name
|
||||
self.include_public_schema = include_public
|
||||
|
||||
def set_schema_to_public(self):
|
||||
"""
|
||||
Instructs to stay in the common 'public' schema.
|
||||
"""
|
||||
self.tenant = None
|
||||
self.schema_name = get_public_schema_name()
|
||||
|
||||
def _cursor(self):
|
||||
"""
|
||||
Here it happens. We hope every Django db operation using PostgreSQL
|
||||
must go through this to get the cursor handle. We change the path.
|
||||
"""
|
||||
cursor = super(DatabaseWrapper, self)._cursor()
|
||||
|
||||
# Actual search_path modification for the cursor. Database will
|
||||
# search schemata from left to right when looking for the object
|
||||
# (table, index, sequence, etc.).
|
||||
if not self.schema_name:
|
||||
raise ImproperlyConfigured("Database schema not set. Did your forget "
|
||||
"to call set_schema() or set_tenant()?")
|
||||
|
||||
_check_identifier(self.schema_name)
|
||||
public_schema_name = get_public_schema_name()
|
||||
if self.schema_name == public_schema_name:
|
||||
|
@ -55,78 +81,5 @@ class PGThread(local):
|
|||
|
||||
return cursor
|
||||
|
||||
def get_schema(self):
|
||||
return self.schema_name
|
||||
|
||||
def get_tenant(self):
|
||||
return self.tenant
|
||||
|
||||
def set_schema(self, schema_name, include_public=True):
|
||||
"""
|
||||
Main API method to current database schema,
|
||||
but it does not actually modify the db connection.
|
||||
"""
|
||||
self.tenant = None
|
||||
self.schema_name = schema_name
|
||||
self.include_public_schema = include_public
|
||||
|
||||
def set_tenant(self, tenant, include_public=True):
|
||||
"""
|
||||
Main API method to current database schema,
|
||||
but it does not actually modify the db connection.
|
||||
"""
|
||||
self.tenant = tenant
|
||||
self.schema_name = tenant.schema_name
|
||||
self.include_public_schema = include_public
|
||||
|
||||
if self.tenant is not None:
|
||||
if self.schema_name != self.tenant.schema_name:
|
||||
raise ImproperlyConfigured("Passed schema '%s' does not match tenant's schema '%s'."
|
||||
% (self.schema_name, self.tenant.schema_name))
|
||||
|
||||
def set_schema_to_public(self):
|
||||
"""
|
||||
Instructs to stay in the common 'public' schema.
|
||||
"""
|
||||
self.tenant = None
|
||||
self.schema_name = get_public_schema_name()
|
||||
|
||||
|
||||
class DatabaseWrapper(original_backend.DatabaseWrapper):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
|
||||
self.pg_thread = PGThread()
|
||||
|
||||
def set_tenant(self, tenant, include_public = True):
|
||||
self.set_settings_schema(tenant.schema_name)
|
||||
self.pg_thread.set_tenant(tenant, include_public)
|
||||
|
||||
def set_schema(self, schema_name, include_public = True):
|
||||
self.set_settings_schema(schema_name)
|
||||
self.pg_thread.set_schema(schema_name, include_public)
|
||||
|
||||
def set_schema_to_public(self):
|
||||
self.set_settings_schema(get_public_schema_name())
|
||||
self.pg_thread.set_schema_to_public()
|
||||
|
||||
def set_settings_schema(self, schema_name):
|
||||
self.settings_dict['SCHEMA'] = schema_name
|
||||
|
||||
def get_schema(self):
|
||||
return self.pg_thread.get_schema()
|
||||
|
||||
def get_tenant(self):
|
||||
return self.pg_thread.get_tenant()
|
||||
|
||||
def _cursor(self):
|
||||
"""
|
||||
Here it happens. We hope every Django db operation using PostgreSQL
|
||||
must go through this to get the cursor handle. We change the path.
|
||||
"""
|
||||
cursor = super(DatabaseWrapper, self)._cursor()
|
||||
cursor = self.pg_thread.set_search_path(cursor)
|
||||
return cursor
|
||||
|
||||
DatabaseError = original_backend.DatabaseError
|
||||
IntegrityError = original_backend.IntegrityError
|
||||
|
|
Loading…
Reference in New Issue