django-tenant-schemas is now compatible with Django 1.7

This commit is contained in:
Bernardo Pires 2015-01-05 11:59:00 +01:00
parent 71e7a89707
commit ac3876d168
6 changed files with 185 additions and 91 deletions

View File

@ -0,0 +1,3 @@
"""
Commands for Django <=1.6 (before south was integrated to Django).
"""

View File

@ -0,0 +1,88 @@
from django.conf import settings
from django.db import connection
from south import migration
from south.migration.base import Migrations
from south.management.commands.migrate import Command as MigrateCommand
from tenant_schemas.management.commands import SyncCommon
from tenant_schemas.utils import get_tenant_model, get_public_schema_name
class Command(SyncCommon):
help = "Migrate schemas with South"
option_list = MigrateCommand.option_list + SyncCommon.option_list
def handle(self, *args, **options):
super(Command, self).handle(*args, **options)
if self.sync_public:
self.migrate_public_apps()
if self.sync_tenant:
self.migrate_tenant_apps(self.schema_name)
def _set_managed_apps(self, included_apps, excluded_apps):
""" while sync_schemas works by setting which apps are managed, on south we set which apps should be ignored """
ignored_apps = []
if excluded_apps:
for item in excluded_apps:
if item not in included_apps:
ignored_apps.append(item)
for app in ignored_apps:
app_label = app.split('.')[-1]
settings.SOUTH_MIGRATION_MODULES[app_label] = 'ignore'
self._clear_south_cache()
def _save_south_settings(self):
self._old_south_modules = None
if hasattr(settings, "SOUTH_MIGRATION_MODULES") and settings.SOUTH_MIGRATION_MODULES is not None:
self._old_south_modules = settings.SOUTH_MIGRATION_MODULES.copy()
else:
settings.SOUTH_MIGRATION_MODULES = dict()
def _restore_south_settings(self):
settings.SOUTH_MIGRATION_MODULES = self._old_south_modules
def _clear_south_cache(self):
for mig in list(migration.all_migrations()):
delattr(mig._application, "migrations")
Migrations._clear_cache()
def _migrate_schema(self, tenant):
connection.set_tenant(tenant, include_public=True)
MigrateCommand().execute(*self.args, **self.options)
def migrate_tenant_apps(self, schema_name=None):
self._save_south_settings()
apps = self.tenant_apps or self.installed_apps
self._set_managed_apps(included_apps=apps, excluded_apps=self.shared_apps)
if schema_name:
self._notice("=== Running migrate for schema: %s" % schema_name)
connection.set_schema_to_public()
tenant = get_tenant_model().objects.get(schema_name=schema_name)
self._migrate_schema(tenant)
else:
all_tenants = get_tenant_model().objects.exclude(schema_name=get_public_schema_name())
if not all_tenants:
self._notice("No tenants found")
for tenant in all_tenants:
Migrations._dependencies_done = False # very important, the dependencies need to be purged from cache
self._notice("=== Running migrate for schema %s" % tenant.schema_name)
self._migrate_schema(tenant)
self._restore_south_settings()
def migrate_public_apps(self):
self._save_south_settings()
apps = self.shared_apps or self.installed_apps
self._set_managed_apps(included_apps=apps, excluded_apps=self.tenant_apps)
self._notice("=== Running migrate for schema public")
MigrateCommand().execute(*self.args, **self.options)
self._clear_south_cache()
self._restore_south_settings()

View File

@ -1,3 +1,4 @@
import django
from django.conf import settings
from django.core.management.base import CommandError, BaseCommand
try:
@ -16,3 +17,8 @@ class Command(MigrateCommand):
"instead. Please read the documentation if you don't know why you "
"shouldn't call migrate directly!".format(database))
super(Command, self).handle(*args, **options)
if django.VERSION >= (1, 7, 0):
from .migrate_schemas import MigrateSchemasCommand
Command = MigrateSchemasCommand

View File

