def test_auto_drop_schema_bulk_delete(self): """ When bulk deleting tenants, it should also drop the schemas of tenants that have auto_drop_schema set to True. """ Tenant.auto_drop_schema = True schemas = ['auto_drop_schema1', 'auto_drop_schema2'] for schema in schemas: self.assertFalse(schema_exists(schema)) tenant = Tenant( domain_url='%s.test.com' % schema, schema_name=schema ) tenant.save(verbosity=BaseTestCase.get_verbosity()) self.assertTrue(schema_exists(tenant.schema_name)) # Force pending trigger events to be executed cursor = connection.cursor() cursor.execute('SET CONSTRAINTS ALL IMMEDIATE') # get a queryset of our 2 tenants and do a bulk delete Tenant.objects.filter(schema_name__in=schemas).delete() # verify that the schemas where deleted for schema in schemas: self.assertFalse(schema_exists(schema)) Tenant.auto_drop_schema = False
def test_auto_drop_schema_bulk_delete(self): """ When bulk deleting tenants, it should also drop the schemas of tenants that have auto_drop_schema set to True. """ Tenant.auto_drop_schema = True schemas = ['auto_drop_schema1', 'auto_drop_schema2'] for schema in schemas: self.assertFalse(schema_exists(schema)) tenant = Tenant(domain_url='%s.test.com' % schema, schema_name=schema) tenant.save(verbosity=BaseTestCase.get_verbosity()) self.assertTrue(schema_exists(tenant.schema_name)) # Force pending trigger events to be executed cursor = connection.cursor() cursor.execute('SET CONSTRAINTS ALL IMMEDIATE') # get a queryset of our 2 tenants and do a bulk delete Tenant.objects.filter(schema_name__in=schemas).delete() # verify that the schemas where deleted for schema in schemas: self.assertFalse(schema_exists(schema)) Tenant.auto_drop_schema = False
def test_create_template_schema(self): """ Test that the template schema can be created directly or indirectly """ Tenant.objects.filter(schema_name=Tenant._TEMPLATE_SCHEMA).delete() self.assertFalse(schema_exists(Tenant._TEMPLATE_SCHEMA)) # Also validate that the template will be created using migrations expected = f'INFO:api.iam.models:Using superclass for "{Tenant._TEMPLATE_SCHEMA}" schema creation' with self.assertLogs("api.iam.models", level="INFO") as _logger: Tenant(schema_name=Tenant._TEMPLATE_SCHEMA).save() self.assertIn(expected, _logger.output) self.assertTrue(schema_exists(Tenant._TEMPLATE_SCHEMA)) Tenant.objects.filter(schema_name=Tenant._TEMPLATE_SCHEMA).delete() self.assertFalse(schema_exists(Tenant._TEMPLATE_SCHEMA)) test_schema = "acct90909090" # Also validate that the customer tenant schema will be created using the clone function expected1 = ( f'INFO:api.iam.models:Cloning template schema "{Tenant._TEMPLATE_SCHEMA}" to "{test_schema}" with data' ) expected2 = f'INFO:api.iam.models:Successful clone of "{Tenant._TEMPLATE_SCHEMA}" to "{test_schema}"' with self.assertLogs("api.iam.models", level="INFO") as _logger: Tenant(schema_name=test_schema).save() self.assertIn(expected1, _logger.output) self.assertIn(expected2, _logger.output) self.assertTrue(schema_exists(Tenant._TEMPLATE_SCHEMA)) self.assertTrue(schema_exists(test_schema))
def test_non_auto_sync_tenant(self): """ When saving a tenant that has the flag auto_create_schema as False, the schema should not be created when saving the tenant. """ self.assertFalse(schema_exists('non_auto_sync_tenant')) tenant = NonAutoSyncTenant(domain_url='something.test.com', schema_name='non_auto_sync_tenant') tenant.save(verbosity=BaseTestCase.get_verbosity()) self.assertFalse(schema_exists(tenant.schema_name))
def test_non_auto_sync_tenant(self): """ when saving a tenant that has the flag auto_create_schema as False, the schema should not be created when saving the tenant """ self.assertFalse(schema_exists('non_auto_sync_tenant')) tenant = NonAutoSyncTenant(domain_url='test.com', schema_name='test') tenant.save() self.assertFalse(schema_exists(tenant.schema_name))
def test_clone_schema_missing_clone_func(self): """ Test that the clone function will be applied if it is missing and the clone will succeed """ _drop_clone_func() self.assertFalse(_verify_clone_func()) test_schema = "acct90909091" self.assertFalse(schema_exists(test_schema)) Tenant(schema_name=test_schema).save() self.assertTrue(_verify_clone_func()) self.assertTrue(schema_exists(test_schema))
def test_tenant_object_delete_leaves_template(self): """ Test that deleting a customer schema will leave the template schema untouched """ cust_tenant = "acct90909093" self.assertFalse(schema_exists(cust_tenant)) self.assertTrue(schema_exists(Tenant._TEMPLATE_SCHEMA)) Tenant(schema_name=cust_tenant).save() self.assertTrue(schema_exists(cust_tenant)) Tenant.objects.filter(schema_name=cust_tenant).delete() self.assertFalse(schema_exists(cust_tenant)) self.assertTrue(schema_exists(Tenant._TEMPLATE_SCHEMA))
def test_has_template_rec_missing_template_schema(self): """ Test that am existing template tenant record with a missing tenant schema will throw an exception """ _drop_template_schema() self.assertFalse(schema_exists(Tenant._TEMPLATE_SCHEMA)) test_schema = "acct90909092" with self.assertRaises(CloneSchemaTemplateMissing): Tenant(schema_name=test_schema).save() Tenant.objects.filter(schema_name=Tenant._TEMPLATE_SCHEMA).delete() Tenant(schema_name=test_schema).save() self.assertTrue(schema_exists(test_schema)) self.assertTrue(schema_exists(Tenant._TEMPLATE_SCHEMA))
def process_exception(self, request, exception): if isinstance(exception, (Tenant.DoesNotExist, ProgrammingError)): if (settings.ROOT_URLCONF == "koku.urls" and request.path in reverse("settings") and (not schema_exists(request.tenant.schema_name) or request.tenant.schema_name == "public")): doc_link = generate_doc_link("/") err_page = { "name": "middleware.settings.err", "component": "error-state", "errorTitle": "Configuration Error", "errorDescription": f"Before adding settings you must create a Source for Cost Management. " f"<br /><span><a href={doc_link}>[Learn more]</a></span>", } return JsonResponse([{ "fields": [err_page], "formProps": { "showFormControls": False } }], safe=False, status=200) paginator = EmptyResultsSetPagination([], request) return paginator.get_paginated_response()
def handle(self, *args, **options): super(migrate_schemas.Command, self).handle(*args, **options) self.PUBLIC_SCHEMA_NAME = get_public_schema_name() executor = get_executor(codename=self.executor)(self.args, self.options) if self.sync_public and not self.schema_name: self.schema_name = self.PUBLIC_SCHEMA_NAME if self.sync_public: executor.run_migrations(tenants=[self.schema_name]) if self.sync_tenant: if self.schema_name and self.schema_name != self.PUBLIC_SCHEMA_NAME: if not schema_exists(self.schema_name): msg = f"Schema {self.schema_name} does not exist, skipping." LOG.info(msg) else: tenants = [self.schema_name] else: from django.db.models.expressions import RawSQL tenant_model = get_tenant_model() tenants = list( tenant_model.objects.filter(schema_name__in=RawSQL( """ SELECT nspname::text FROM pg_catalog.pg_namespace WHERE nspname = %s OR nspname ~ '^acct' """, (tenant_model._TEMPLATE_SCHEMA, ), )).values_list("schema_name", flat=True)) executor.run_migrations(tenants=tenants)
def handle(self, *args, **options): super(Command, self).handle(*args, **options) self.PUBLIC_SCHEMA_NAME = get_public_schema_name() executor = get_executor(codename=self.executor)(self.args, self.options) if self.sync_public and not self.schema_name: self.schema_name = self.PUBLIC_SCHEMA_NAME if self.sync_public: executor.run_migrations(tenants=[self.schema_name]) if self.sync_tenant: if self.schema_name and self.schema_name != self.PUBLIC_SCHEMA_NAME: if not schema_exists(self.schema_name): raise MigrationSchemaMissing( 'Schema "{}" does not exist'.format(self.schema_name) ) else: tenants = [self.schema_name] else: tenants = ( get_tenant_model() .objects.exclude(schema_name=get_public_schema_name()) .values_list("schema_name", flat=True) ) executor.run_migrations(tenants=tenants)
def post(self, request): kwargs = { 'active_menu': self.active_menu, 'url_login_new_schema': None, 'form_data': request.POST } tenant_name = request.POST.get('subdomain') email = request.POST.get('email') password = request.POST.get('password') if schema_exists(tenant_name): kwargs['tenant_exist'] = True else: if settings["USE_SSL"]: kwargs["protocolo"] = "https" else: kwargs["protocolo"] = "http" client = Client() client.domain_url = '{0}.{1}'.format(tenant_name, request.tenant.domain_url) client.name = tenant_name client.schema_name = tenant_name client.save() # Ejecutar las migraciones with schema_context(tenant_name): user = User() user.email = email user.set_password(password) user.is_active = True user.is_superuser = True user.save() url_redirect = "{0}.{1}{2}".format(tenant_name, request.META['HTTP_HOST'], reverse('security:login')) kwargs['tenant_exist'] = False kwargs['url_login_new_schema'] = url_redirect return render(request, self.template_name, kwargs)
def clone_schema(schema_name, clone_schema_name, set_connection=True): """ Creates a full clone of an existing schema. """ # check the clone_schema_name like we usually would _check_schema_name(clone_schema_name) if schema_exists(clone_schema_name): raise ValidationError("Schema name already exists") # The schema is changed to public because the clone function should live there. if set_connection: connection.set_schema_to_public() cursor = connection.cursor() # check if the clone_schema function already exists in the db try: cursor.execute("SELECT 'clone_schema'::regproc") except ProgrammingError: _create_clone_schema_function() transaction.commit() sql = 'SELECT clone_schema(%(schema_name)s, %(clone_schema_name)s, true, false)' cursor.execute(sql, { 'schema_name': schema_name, 'clone_schema_name': clone_schema_name }) cursor.close()
def create_schema(self, check_if_exists=False, sync_schema=True, verbosity=1): """ Creates the schema 'schema_name' for this tenant. Optionally checks if the schema already exists before creating it. Returns true if the schema was created, false otherwise. """ # safety check _check_schema_name(self.schema_name) cursor = connection.cursor() if check_if_exists and schema_exists(self.schema_name): return False # create the schema cursor.execute('CREATE SCHEMA %s' % self.schema_name) if sync_schema: call_command('migrate_schemas', schema_name=self.schema_name, interactive=False, verbosity=verbosity) connection.set_schema_to_public()
def create_schema(self, check_if_exists=False, sync_schema=True, verbosity=1): """ Creates the schema 'schema_name' for this tenant. Optionally checks if the schema already exists before creating it. Returns true if the schema was created, false otherwise. """ # safety check _check_identifier(self.schema_name) cursor = connection.cursor() if check_if_exists and schema_exists(self.schema_name): return False # create the schema cursor.execute('CREATE SCHEMA %s' % self.schema_name) if sync_schema: call_command('sync_schemas', schema_name=self.schema_name, tenant=True, public=False, interactive=False, # don't ask to create an admin user migrate_all=True, # migrate all apps directly to last version verbosity=verbosity, ) # fake all migrations if 'south' in settings.INSTALLED_APPS and not django_is_in_test_mode(): call_command('migrate_schemas', fake=True, schema_name=self.schema_name, verbosity=verbosity) connection.set_schema_to_public() return True
def create_schema(self, check_if_exists = False, sync_schema = True, verbosity = 1): """ Creates the schema 'schema_name' for this tenant. Optionally checks if the schema already exists before creating it. Returns true if the schema was created, false otherwise. """ # safety check _check_identifier(self.schema_name) cursor = connection.cursor() if check_if_exists \ and schema_exists(self.schema_name): return False # create the schema cursor.execute('CREATE SCHEMA %s' % self.schema_name) if sync_schema: call_command('sync_schemas', schema_name=self.schema_name, interactive=False, # don't ask to create an admin user migrate_all=True, # migrate all apps directly to last version verbosity=verbosity, ) # fake all migrations if 'south' in settings.INSTALLED_APPS and not django_is_in_test_mode(): call_command('migrate_schemas', fake=True, schema_name=self.schema_name, verbosity=verbosity) return True
def test_nuevo_cliente(self): tenant = TenantBuilder(email='*****@*****.**', owner_name='El nombre de la persona', client_name='mi empresa', domain_url='localhost', password='******') cliente = tenant.crear() self.assertTrue(schema_exists('miempresa')) self.assertEqual(cliente.email, '*****@*****.**') self.assertEqual(cliente.name, 'mi empresa') self.assertEqual(cliente.owner_name, 'El nombre de la persona') self.assertEqual(cliente.schema_name, 'miempresa') self.assertEqual(cliente.domain_url, 'miempresa.localhost') self.assertEqual(cliente.is_active, True) self.assertTrue(tenant.usuario_principal) self.assertEqual(tenant.get_numero_usuarios(), 1) self.assertEqual(tenant.usuario_principal.email, '*****@*****.**') self.assertEqual(tenant.usuario_principal.first_name, 'El nombre de la persona') self.assertEqual(tenant.usuario_principal.last_name, '') self.assertTrue( check_password('1234', tenant.usuario_principal.password))
def create_schema(self, check_if_exists=True, sync_schema=True, verbosity=1): """ If schema is "public" or matches _TEMPLATE_SCHEMA, then use the superclass' create_schema() method. Else, verify the template and inputs and use the database clone function. """ if self.schema_name in ("public", self._TEMPLATE_SCHEMA): LOG.info(f'Using superclass for "{self.schema_name}" schema creation') return super().create_schema(check_if_exists=True, sync_schema=sync_schema, verbosity=verbosity) db_exc = None # Verify name structure if not _is_valid_schema_name(self.schema_name): exc = ValidationError(f'Invalid schema name: "{self.schema_name}"') LOG.error(f"{exc.__class__.__name__}:: {''.join(exc)}") raise exc with transaction.atomic(): # Make sure all of our special pieces are in play ret = self._check_clone_func() if not ret: errmsg = "Missing clone_schema function even after re-applying the function SQL file." LOG.critical(errmsg) raise CloneSchemaFuncMissing(errmsg) ret = self._verify_template(verbosity=verbosity) if not ret: errmsg = f'Template schema "{self._TEMPLATE_SCHEMA}" does not exist' LOG.critical(errmsg) raise CloneSchemaTemplateMissing(errmsg) # Always check to see if the schema exists! LOG.info(f"Check if target schema {self.schema_name} already exists") if schema_exists(self.schema_name): LOG.warning(f'Schema "{self.schema_name}" already exists. Exit with False.') return False # Clone the schema. The database function will check # that the source schema exists and the destination schema does not. try: self._clone_schema() except Exception as dbe: db_exc = dbe LOG.error( f"""Exception {dbe.__class__.__name__} cloning""" + f""" "{self._TEMPLATE_SCHEMA}" to "{self.schema_name}": {str(dbe)}""" ) LOG.info("Setting transaction to exit with ROLLBACK") transaction.set_rollback(True) # Set this transaction context to issue a rollback on exit else: LOG.info(f'Successful clone of "{self._TEMPLATE_SCHEMA}" to "{self.schema_name}"') # Set schema to public (even if there was an exception) with transaction.atomic(): LOG.info("Reset DB search path to public") conn.set_schema_to_public() if db_exc: raise db_exc return True
def _tenant_for_schema(self, schema_name): """Get or create tenant for schema.""" tenant, created = Tenant.objects.get_or_create(schema_name=schema_name) if not schema_exists(schema_name): tenant.create_schema() msg = f"Created tenant {schema_name}" LOG.info(msg) return tenant
def process_exception(self, request, exception): if isinstance(exception, (Tenant.DoesNotExist, ProgrammingError)): if (settings.ROOT_URLCONF == "koku.urls" and request.path in reverse("settings") and not schema_exists(request.tenant.schema_name)): return JsonResponse([{}], safe=False) paginator = EmptyResultsSetPagination([], request) return paginator.get_paginated_response()
def test_tenant_schema_is_created(self): """ when saving a tenant, it's schema should be created """ tenant = Tenant(domain_url='test.com', schema_name='test_tenant') tenant.save() self.assertTrue(schema_exists(tenant.schema_name))
def test_tenant_schema_is_created(self): """ When saving a tenant, it's schema should be created. """ tenant = Tenant(domain_url='something.test.com', schema_name='test') tenant.save() self.assertTrue(schema_exists(tenant.schema_name))
def test_tenant_schema_is_created(self): """ When saving a tenant, it's schema should be created. """ tenant = Tenant(domain_url='something.test.com', schema_name='test') tenant.save(verbosity=BaseTestCase.get_verbosity()) self.assertTrue(schema_exists(tenant.schema_name))
def drop_schema(sender, instance, **kwargs): """ Called in post_delete signal. 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 schema_exists(instance.schema_name) and instance.auto_drop_schema: cursor = connection.cursor() cursor.execute('DROP SCHEMA %s CASCADE' % instance.schema_name)
def test_auto_drop_schema(self): """ When deleting a tenant with auto_drop_schema=True, it should delete the schema associated with the tenant. """ self.assertFalse(schema_exists('auto_drop_tenant')) Tenant.auto_drop_schema = True tenant = Tenant(domain_url='something.test.com', schema_name='auto_drop_tenant') tenant.save(verbosity=BaseTestCase.get_verbosity()) self.assertTrue(schema_exists(tenant.schema_name)) cursor = connection.cursor() # Force pending trigger events to be executed cursor.execute('SET CONSTRAINTS ALL IMMEDIATE') tenant.delete() self.assertFalse(schema_exists(tenant.schema_name)) Tenant.auto_drop_schema = False
def test_tenant_schema_creation_in_specified_db(self): """ When saving a tenant using a db, its schema should be created in that db """ db = random.choice(get_all_dbs()) tenant = Tenant(domain_url='something1.test.com', schema_name='test1') tenant.save( verbosity=BaseTestCase.get_verbosity(), using=db) for d in get_all_dbs(): if d == db: self.assertTrue(schema_exists( tenant.schema_name, db=d)) else: self.assertFalse(schema_exists( tenant.schema_name, db=d))
def run_migrations(self, schema_name, included_apps): if int(self.options.get('verbosity', 1)) >= 1: self._notice("=== Running migrate for schema %s" % schema_name) if not schema_exists(schema_name): raise MigrationSchemaMissing( 'Schema "{}" does not exist'.format(schema_name)) connection.set_schema(schema_name) command = MigrateCommand() command.execute(*self.args, **self.options) connection.set_schema_to_public()
def _verify_template(self, verbosity=1): LOG.info( f'Verify that template schema "{self._TEMPLATE_SCHEMA}" exists') # This is using the teanant table data as the source of truth which can be dangerous. # If this becomes unreliable, then the database itself should be the source of truth # and extra code must be written to handle the sync of the table data to the state of # the database. template_schema = self.__class__.objects.get_or_create( schema_name=self._TEMPLATE_SCHEMA) # Strict check here! Both the record and the schema *should* exist! return template_schema and schema_exists(self._TEMPLATE_SCHEMA)
def run_migrations(self, schema_name, included_apps): if int(self.options.get('verbosity', 1)) >= 1: self._notice("=== Running migrate for schema %s" % schema_name) if not schema_exists(schema_name): raise MigrationSchemaMissing('Schema "{}" does not exist'.format( schema_name)) connection.set_schema(schema_name) command = MigrateCommand() command.execute(*self.args, **self.options) connection.set_schema_to_public()
def create_schema(self, check_if_exists=False, sync_schema=True, verbosity=1): """ Creates the schema 'schema_name' for this tenant. Optionally checks if the schema already exists before creating it. Returns true if the schema was created, false otherwise. """ # safety check _check_schema_name(self.schema_name) cursor = connection.cursor() if check_if_exists and schema_exists(self.schema_name): return False # create the schema cursor.execute('CREATE SCHEMA %s' % self.schema_name) if sync_schema: if django.VERSION >= ( 1, 7, 0, ): call_command('migrate_schemas', 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() post_schema_sync.send(sender=TenantMixin, tenant=self)
def delete(self, *args, **kwargs): """ 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()): raise Exception("Can't delete tenant outside it's own schema or the public schema. Current schema is %s." % connection.get_schema()) if schema_exists(self.schema_name) and self.auto_drop_schema: cursor = connection.cursor() cursor.execute('DROP SCHEMA %s CASCADE' % self.schema_name) super(TenantMixin, self).delete(*args, **kwargs)
def rename_schema(*, schema_name, new_schema_name): """ This renames a schema to a new name. It checks to see if it exists first """ cursor = connection.cursor() if schema_exists(new_schema_name): raise ValidationError("New schema name already exists") if not _is_valid_schema_name(new_schema_name): raise ValidationError("Invalid string used for the schema name.") sql = 'ALTER SCHEMA {0} RENAME TO {1}'.format(schema_name, new_schema_name) cursor.execute(sql) cursor.close()
def post(self, request): kwargs = {'url_login_new_schema': None, 'form_data': request.POST} tenant_name = request.POST.get('subdomain', None) name_fantasy = request.POST.get('name_fantasy', None) email = request.POST.get('email', None) phone_number = request.POST.get('phone_number', None) area_code = phone_number[1:3] phone = phone_number[5:15] phone = phone.replace('-', '') password = request.POST.get('password', None) plan = request.POST.get('plan', None) if schema_exists(tenant_name): print('O inquilino já foi criado!') kwargs['tenant_exist'] = True else: print('Criando inquilino para o provarme') client = Client() site_url = request.tenant.domain_url.replace('www.', '') client.domain_url = '{0}.{1}'.format(tenant_name, site_url) client.name = tenant_name client.name_fantasy = name_fantasy client.is_active = False client.schema_name = 'provarme_' + tenant_name client.save() # Executando as migrações with schema_context('provarme_' + tenant_name): print('Rodando as migrações com o Cliente que foi criado!') user = User() user.email = email user.username = name_fantasy user.set_password(password) user.is_active = True user.is_staff = False user.is_superuser = False user.save() # Lógica para o pagamento do plano e assinatura url_payment = "https://upnid.com/go/p9854?p=n4kj" mail_admins( 'Notificação - Nova contratação do sistema', 'O sistema acabou de criar e aguarda pagamento da instancia: %s' % tenant_name, fail_silently=False) print('Criação de instância finalizada!') print("Redirecionando usuário para o pagamento.....") return HttpResponseRedirect(url_payment)
def delete(self, *args, **kwargs): """ 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.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.schema_name) if schema_exists(self.schema_name) and self.auto_drop_schema: cursor = connection.cursor() cursor.execute('DROP SCHEMA %s CASCADE' % self.schema_name) transaction.commit_unless_managed() super(TenantMixin, self).delete(*args, **kwargs)
def get(self, request): if request.GET.get('tenant_name', False) and schema_exists( request.GET.get('tenant_name')): protocol = "https" if settings['USE_SSL'] else "http" url_redirect = "{0}://{1}.{2}{3}".format( protocol, request.GET.get('tenant_name'), request.META['HTTP_HOST'], reverse_lazy('security:login')) return HttpResponseRedirect(url_redirect) if request.user.is_authenticated: return HttpResponseRedirect(reverse('flow:dashboard')) else: ctx = {'form': self.form} if 'next' in request.GET: ctx['next'] = request.GET['next'] return render(request, self.template_name, ctx)
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 %s." % connection.schema_name) 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) super(TenantMixin, self).delete(*args, **kwargs)
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 %s." % connection.schema_name) if schema_exists(self.schema_name) and (self.auto_drop_schema or force_drop): cursor = connection.cursor() cursor.execute('DROP SCHEMA IF EXISTS %s CASCADE' % self.schema_name) return super(TenantMixin, self).delete(*args, **kwargs)
def drop_schema(sender, instance, **kwargs): """ Called in post_delete signal. Drops the schema related to the tenant instance. Just drop the schema if the parent class model has the attribute auto_drop_schema setted to True. """ # this function is called each time some model object is deleted, even if it is not a # tenant. So we check that a tenant is being deleted: if isinstance(instance, TenantMixin): cursor = connection.cursor() if schema_exists(instance.schema_name) and instance.auto_drop_schema: # remove the schema cursor.execute('DROP SCHEMA %s CASCADE' % instance.schema_name)
def handle(self, *args, **options): self.non_tenant_schemas = settings.PG_EXTRA_SEARCH_PATHS + ['public'] self.sync_tenant = options.get('tenant') self.sync_public = options.get('shared') self.schema_name = options.get('schema_name') self.args = args self.options = options self.PUBLIC_SCHEMA_NAME = get_public_schema_name() if self.schema_name: if self.sync_public: raise CommandError("schema should only be used " "with the --tenant switch.") elif self.schema_name == self.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 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): # Make sure the tenant exists and the schema belongs to # a tenant; We don't want to sync to extensions schema by # mistake if not schema_exists(self.schema_name): raise RuntimeError('Schema "{}" does not exist'.format( self.schema_name)) elif self.schema_name in self.non_tenant_schemas: raise RuntimeError( 'Schema "{}" does not belong to any tenant'.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 create_schema(self, check_if_exists=False, sync_schema=True, verbosity=1): """ Creates the schema 'schema_name' for this tenant. Optionally checks if the schema already exists before creating it. Returns true if the schema was created, false otherwise. """ # safety check _check_schema_name(self.schema_name) cursor = connection.cursor() if check_if_exists and schema_exists(self.schema_name): return False # create the schema cursor.execute('CREATE SCHEMA %s' % self.schema_name) if sync_schema: if django.VERSION >= (1, 7, 0,): call_command('migrate_schemas', 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() post_schema_sync.send(sender=TenantMixin, tenant=self)
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 create_schema(self, check_if_exists=False, sync_schema=True, verbosity=1): """ Creates the schema 'schema_name' for this tenant. Optionally checks if the schema already exists before creating it. Returns true if the schema was created, false otherwise. """ # safety check _check_identifier(self.schema_name) cursor = connection.cursor() if check_if_exists and schema_exists(self.schema_name): return False # 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 user 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() return True
def handle(self, *args, **options): super(Command, self).handle(*args, **options) self.PUBLIC_SCHEMA_NAME = get_public_schema_name() executor = get_executor(codename=self.executor)(self.args, self.options) if self.sync_public and not self.schema_name: self.schema_name = self.PUBLIC_SCHEMA_NAME if self.sync_public: executor.run_migrations(tenants=[self.schema_name]) if self.sync_tenant: if self.schema_name and self.schema_name != self.PUBLIC_SCHEMA_NAME: if not schema_exists(self.schema_name): raise MigrationSchemaMissing('Schema "{}" does not exist'.format( self.schema_name)) else: tenants = [self.schema_name] else: tenants = get_tenant_model().objects.exclude(schema_name=get_public_schema_name()).values_list( 'schema_name', flat=True) executor.run_migrations(tenants=tenants)