def save(self, verbosity=1, *args, **kwargs): is_new = self.pk is None if is_new and connection.schema_name != get_public_schema_name(): raise Exception("Can't create tenant outside the public schema. " "Current schema is {}.".format( 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 {}.".format( connection.schema_name)) super().save(*args, **kwargs) if is_new and self.auto_create_schema: try: self.create_schema(check_if_exists=True, verbosity=verbosity) except: # We failed creating the tenant, delete what we created and # re-raise the exception self.delete(force_drop=True) raise else: post_schema_sync.send(sender=TenantMixin, tenant=self)
def set_schema_to_public(self): """ Instructs to stay in the common 'public' schema. """ self.tenant = FakeTenant(schema_name=get_public_schema_name()) self.schema_name = get_public_schema_name() self.set_settings_schema(self.schema_name) self.search_path_set = False
def handle(self, *args, **options): self.sync_tenant = options.get('tenant') self.sync_public = options.get('shared') self.schema_name = options.get('schema_name') self.installed_apps = settings.INSTALLED_APPS self.args = args self.options = options if self.schema_name: if self.sync_public: raise CommandError( "schema should only be used with the --tenant switch.") elif self.schema_name == get_public_schema_name(): self.sync_public = True else: self.sync_tenant = True elif not self.sync_public and not self.sync_tenant: # no options set, sync both self.sync_tenant = True self.sync_public = True if hasattr(settings, 'TENANT_APPS'): self.tenant_apps = settings.TENANT_APPS if hasattr(settings, 'SHARED_APPS'): self.shared_apps = settings.SHARED_APPS
def allow_migrate(db, app_label, model_name=None, **hints): if connection.schema_name == get_public_schema_name(): if app_label not in app_labels(settings.SHARED_APPS): return False else: if app_label not in app_labels(settings.TENANT_APPS): return False return None
def handle(self, *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.run_migrations(self.schema_name, settings.SHARED_APPS) if self.sync_tenant: 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 handle(self, *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.run_migrations(self.schema_name, settings.SHARED_APPS) if self.sync_tenant: 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 save(self, verbosity=1, *args, **kwargs): is_new = self.pk is None if is_new and connection.schema_name != get_public_schema_name(): raise Exception("Can't create tenant outside the public schema. " "Current schema is {}.".format(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 {}.".format(connection.schema_name)) super().save(*args, **kwargs) if is_new and self.auto_create_schema: try: self.create_schema(check_if_exists=True, verbosity=verbosity) except: # We failed creating the tenant, delete what we created and # re-raise the exception self.delete(force_drop=True) raise else: post_schema_sync.send(sender=TenantMixin, tenant=self)
def handle(self, *args, **options): """ Iterates a command over all registered schemata. """ if options['schema_name']: # only run on a particular schema connection.set_schema_to_public() self.execute_command(get_tenant_model().objects.get(schema_name=options['schema_name']), self.COMMAND_NAME, *args, **options) else: for tenant in get_tenant_model().objects.all(): if not (options['skip_public'] and tenant.schema_name == get_public_schema_name()): self.execute_command(tenant, self.COMMAND_NAME, *args, **options)
def delete(self, force_drop=False, *args, **kwargs): """ Deletes this row. Drops the tenant's schema if the attribute auto_drop_schema set to True. """ 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 {}.".format(connection.schema_name)) if schema_exists(self.schema_name) and (self.auto_drop_schema or force_drop): cursor = connection.cursor() cursor.execute('DROP SCHEMA {} CASCADE'.format(self.schema_name)) super().delete(*args, **kwargs)
def handle(self, *args, **options): """ Iterates a command over all registered schemata. """ if options['schema_name']: # only run on a particular schema connection.set_schema_to_public() self.execute_command( get_tenant_model().objects.get( schema_name=options['schema_name']), self.COMMAND_NAME, *args, **options) else: for tenant in get_tenant_model().objects.all(): if not (options['skip_public'] and tenant.schema_name == get_public_schema_name()): self.execute_command(tenant, self.COMMAND_NAME, *args, **options)
def delete(self, force_drop=False, *args, **kwargs): """ Deletes this row. Drops the tenant's schema if the attribute auto_drop_schema set to True. """ 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 {}.".format( connection.schema_name)) if schema_exists(self.schema_name) and (self.auto_drop_schema or force_drop): cursor = connection.cursor() cursor.execute('DROP SCHEMA {} CASCADE'.format(self.schema_name)) super().delete(*args, **kwargs)
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() # optionally limit the number of executions - under load, the execution # of `set search_path` can be quite time consuming if (not get_limit_set_calls()) or not self.search_path_set: # 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 you forget " "to call set_schema() or set_tenant()?") _check_schema_name(self.schema_name) public_schema_name = get_public_schema_name() search_paths = [] if self.schema_name == public_schema_name: search_paths = [public_schema_name] elif self.include_public_schema: search_paths = [self.schema_name, public_schema_name] else: search_paths = [self.schema_name] search_paths.extend(EXTRA_SEARCH_PATHS) # In the event that an error already happened in this transaction and we are going # to rollback we should just ignore database error when setting the search_path # if the next instruction is not a rollback it will just fail also, so # we do not have to worry that it's not the good one try: cursor.execute('SET search_path = {0}'.format( ','.join(search_paths))) except (django.db.utils.DatabaseError, psycopg2.InternalError): self.search_path_set = False else: self.search_path_set = True return cursor
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() # optionally limit the number of executions - under load, the execution # of `set search_path` can be quite time consuming if (not get_limit_set_calls()) or not self.search_path_set: # 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 you forget " "to call set_schema() or set_tenant()?") _check_schema_name(self.schema_name) public_schema_name = get_public_schema_name() search_paths = [] if self.schema_name == public_schema_name: search_paths = [public_schema_name] elif self.include_public_schema: search_paths = [self.schema_name, public_schema_name] else: search_paths = [self.schema_name] search_paths.extend(EXTRA_SEARCH_PATHS) # In the event that an error already happened in this transaction and we are going # to rollback we should just ignore database error when setting the search_path # if the next instruction is not a rollback it will just fail also, so # we do not have to worry that it's not the good one try: cursor.execute('SET search_path = {0}'.format(','.join(search_paths))) except (django.db.utils.DatabaseError, psycopg2.InternalError): self.search_path_set = False else: self.search_path_set = True return cursor
def handle(self, *args, **options): self.sync_tenant = options.get('tenant') self.sync_public = options.get('shared') self.schema_name = options.get('schema_name') self.installed_apps = settings.INSTALLED_APPS self.args = args self.options = options if self.schema_name: if self.sync_public: raise CommandError("schema should only be used with the --tenant switch.") elif self.schema_name == get_public_schema_name(): self.sync_public = True else: self.sync_tenant = True elif not self.sync_public and not self.sync_tenant: # no options set, sync both self.sync_tenant = True self.sync_public = True if hasattr(settings, 'TENANT_APPS'): self.tenant_apps = settings.TENANT_APPS if hasattr(settings, 'SHARED_APPS'): self.shared_apps = settings.SHARED_APPS