def test_rename_schema_ok(self): Client = get_tenant_model() tenant = Client(schema_name='test') tenant.save() self.assertTrue(schema_exists(tenant.schema_name)) domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() schema_rename(tenant=Client.objects.filter(pk=tenant.pk).first(), new_schema_name='new_name') self.assertFalse(schema_exists('test')) self.assertTrue(schema_exists('new_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_urls=['something.test.com'], schema_name='test') tenant.save() self.assertFalse(schema_exists(tenant.schema_name))
def test_manager_method_deletes_schema(self): Client = get_tenant_model() Client.auto_drop_schema = True tenant = Client(schema_name='test') tenant.save() self.assertTrue(schema_exists(tenant.schema_name)) domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() Client.objects.filter(pk=tenant.pk).delete() self.assertFalse(schema_exists(tenant.schema_name))
def test_tenant_schema_is_created_atomically(self): """ When saving a tenant, it's schema should be created. This should work in atomic transactions too. """ executor = get_executor() Tenant = get_tenant_model() schema_name = 'test' @transaction.atomic() def atomically_create_tenant(): t = Tenant(schema_name=schema_name) t.save() self.created = [t] if executor == 'simple': atomically_create_tenant() self.assertTrue(schema_exists(schema_name)) elif executor == 'multiprocessing': # Unfortunately, it's impossible for the multiprocessing executor # to assert atomic transactions when creating a tenant with self.assertRaises(transaction.TransactionManagementError): atomically_create_tenant()
def test_tenant_schema_creation_with_only_numbers_name(self): tenant = get_tenant_model()(schema_name='123') tenant.save() self.assertTrue(schema_exists(tenant.schema_name)) self.created = [tenant]
def test_tenant_schema_creation_with_sql_keyword_name(self): tenant = get_tenant_model()(schema_name='select') tenant.save() self.assertTrue(schema_exists(tenant.schema_name)) self.created = [tenant]
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 executor = get_executor(codename=self.executor)(self.args, self.options) if self.sync_public: executor.run_migrations(tenants=[self.PUBLIC_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 RuntimeError('Schema "{}" does not exist'.format( self.schema_name)) else: tenants = [self.schema_name] else: tenants = get_tenant_model().objects.only( 'schema_name').exclude( schema_name=self.PUBLIC_SCHEMA_NAME).values_list( 'schema_name', flat=True) executor.run_migrations(tenants=tenants) tenant_objects = get_tenant_model().objects.filter(schema_name__in=tenants) for tenant in tenant_objects: if not isinstance(tenant, TenantMixin): continue post_schema_migrate.send(sender=TenantMixin, tenant=tenant.serializable_fields())
def clone_schema(self, base_schema_name, new_schema_name, set_connection=True): """ Creates a new schema `new_schema_name` as a clone of an existing schema `old_schema_name`. """ 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: self._create_clone_schema_function() transaction.commit() if schema_exists(new_schema_name): raise ValidationError("New schema name already exists") sql = 'SELECT clone_schema(%(base_schema)s, %(new_schema)s, true, false)' cursor.execute(sql, { 'base_schema': base_schema_name, 'new_schema': new_schema_name }) cursor.close()
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 executor = get_executor(codename=self.executor)(self.args, self.options) if self.sync_public: executor.run_migrations(tenants=[self.PUBLIC_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 RuntimeError('Schema "{}" does not exist'.format( self.schema_name)) else: tenants = [self.schema_name] else: tenants = get_tenant_model().objects.only( 'schema_name').exclude( schema_name=self.PUBLIC_SCHEMA_NAME).values_list( 'schema_name', flat=True) executor.run_migrations(tenants=tenants)
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 test_tenant_schema_is_created(self): """ When saving a tenant, it's schema should be created. """ tenant = Tenant(domain_urls=['something.test.com'], schema_name='test') tenant.save() self.assertTrue(schema_exists(tenant.schema_name))
def test_tenant_schema_creation_with_special_chars(self): """Tests using special characters in schema name.""" schema_names = ('test-hyphen', 'test@at', 'test`backtick') Client = get_tenant_model() for schema_name in schema_names: tenant = Client(schema_name=schema_name) tenant.save() self.assertTrue(schema_exists(tenant.schema_name))
def handle(self, *args, **options): tenants = get_tenant_model().objects.all() for tenant in tenants: if not schema_exists(schema_name=tenant.schema_name): self.stdout.write( self.style.NOTICE("Missing '%s' schema lets create it" % tenant.schema_name)) tenant.create_schema() self.stdout.write("Done")
def test_tenant_schema_is_created(self): """ When saving a tenant, it's schema should be created. """ tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() self.assertTrue(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. """ tenant = get_tenant_model()(schema_name='test') tenant.auto_create_schema = False tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() self.assertFalse(schema_exists(tenant.schema_name))
def handle(self, *args, **options): super().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 executor = get_executor(codename=self.executor)(self.args, self.options) if self.sync_public: executor.run_migrations(tenants=[self.PUBLIC_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, self.options.get('database', None)): raise RuntimeError('Schema "{}" does not exist'.format( self.schema_name)) elif has_multi_type_tenants(): type_field_name = get_multi_type_database_field_name() tenants = get_tenant_model().objects.only('schema_name', type_field_name)\ .filter(schema_name=self.schema_name)\ .values_list('schema_name', type_field_name) executor.run_multi_type_migrations(tenants=tenants) else: tenants = [self.schema_name] executor.run_migrations(tenants=tenants) else: migration_order = get_tenant_migration_order() if has_multi_type_tenants(): type_field_name = get_multi_type_database_field_name() tenants = get_tenant_model().objects.only('schema_name', type_field_name)\ .exclude(schema_name=self.PUBLIC_SCHEMA_NAME)\ .values_list('schema_name', type_field_name) if migration_order is not None: tenants = tenants.order_by(*migration_order) executor.run_multi_type_migrations(tenants=tenants) else: tenants = get_tenant_model().objects.only( 'schema_name').exclude( schema_name=self.PUBLIC_SCHEMA_NAME).values_list( 'schema_name', flat=True) if migration_order is not None: tenants = tenants.order_by(*migration_order) executor.run_migrations(tenants=tenants)
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 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, options) 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, options) 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, options)
def store_tenant(self, clone_schema_from, **fields): connection.set_schema_to_public() cursor = connection.cursor() tm = get_tenant_model() try: if schema_exists(fields["schema_name"]): raise exceptions.ValidationError( "Error: Schema %s already exists." % fields["schema_name"]) try: tenant = tm.objects.get(schema_name=fields["schema_name"]) # IMPORTANT: Enter here, only if the row in table 'Tenant Model' exists # and the Schema Postgre not. # If the schema exists one exception will be thrown and # one new row on table 'Tenants' will be created. tm.objects.filter(id=tenant.id).update(**fields) except tm.DoesNotExist: tenant = tm(**fields) tenant.auto_create_schema = False tenant.save() clone_schema = CloneSchema(cursor) clone_schema.clone(clone_schema_from, tenant.schema_name) return tenant # try: # tenant = get_tenant_model()(**fields) # tenant.auto_create_schema = False # tenant.save() # clone_schema = CloneSchema(cursor) # clone_schema.clone(clone_schema_from, tenant.schema_name) # return tenant except exceptions.ValidationError as e: self.stderr.write("Error: %s" % '; '.join(e.messages)) return None except IntegrityError as e: self.stderr.write("Error: %s" % e.message) return None
def store_tenant(self, clone_schema_from, **fields): connection.set_schema_to_public() cursor = connection.cursor() tm = get_tenant_model() try: if schema_exists(fields["schema_name"]): raise exceptions.ValidationError("Error: Schema %s already exists." % fields["schema_name"]) try: tenant = tm.objects.get(schema_name=fields["schema_name"]) # IMPORTANT: Enter here, only if the row in table 'Tenant Model' exists # and the Schema Postgre not. # If the schema exists one exception will be thrown and # one new row on table 'Tenants' will be created. tm.objects.filter(id=tenant.id).update(**fields) except tm.DoesNotExist: tenant = tm(**fields) tenant.auto_create_schema = False tenant.save() clone_schema = CloneSchema(cursor) clone_schema.clone(clone_schema_from, tenant.schema_name) return tenant # try: # tenant = get_tenant_model()(**fields) # tenant.auto_create_schema = False # tenant.save() # clone_schema = CloneSchema(cursor) # clone_schema.clone(clone_schema_from, tenant.schema_name) # return tenant except exceptions.ValidationError as e: self.stderr.write("Error: %s" % '; '.join(e.messages)) return None except IntegrityError as e: self.stderr.write("Error: %s" % e.message) 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, options) 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, options) 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, options)
def tenant_delete_callback(sender, instance, **kwargs): if not isinstance(instance, get_tenant_model()): return if instance.auto_drop_schema and schema_exists(instance.schema_name): instance._drop_schema(True)
def school_add_save(request): if request.is_ajax(): if request.method == "POST": form = SchoolAddForm(request.POST) if form.is_valid(): data = form.cleaned_data #tenant related data school_name = data.get('school_name') subdomain = data.get('subdomain') description = data.get('description') on_trial = data.get('ontrial') active_until = data.get('active_until') # user related data admin_email = data.get('email') phone = data.get('phone') username = data.get('username') password = data.get('password') if not special_match(subdomain): return HttpResponse(2) # special character is found if schema_exists(subdomain): return HttpResponse(3) # client already exist else: tenant = Client(schema_name=subdomain, name=subdomain, description=description, on_trial=False, active_until=active_until) tenant.save( ) # migrate_schemas will automatically be called # Create Domain domain = Domain() domain.domain = '{}.bitpoint.com'.format(subdomain) domain.tenant = tenant domain.is_primary = True domain.save() with schema_context(tenant.schema_name): admin = User.objects.create_superuser( username=username, password=password, email=admin_email, phone=phone) Setting.objects.create(school_name=school_name, business_email=admin_email, business_phone1=phone).save() Notification.objects.create( user=admin, title='Welcome to Bitpoint inc.', body='Hello {} and Welcome to Bitpoint Inc., \ we wish you and the entire a happy schooling. \ Thank you for choosing bitpoint', message_type='Info') message = 'Dear {}, Welcome to Bitpoint inc.,\ please login to http://{}/app\ using username: {} and password: {}\ '.format(school_name, domain.domain, username, password) send_sms(phone=phone, msg=message) tenant.school_admin = admin tenant.save() return HttpResponse('success') else: form = SchoolAddForm(request.POST) template = "authenticated/ajax/school_add_form_not_valid.html" context = { "form": form, } return render(request, template, context) else: return HttpResponse('Get')