@ -1,88 +1,77 @@
from django.conf import settings
import django
from optparse import NO_DEFAULT
if django.VERSION >= (1, 7, 0):
from django.core.management.commands.migrate import Command as MigrateCommand
from django.db.migrations.recorder import MigrationRecorder
from django.db import connection
from south import migration
from south.migration.base import Migrations
from south.management.commands.migrate import Command as MigrateCommand
from django.conf import settings
from tenant_schemas.utils import get_tenant_model, get_public_schema_name, schema_exists
from tenant_schemas.management.commands import SyncCommon
from tenant_schemas.utils import get_tenant_model, get_public_schema_name
class Command(SyncCommon):
help = "Migrate schemas with South"
option_list = MigrateCommand.option_list + SyncCommon.option_list
class MigrateSchemasCommand(SyncCommon):
help = "Updates database schema. Manages both apps with migrations and those without."
def run_from_argv(self, argv):
"""
Changes the option_list to use the options from the wrapped command.
Adds schema parameter to specify which schema will be used when
executing the wrapped command.
"""
self.option_list += MigrateCommand.option_list
super(MigrateSchemasCommand, self).run_from_argv(argv)
def handle(self, *args, **options):
super(Command, self).handle(*args, **options)
super(MigrateSchemasCommand, self).handle(*args, **options)
self.PUBLIC_SCHEMA_NAME = get_public_schema_name()
if self.sync_public and not self.schema_name:
self.schema_name = self.PUBLIC_SCHEMA_NAME
if self.sync_public:
self.migrate_public_apps()
self.run_migrations(self.schema_name, settings.SHARED_APPS)
if self.sync_tenant:
self.migrate_tenant_apps(self.schema_name)
if self.schema_name and self.schema_name != self.PUBLIC_SCHEMA_NAME:
if not schema_exists(self.schema_name):
raise RuntimeError('Schema "{}" does not exist'.format(
self.schema_name))
else:
self.run_migrations(self.schema_name, settings.TENANT_APPS)
else:
all_tenants = get_tenant_model().objects.exclude(schema_name=get_public_schema_name())
for tenant in all_tenants:
self.run_migrations(tenant.schema_name, settings.TENANT_APPS)
def _set_managed_apps(self, included_apps, excluded_apps):
""" while sync_schemas works by setting which apps are managed, on south we set which apps should be ignored """
ignored_apps = []
if excluded_apps:
for item in excluded_apps:
if item not in included_apps:
ignored_apps.append(item)
def run_migrations(self, schema_name, included_apps):
self._notice("=== Running migrate for schema %s" % schema_name)
# The first step is ensuring the tenant schema already has a migrations table.
connection.set_schema(schema_name, include_public=False)
MigrationRecorder(connection).ensure_schema() # Creates a migrations table if it doesn't exist.
for app in ignored_apps:
app_label = app.split('.')[-1]
settings.SOUTH_MIGRATION_MODULES[app_label] = 'ignore'
# Now that the tenant schema has for sure a migrations table, we can include public and Django will only
# know about the migrations that were done on the tenant.
connection.set_schema(schema_name, include_public=True)
command = MigrateCommand()
self._clear_south_cache()
defaults = {}
for opt in MigrateCommand.option_list:
if opt.dest in self.options:
defaults[opt.dest] = self.options[opt.dest]
elif opt.default is NO_DEFAULT:
defaults[opt.dest] = None
else:
defaults[opt.dest] = opt.default
def _save_south_settings(self):
self._old_south_modules = None
if hasattr(settings, "SOUTH_MIGRATION_MODULES") and settings.SOUTH_MIGRATION_MODULES is not None:
self._old_south_modules = settings.SOUTH_MIGRATION_MODULES.copy()
else:
settings.SOUTH_MIGRATION_MODULES = dict()
command.execute(*self.args, **defaults)
connection.set_schema_to_public()
def _restore_south_settings(self):
settings.SOUTH_MIGRATION_MODULES = self._old_south_modules
def _notice(self, output):
self.stdout.write(self.style.NOTICE(output))
def _clear_south_cache(self):
for mig in list(migration.all_migrations()):
delattr(mig._application, "migrations")
Migrations._clear_cache()
def _migrate_schema(self, tenant):
connection.set_tenant(tenant, include_public=True)
MigrateCommand().execute(*self.args, **self.options)
def migrate_tenant_apps(self, schema_name=None):
self._save_south_settings()
apps = self.tenant_apps or self.installed_apps
self._set_managed_apps(included_apps=apps, excluded_apps=self.shared_apps)
if schema_name:
self._notice("=== Running migrate for schema: %s" % schema_name)
connection.set_schema_to_public()
tenant = get_tenant_model().objects.get(schema_name=schema_name)
self._migrate_schema(tenant)
else:
all_tenants = get_tenant_model().objects.exclude(schema_name=get_public_schema_name())
if not all_tenants:
self._notice("No tenants found")
for tenant in all_tenants:
Migrations._dependencies_done = False # very important, the dependencies need to be purged from cache
self._notice("=== Running migrate for schema %s" % tenant.schema_name)
self._migrate_schema(tenant)
self._restore_south_settings()
def migrate_public_apps(self):
self._save_south_settings()
apps = self.shared_apps or self.installed_apps
self._set_managed_apps(included_apps=apps, excluded_apps=self.tenant_apps)
self._notice("=== Running migrate for schema public")
MigrateCommand().execute(*self.args, **self.options)
self._clear_south_cache()
self._restore_south_settings()
if django.VERSION >= (1, 7, 0):
Command = MigrateSchemasCommand
else:
from .legacy.migrate_schemas import Command

