Added database router. Allows TENANT_APPS and SHARED_APPS to be synced completely separately without messing with Django settings like INSTALLED_APPS or model.meta.managed. Centralized auth is now possible, but cross schema constraints will not be created. This also makes this app thread safe, fixes #2.

This commit is contained in:
Bernardo Pires 2014-12-27 16:13:37 +01:00
parent 70dd4ad463
commit eed14ccc3c
4 changed files with 43 additions and 26 deletions

View File

@ -21,6 +21,13 @@ Your ``DATABASE_ENGINE`` setting needs to be changed to
# ..
}
}
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).
DATABASE_ROUTERS = (
'tenant_schemas.routers.TenantSyncRouter',
)
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.

View File

@ -1,5 +1,6 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_apps, get_models
if "south" in settings.INSTALLED_APPS:
from south.management.commands.syncdb import Command as SyncdbCommand
@ -17,13 +18,14 @@ class Command(SyncCommon):
def handle(self, *args, **options):
super(Command, self).handle(*args, **options)
if 'tenant_schemas.routers.TenantSyncRouter' not in settings.DATABASE_ROUTERS:
raise ImproperlyConfigured("DATABASE_ROUTERS setting must contain "
"'tenant_schemas.routers.TenantSyncRouter'.")
if "south" in settings.INSTALLED_APPS:
self.options["migrate"] = False
# save original settings
for model in get_models(include_auto_created=True):
setattr(model._meta, 'was_managed', model._meta.managed)
# Content types may be different on tenants, so reset the cache
ContentType.objects.clear_cache()
if self.sync_public:
@ -31,32 +33,12 @@ class Command(SyncCommon):
if self.sync_tenant:
self.sync_tenant_apps(self.schema_name)
# restore settings
for model in get_models(include_auto_created=True):
model._meta.managed = model._meta.was_managed
def _set_managed_apps(self, included_apps):
""" sets which apps are managed by syncdb """
for model in get_models(include_auto_created=True):
model._meta.managed = False
verbosity = int(self.options.get('verbosity'))
for app_model in get_apps():
app_name = app_model.__name__.replace('.models', '')
if app_name in included_apps:
for model in get_models(app_model, include_auto_created=True):
model._meta.managed = model._meta.was_managed
if model._meta.managed and verbosity >= 3:
self._notice("=== Include Model: %s: %s" % (app_name, model.__name__))
def _sync_tenant(self, tenant):
self._notice("=== Running syncdb for schema: %s" % tenant.schema_name)
connection.set_tenant(tenant, include_public=False)
SyncdbCommand().execute(**self.options)
def sync_tenant_apps(self, schema_name=None):
apps = self.tenant_apps or self.installed_apps
self._set_managed_apps(apps)
if schema_name:
tenant = get_tenant_model().objects.filter(schema_name=schema_name).get()
self._sync_tenant(tenant)
@ -69,7 +51,5 @@ class Command(SyncCommon):
self._sync_tenant(tenant)
def sync_public_apps(self):
apps = self.shared_apps or self.installed_apps
self._set_managed_apps(apps)
SyncdbCommand().execute(**self.options)
self._notice("=== Running syncdb for schema public")

23
tenant_schemas/routers.py Normal file
View File

@ -0,0 +1,23 @@
from django.conf import settings
class TenantSyncRouter(object):
"""
A router to control which applications will be synced,
depending if we are syncing the shared apps or the tenant apps.
"""
def allow_syncdb(self, db, model):
# the imports below need to be done here else django <1.5 goes crazy
# https://code.djangoproject.com/ticket/20704
from django.db import connection
from tenant_schemas.utils import get_public_schema_name, app_labels
if connection.schema_name == get_public_schema_name():
if model._meta.app_label not in app_labels(settings.SHARED_APPS):
return False
else:
if model._meta.app_label not in app_labels(settings.TENANT_APPS):
return False
return None

View File

@ -96,3 +96,10 @@ def schema_exists(schema_name):
cursor.close()
return exists
def app_labels(apps_list):
"""
Returns a list of app labels of the given apps_list
"""
return [app.split('.')[-1] for app in apps_list]