View File

@ -1,3 +1,4 @@
import django
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
@ -16,6 +17,10 @@ class Command(SyncCommon):
option_list = SyncdbCommand.option_list + SyncCommon.option_list
def handle(self, *args, **options):
if django.VERSION >= (1, 7, 0):
raise RuntimeError('This command is only meant to be used for 1.6'
' and older version of django. For 1.7, use'
' `migrate_schemas` instead.')
super(Command, self).handle(*args, **options)
if "south" in settings.INSTALLED_APPS:

View File

@ -1,6 +1,8 @@
import django
from django.conf import settings
from django.db import models, connection, transaction
from django.db import models, connection
from django.core.management import call_command
from tenant_schemas.postgresql_backend.base import _check_schema_name
from tenant_schemas.signals import post_schema_sync
from tenant_schemas.utils import django_is_in_test_mode, schema_exists
@ -68,7 +70,6 @@ class TenantMixin(models.Model):
if schema_exists(self.schema_name) and (self.auto_drop_schema or force_drop):
cursor = connection.cursor()
cursor.execute('DROP SCHEMA %s CASCADE' % self.schema_name)
transaction.commit_unless_managed()
super(TenantMixin, self).delete(*args, **kwargs)
@ -89,27 +90,29 @@ class TenantMixin(models.Model):
# create the schema
cursor.execute('CREATE SCHEMA %s' % self.schema_name)
transaction.commit_unless_managed()
if sync_schema:
# Default is faking all migrations and syncing directly to the
# current models state.
fake_all_migrations = getattr(settings, 'TENANT_CREATION_FAKES_MIGRATIONS', True)
call_command('sync_schemas',
schema_name=self.schema_name,
tenant=True,
public=False,
interactive=False, # don't ask to create an admin
migrate_all=fake_all_migrations,
verbosity=verbosity,
)
# run/fake all migrations
if 'south' in settings.INSTALLED_APPS and not django_is_in_test_mode():
if django.VERSION >= (1, 7, 0,):
call_command('migrate_schemas',
fake=fake_all_migrations,
schema_name=self.schema_name,
interactive=False,
verbosity=verbosity)
else:
# default is faking all migrations and syncing directly to the current models state
fake_all_migrations = getattr(settings, 'TENANT_CREATION_FAKES_MIGRATIONS', True)
call_command('sync_schemas',
schema_name=self.schema_name,
tenant=True,
public=False,
interactive=False,
migrate_all=fake_all_migrations,
verbosity=verbosity)
# run/fake all migrations
if 'south' in settings.INSTALLED_APPS and not django_is_in_test_mode():
call_command('migrate_schemas',
fake=fake_all_migrations,
schema_name=self.schema_name,
verbosity=verbosity)
connection.set_schema_to_public()