def handle(self, *args, **options): all_tenants = get_tenant_model().objects.exclude(schema_name='public') for tenant in all_tenants: connection.set_tenant(tenant) logger.info(f'Set {tenant.schema_name}') call_command('loaddata', *args, **options)
def test_switching_search_path(self): tenant1 = get_tenant_model()(schema_name='tenant1') tenant1.save() domain1 = get_tenant_domain_model()(tenant=tenant1, domain='something.test.com') domain1.save() connection.set_schema_to_public() tenant2 = get_tenant_model()(schema_name='tenant2') tenant2.save() domain2 = get_tenant_domain_model()(tenant=tenant2, domain='example.com') domain2.save() # go to tenant1's path connection.set_tenant(tenant1) # add some data, 2 DummyModels for tenant1 DummyModel(name="Schemas are").save() DummyModel(name="awesome!").save() # switch temporarily to tenant2's path with tenant_context(tenant2): # add some data, 3 DummyModels for tenant2 DummyModel(name="Man,").save() DummyModel(name="testing").save() DummyModel(name="is great!").save() # we should be back to tenant1's path, test what we have self.assertEqual(2, DummyModel.objects.count()) # switch back to tenant2's path with tenant_context(tenant2): self.assertEqual(3, DummyModel.objects.count())
def setUp(self): super(SharedAuthTest, self).setUp() settings.SHARED_APPS = ('django_tenants', 'customers', 'django.contrib.auth', 'django.contrib.contenttypes', ) settings.TENANT_APPS = ('dts_test_app', ) settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS self.sync_shared() self.public_tenant = get_tenant_model()(schema_name=get_public_schema_name()) self.public_tenant.save() self.public_domain = get_tenant_domain_model()(tenant=self.public_tenant, domain='test.com') self.public_domain.save() # Create a tenant self.tenant = get_tenant_model()(schema_name='tenant') self.tenant.save() self.domain = get_tenant_domain_model()(tenant=self.tenant, domain='tenant.test.com') self.domain.save() # Create some users with schema_context(get_public_schema_name()): # this could actually also be executed inside a tenant self.user1 = User(username='******', email="*****@*****.**") self.user1.save() self.user2 = User(username='******', email="*****@*****.**") self.user2.save() # Create instances on the tenant that point to the users on public with tenant_context(self.tenant): self.d1 = ModelWithFkToPublicUser(user=self.user1) self.d1.save() self.d2 = ModelWithFkToPublicUser(user=self.user2) self.d2.save()
def test_files_are_saved_under_subdirectories_per_tenant(self): storage = TenantFileSystemStorage() connection.set_schema_to_public() tenant1 = utils.get_tenant_model()(schema_name='tenant1') tenant1.save() domain1 = utils.get_tenant_domain_model()(tenant=tenant1, domain='something.test.com') domain1.save() connection.set_schema_to_public() tenant2 = utils.get_tenant_model()(schema_name='tenant2') tenant2.save() domain2 = utils.get_tenant_domain_model()(tenant=tenant2, domain='example.com') domain2.save() # this file should be saved on the public schema public_file_name = storage.save('hello_world.txt', ContentFile('Hello World')) public_os_path = storage.path(public_file_name) public_url = storage.url(public_file_name) # switch to tenant1 with utils.tenant_context(tenant1): t1_file_name = storage.save('hello_from_1.txt', ContentFile('Hello T1')) t1_os_path = storage.path(t1_file_name) t1_url = storage.url(t1_file_name) # switch to tenant2 with utils.tenant_context(tenant2): t2_file_name = storage.save('hello_from_2.txt', ContentFile('Hello T2')) t2_os_path = storage.path(t2_file_name) t2_url = storage.url(t2_file_name) # assert the paths are correct self.assertTrue(public_os_path.endswith('apps_dir/media/public/%s' % public_file_name)) self.assertTrue(t1_os_path.endswith('apps_dir/media/tenant1/%s' % t1_file_name)) self.assertTrue(t2_os_path.endswith('apps_dir/media/tenant2/%s' % t2_file_name)) # assert urls are correct self.assertEqual(public_url, '/media/public/%s' % public_file_name) self.assertEqual(t1_url, '/media/tenant1/%s' % t1_file_name) self.assertEqual(t2_url, '/media/tenant2/%s' % t2_file_name) # assert contents are correct with open(public_os_path, 'r') as f: self.assertEqual(f.read(), 'Hello World') with open(t1_os_path, 'r') as f: self.assertEqual(f.read(), 'Hello T1') with open(t2_os_path, 'r') as f: self.assertEqual(f.read(), 'Hello T2')
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 test_sync_tenant(self): """ When editing an existing tenant, all data should be kept. """ tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() # go to tenant's path connection.set_tenant(tenant) # add some data DummyModel(name="Schemas are").save() DummyModel(name="awesome!").save() # edit tenant connection.set_schema_to_public() tenant.domain_urls = ['example.com'] tenant.save() connection.set_tenant(tenant) # test if data is still there self.assertEquals(DummyModel.objects.count(), 2) self.created = [domain, tenant]
def test_tenant_apps_and_shared_apps_can_have_the_same_apps(self): """ Tests that both SHARED_APPS and TENANT_APPS can have apps in common. In this case they should get synced to both tenant and public schemas. """ settings.SHARED_APPS = ('django_tenants', # 2 tables 'customers', 'django.contrib.auth', # 6 tables 'django.contrib.contenttypes', # 1 table 'django.contrib.sessions', ) # 1 table settings.TENANT_APPS = ('django.contrib.sessions', ) # 1 table settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS self.sync_shared() tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='arbitrary.test.com') domain.save() shared_tables = self.get_tables_list_in_schema(get_public_schema_name()) tenant_tables = self.get_tables_list_in_schema(tenant.schema_name) self.assertEqual(2+6+1+1+self.MIGRATION_TABLE_SIZE, len(shared_tables)) self.assertIn('django_session', shared_tables) self.assertEqual(1+self.MIGRATION_TABLE_SIZE, len(tenant_tables)) self.assertIn('django_session', tenant_tables)
def ready(self): # Test for configuration recommendations. These are best practices, # they avoid hard to find bugs and unexpected behaviour. if not hasattr(settings, "TENANT_APPS"): raise ImproperlyConfigured("TENANT_APPS setting not set") if not settings.TENANT_APPS: raise ImproperlyConfigured("TENANT_APPS is empty. " "Maybe you don't need this app?") if not hasattr(settings, "TENANT_MODEL"): raise ImproperlyConfigured("TENANT_MODEL setting not set") if "django_tenants.routers.TenantSyncRouter" not in settings.DATABASE_ROUTERS: raise ImproperlyConfigured( "DATABASE_ROUTERS setting must contain " "'django_tenants.routers.TenantSyncRouter'." ) if hasattr(settings, "PG_EXTRA_SEARCH_PATHS"): if get_public_schema_name() in settings.PG_EXTRA_SEARCH_PATHS: raise ImproperlyConfigured( "%s can not be included on PG_EXTRA_SEARCH_PATHS." % get_public_schema_name() ) # make sure no tenant schema is in settings.PG_EXTRA_SEARCH_PATHS invalid_schemas = set(settings.PG_EXTRA_SEARCH_PATHS).intersection( get_tenant_model().objects.all().values_list("schema_name", flat=True) ) if invalid_schemas: raise ImproperlyConfigured( "Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS." % list(invalid_schemas) )
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 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 store_tenant(self, **fields): try: tenant = get_tenant_model().objects.create(**fields) return tenant except exceptions.ValidationError as e: self.stderr.write("Error: %s" % '; '.join(e.messages)) return None except IntegrityError: return None
def get_tenant(self, domain_model, hostname): try: return super().get_tenant(domain_model, hostname) except domain_model.DoesNotExist: schema_name = self.DEFAULT_SCHEMA_NAME if not schema_name: schema_name = get_public_schema_name() tenant_model = get_tenant_model() return tenant_model.objects.get(schema_name=schema_name)
def setUp(self): super(RoutesTestCase, self).setUp() self.factory = RequestFactory() self.tm = TenantMiddleware() self.tenant_domain = 'tenant.test.com' self.tenant = get_tenant_model()(schema_name='test') self.tenant.save() self.domain = get_tenant_domain_model()(tenant=self.tenant, domain=self.tenant_domain) self.domain.save()
def setUp(self): super().setUp() self.factory = RequestFactory() self.tsf = TenantSubfolderMiddleware(noop_middleware) self.sync_shared() self.public_tenant = get_tenant_model()( schema_name=get_public_schema_name()) self.public_tenant.save() self.public_domain = get_tenant_domain_model()( domain='test.com', tenant=self.public_tenant) self.public_domain.save() self.tenant_domain = 'tenant.test.com' self.tenant = get_tenant_model()(schema_name='test') self.tenant.save() self.domain = get_tenant_domain_model()(tenant=self.tenant, domain=self.tenant_domain) self.domain.save()
def test_files_are_saved_under_subdirectories_per_tenant(self): storage = TenantFileSystemStorage() connection.set_schema_to_public() tenant2 = utils.get_tenant_model()(schema_name='tenant2', owner=UserFactory()) tenant2.save() domain2 = utils.get_tenant_domain_model()(tenant=tenant2, domain='example.com') domain2.save() # this file should be saved on the public schema public_file_name = storage.save('hello_world.txt', ContentFile('Hello World')) public_os_path = storage.path(public_file_name) public_url = storage.url(public_file_name) # switch to tenant1 with utils.tenant_context(self.tenant): t1_file_name = storage.save('hello_from_1.txt', ContentFile('Hello T1')) t1_os_path = storage.path(t1_file_name) t1_url = storage.url(t1_file_name) # switch to tenant2 with utils.tenant_context(tenant2): t2_file_name = storage.save('hello_from_2.txt', ContentFile('Hello T2')) t2_os_path = storage.path(t2_file_name) t2_url = storage.url(t2_file_name) # assert the paths are correct self.assertTrue( public_os_path.endswith('apps_dir/media/public/%s' % public_file_name)) self.assertTrue( t1_os_path.endswith('apps_dir/media/test/%s' % t1_file_name)) self.assertTrue( t2_os_path.endswith('apps_dir/media/tenant2/%s' % t2_file_name)) # assert urls are correct self.assertEqual(public_url, '/media/public/%s' % public_file_name) self.assertEqual(t1_url, '/media/test/%s' % t1_file_name) self.assertEqual(t2_url, '/media/tenant2/%s' % t2_file_name) # assert contents are correct with open(public_os_path, 'r') as fobj: self.assertEqual(fobj.read(), 'Hello World') with open(t1_os_path, 'r') as fobj: self.assertEqual(fobj.read(), 'Hello T1') with open(t2_os_path, 'r') as fobj: self.assertEqual(fobj.read(), 'Hello T2')
def test_switching_search_path(self): tenant1 = get_tenant_model()(schema_name='tenant1') tenant1.save() domain1 = get_tenant_domain_model()(tenant=tenant1, domain='something.test.com') domain1.save() connection.set_schema_to_public() tenant2 = get_tenant_model()(schema_name='tenant2') tenant2.save() domain2 = get_tenant_domain_model()(tenant=tenant2, domain='example.com') domain2.save() # go to tenant1's path connection.set_tenant(tenant1) # add some data, 2 DummyModels for tenant1 DummyModel(name="Schemas are").save() DummyModel(name="awesome!").save() # switch temporarily to tenant2's path with self.assertNumQueries(6): with tenant_context(tenant2): # add some data, 3 DummyModels for tenant2 DummyModel(name="Man,").save() DummyModel(name="testing").save() DummyModel(name="is great!").save() # we should be back to tenant1's path, test what we have with self.assertNumQueries(2): self.assertEqual(2, DummyModel.objects.count()) # switch back to tenant2's path with self.assertNumQueries(2): with tenant_context(tenant2): self.assertEqual(3, DummyModel.objects.count()) self.created = [domain2, domain1, tenant2, tenant1]
def process_request(self, request): # Short circuit if tenant is already set by another middleware. # This allows for multiple tenant-resolving middleware chained together. if hasattr(request, "tenant"): return connection.set_schema_to_public() tenant = None urlconf = None TenantModel = get_tenant_model() hostname = self.hostname_from_request(request) subfolder_prefix_path = "/{}/".format(get_subfolder_prefix()) # We are in the public tenant if not request.path.startswith(subfolder_prefix_path): try: tenant = TenantModel.objects.get( schema_name=get_public_schema_name()) except TenantModel.DoesNotExist: raise self.TENANT_NOT_FOUND_EXCEPTION( "Unable to find public tenant") # Do we have a public-specific urlconf? if (hasattr(settings, "PUBLIC_SCHEMA_URLCONF") and tenant.schema_name == get_public_schema_name()): urlconf = settings.PUBLIC_SCHEMA_URLCONF # We are in a specific tenant else: path_chunks = request.path[len(subfolder_prefix_path):].split("/") tenant_subfolder = path_chunks[0] try: tenant = TenantModel.objects.get( domains__domain=tenant_subfolder) except TenantModel.DoesNotExist: raise self.TENANT_NOT_FOUND_EXCEPTION( 'No tenant for subfolder "%s"' % (tenant_subfolder or "")) tenant.domain_subfolder = tenant_subfolder urlconf = get_subfolder_urlconf(tenant) tenant.domain_url = hostname request.tenant = tenant connection.set_tenant(request.tenant) clear_url_caches( ) # Required to remove previous tenant prefix from cache, if present if urlconf: request.urlconf = urlconf set_urlconf(urlconf)
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 setup_test_tenant_and_domain(cls): cls.tenant = get_tenant_model()(schema_name=cls.get_test_schema_name()) cls.setup_tenant(cls.tenant) cls.tenant.save(verbosity=cls.get_verbosity()) # Set up domain tenant_domain = cls.get_test_tenant_domain() cls.domain = get_tenant_domain_model()(tenant=cls.tenant, domain=tenant_domain) cls.setup_domain(cls.domain) cls.domain.save() cls.use_new_tenant()
def process_request(self, request): try: connection.set_schema_to_public() hostname_without_port = remove_www_and_dev( request.get_host().split(':')[0]) tenant_model = get_tenant_model() request.main_url = MAIN_URL request.login_url = LOGIN_URL request.protocol = PROTOCOL if hostname_without_port.startswith('login.'): hostname_without_port = hostname_without_port.replace( 'login.', '') tenant = tenant_model.objects.filter( domain_name=hostname_without_port) if tenant: tenant = tenant[0] request.tenant = tenant selected_schema_name = tenant.schema_name else: res = { 'error': 'Subdomain ' + hostname_without_port + ' does not exist', 'status': 'Invalid Client' } not_found = { 'error': 'This subdomain does not exist => ' + hostname_without_port, 'error_code': 404 } if hostname_without_port != server_domain: return render(request, 'error.html', not_found) tenant = tenant_model.objects.filter(schema_name='public') if not tenant: tenant = create_public_tenant(tenant_model) if type(tenant) is not str: request.tenant = tenant selected_schema_name = tenant.schema_name else: return render(request, 'error.html', not_found) else: tenant = tenant[0] request.tenant = tenant selected_schema_name = tenant.schema_name except utils.DatabaseError: request.urlconf = PUBLIC_SCHEMA_URLCONF return connection.set_tenant(request.tenant, False) ContentType.objects.clear_cache() if selected_schema_name == 'public': request.home_url = PROTOCOL + "://" + server_domain + SERVER_PORT_STR request.urlconf = PUBLIC_SCHEMA_URLCONF else: request.home_url = PROTOCOL + "://" + selected_schema_name + '.' + server_domain + SERVER_PORT_STR
def setUpClass(cls): tenant_model = get_tenant_model() test_schema_name = cls.get_test_schema_name() if tenant_model.objects.filter(schema_name=test_schema_name).exists(): cls.tenant = tenant_model.objects.filter(schema_name=test_schema_name).first() cls.use_existing_tenant() else: cls.setup_test_tenant_and_domain() connection.set_tenant(cls.tenant)
def test_tenant_prefix(self): from django.db import connection tpp = TenantPrefixPattern() for tenant in get_tenant_model().objects.all(): domain = tenant.domains.first() tenant.domain_subfolder = domain.domain # Normally done by middleware connection.set_tenant(tenant) self.assertEqual( tpp.tenant_prefix, "clients/{}/".format(tenant.domain_subfolder) )
def connect(realm, *args, **kwargs): if hasattr(realm, 'schema_name') and hasattr(connection, 'set_schema'): try: from django_tenants.utils import get_tenant_model connection.set_tenant(get_tenant_model().objects.get( schema_name=realm.schema_name)) except ImportError: raise ModuleNotFoundError( 'django-tenants package is not installed: pip install django-quickbooks[tenant]' ) return func(realm, *args, **kwargs)
def setUpClass(cls): cls.sync_shared() cls.tenant = get_tenant_model()(schema_name='test') cls.tenant.save(verbosity=0) # todo: is there any way to get the verbosity from the test command here? # Set up domain tenant_domain = 'tenant.test.com' domain = get_tenant_domain_model()(tenant=cls.tenant, domain=tenant_domain) domain.save() connection.set_tenant(cls.tenant)
def setUpClass(cls): super().setUpClass() with utils.schema_context('public'): cls.tenant2 = django_tenant_utils.get_tenant_model()( schema_name='other2', owner=cls.tester) cls.tenant2.save() cls.domain2 = django_tenant_utils.get_tenant_domain_model()( tenant=cls.tenant2, domain='other2.example.com') cls.domain2.save()
def setUpClass(cls): cls.sync_shared() cls.tenant = get_tenant_model()(schema_name='test') cls.tenant.save(verbosity=0) # todo: is there any way to get the verbosity from the test command here? # Set up domain tenant_domain = 'tenant.test.com' domain = get_tenant_domain_model()(domain=tenant_domain, tenant=cls.tenant) domain.save() connection.set_tenant(cls.tenant)
def setUp(self): self.sync_shared() self.tenant = get_tenant_model()(schema_name='test') self.tenant.save(verbosity=0) # todo: is there any way to get the verbosity from the test command here? # Set up domain tenant_domain = 'tenant.test.com' self.domain = get_tenant_domain_model()(tenant=self.tenant, domain=tenant_domain) self.domain.save() connection.set_tenant(self.tenant)
def setUp(self): super(SharedAuthTest, self).setUp() settings.SHARED_APPS = ( 'django_tenants', 'abi_back.tenant_configuration', 'django.contrib.auth', 'django.contrib.contenttypes', ) settings.TENANT_APPS = ( 'abi_back.tenant_configuration.custom_dts_test_app', ) settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS self.sync_shared() self.public_tenant = get_tenant_model()( schema_name=get_public_schema_name()) self.public_tenant.save() self.public_domain = get_tenant_domain_model()( tenant=self.public_tenant, domain='test.com') self.public_domain.save() # Create a tenant self.tenant = get_tenant_model()(schema_name='tenant') self.tenant.save() self.domain = get_tenant_domain_model()(tenant=self.tenant, domain='tenant.test.com') self.domain.save() # Create some users with schema_context(get_public_schema_name( )): # this could actually also be executed inside a tenant self.user1 = User(username='******', email="*****@*****.**") self.user1.save() self.user2 = User(username='******', email="*****@*****.**") self.user2.save() # Create instances on the tenant that point to the users on public with tenant_context(self.tenant): self.d1 = ModelWithFkToPublicUser(user=self.user1) self.d1.save() self.d2 = ModelWithFkToPublicUser(user=self.user2) self.d2.save()
def store_tenant(self, clone_schema_from, clone_tenant_fields, **fields): connection.set_schema_to_public() try: if clone_tenant_fields: tenant = get_tenant_model().objects.get(schema_name=clone_schema_from) tenant.pk = None tenant.schema_name = fields['schema_name'] else: tenant = get_tenant_model()(**fields) tenant.auto_create_schema = False tenant.save() clone_schema = CloneSchema() clone_schema.clone_schema(clone_schema_from, tenant.schema_name, set_connection=False) 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: " + str(e)) return None
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 create_tenant(email, subscription_id, request): res = 'Unknown issue' req_data = request.POST sub_domain = req_data['sub_domain'] company = req_data['company'] password = req_data['password'] plan_id = req_data['plan_id'] public_tenant = request.tenant try: tenant_model = get_tenant_model() with transaction.atomic(): if not tenant_model.objects.filter(schema_name=sub_domain): schema_owner = User.objects.create(username='******' + sub_domain + '.com') schema_owner.save() domain_name = sub_domain + '.' + server_domain company = tenant_model(schema_name=sub_domain, name=company, owner_id=schema_owner.id, domain_name=domain_name) company.subscription_id = subscription_id company.plan_id = plan_id company.save() create_public_user(company, email, password) request.tenant = company connection.set_tenant(request.tenant, False) ContentType.objects.clear_cache() try: tenant_user = AuthUser(username=email, is_superuser=True, is_staff=True, is_active=True) tenant_user.on_schema_creating = True tenant_user.email = email tenant_user.save() tenant_user.set_password(password) tenant_user.save() print('\n\n\n Success while creating user\n\n\n') except: print('\n\n\n Error while creating user\n\n\n') res = 'done' else: res = 'Client with name "' + sub_domain + '" already exists' except: res = ws_methods.produce_exception() request.tenant = public_tenant connection.set_tenant(request.tenant, False) ContentType.objects.clear_cache() return res
def test_prefixed_reverse(self): from django.db import connection for tenant in get_tenant_model().objects.all(): domain = tenant.domains.first() tenant.domain_subfolder = domain.domain # Normally done by middleware connection.set_tenant(tenant) for name, path in self.paths.items(): self.assertEqual( self.reverser(name, tenant), "/clients/{}{}".format(domain.domain, path), )
def provision_tenant(tenant_name, tenant_slug, user_email, is_staff=False): """ Create a tenant with default roles and permissions Returns: The Fully Qualified Domain Name(FQDN) for the tenant. """ tenant = None UserModel = get_user_model() TenantModel = get_tenant_model() user = UserModel.objects.get(email=user_email) if not user.is_active: raise InactiveError("Inactive user passed to provision tenant") tenant_domain = '{}.{}'.format(tenant_slug, settings.TENANT_USERS_DOMAIN) if get_tenant_domain_model().objects.filter(domain=tenant_domain).first(): raise ExistsError("Tenant URL already exists.") time_string = str(int(time.time())) # Must be valid postgres schema characters see: # https://www.postgresql.org/docs/9.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS # We generate unique schema names each time so we can keep tenants around without # taking up url/schema namespace. schema_name = '{}_{}'.format(tenant_slug, time_string) domain = None # noinspection PyBroadException try: # Wrap it in public schema context so schema consistency is maintained # if any error occurs with schema_context(get_public_schema_name()): tenant = TenantModel.objects.create(name=tenant_name, slug=tenant_slug, schema_name=schema_name, owner=user) # Add one or more domains for the tenant domain = get_tenant_domain_model().objects.create( domain=tenant_domain, tenant=tenant, is_primary=True) # Add user as a superuser inside the tenant tenant.add_user(user, is_superuser=True, is_staff=is_staff) except: # noqa if domain is not None: domain.delete() if tenant is not None: # Flag is set to auto-drop the schema for the tenant tenant.delete(True) raise return tenant
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 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: 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) 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) executor.run_migrations(tenants=tenants)
def ready(self): from django.db import connections # Test for configuration recommendations. These are best practices, # they avoid hard to find bugs and unexpected behaviour. if not hasattr(settings, 'TENANT_APPS'): raise ImproperlyConfigured('TENANT_APPS setting not set') if not settings.TENANT_APPS: raise ImproperlyConfigured("TENANT_APPS is empty. " "Maybe you don't need this app?") if not hasattr(settings, 'TENANT_MODEL'): raise ImproperlyConfigured('TENANT_MODEL setting not set') if not hasattr(settings, 'TENANT_SESSION_KEY'): setattr(settings, 'TENANT_SESSION_KEY', 'tenant_schema') if not hasattr(settings, 'TENANT_SELECTION_METHOD'): setattr(settings, 'TENANT_SELECTION_METHOD', 'domain') if not hasattr(settings, 'TENANT_DATABASE'): setattr(settings, 'TENANT_DATABASE', 'default') if 'django_tenants.routers.TenantSyncRouter' not in settings.DATABASE_ROUTERS: raise ImproperlyConfigured( "DATABASE_ROUTERS setting must contain " "'django_tenants.routers.TenantSyncRouter'.") if hasattr(settings, 'PG_EXTRA_SEARCH_PATHS'): if get_public_schema_name() in settings.PG_EXTRA_SEARCH_PATHS: raise ImproperlyConfigured( "%s can not be included on PG_EXTRA_SEARCH_PATHS." % get_public_schema_name()) # make sure no tenant schema is in settings.PG_EXTRA_SEARCH_PATHS # first check that the model table is created model = get_tenant_model() c = connections[DEFAULT_DB_ALIAS].cursor() c.execute( 'SELECT 1 FROM information_schema.tables WHERE table_name = %s;', [model._meta.db_table]) if c.fetchone(): invalid_schemas = set( settings.PG_EXTRA_SEARCH_PATHS).intersection( model.objects.all().values_list('schema_name', flat=True)) if invalid_schemas: raise ImproperlyConfigured( "Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS." % list(invalid_schemas))
def setup_test_tenant_and_domain(cls): cls.tenant, created_tenant = get_tenant_model().objects.get_or_create( schema_name=cls.get_test_schema_name()) cls.setup_tenant(cls.tenant) # cls.tenant.save(verbosity=cls.get_verbosity()) # Set up domain tenant_domain = cls.get_test_tenant_domain() cls.domain, created_domain = get_tenant_domain_model( ).objects.get_or_create(tenant=cls.tenant, domain=tenant_domain) cls.setup_domain(cls.domain) # cls.domain.save() cls.use_new_tenant()
def get_tenant(self, tenant_model, user): try: return super(DefaultTenantMiddleware, self).get_tenant(tenant_model, user) except self.TENANT_NOT_FOUND_EXCEPTION or self.NO_TENANT_EXCEPTION: schema_name = self.DEFAULT_SCHEMA_NAME if not schema_name: schema_name = get_public_schema_name() tenant_model = get_tenant_model() return tenant_model.objects.get(schema_name=schema_name) except Exception as e: print(e) print(e.message)
def setUp(self): self.sync_shared() self.tenant = get_tenant_model()(schema_name=self.get_test_schema_name()) self.setup_tenant(self.tenant) self.tenant.save(verbosity=0) # todo: is there any way to get the verbosity from the test command here? # Set up domain tenant_domain = self.get_test_tenant_domain() self.domain = get_tenant_domain_model()(tenant=self.tenant, domain=tenant_domain) self.setup_domain(self.domain) self.domain.save() connection.set_tenant(self.tenant)
def setUpClass(cls): super(RoutesTestCase, cls).setUpClass() settings.SHARED_APPS = ('django_tenants', 'customers') settings.TENANT_APPS = ('dts_test_app', 'django.contrib.contenttypes', 'django.contrib.auth', ) settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS cls.sync_shared() cls.public_tenant = get_tenant_model()(schema_name=get_public_schema_name()) cls.public_tenant.save() cls.public_domain = get_tenant_domain_model()(domain='test.com', tenant=cls.public_tenant) cls.public_domain.save()
def setUpClass(cls): super(SharedAuthTest, cls).setUpClass() settings.SHARED_APPS = ( 'django_tenants', 'customers', 'django.contrib.auth', 'django.contrib.contenttypes', ) settings.TENANT_APPS = ('dts_test_app', ) settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS cls.sync_shared() tenant = get_tenant_model()(schema_name=get_public_schema_name()) tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='test.com') domain.save() # Create a tenant cls.tenant = get_tenant_model()(schema_name='tenant') cls.tenant.save() cls.domain = get_tenant_domain_model()(tenant=cls.tenant, domain='tenant.test.com') cls.domain.save() # Create some users with schema_context(get_public_schema_name( )): # this could actually also be executed inside a tenant cls.user1 = User(username='******', email="*****@*****.**") cls.user1.save() cls.user2 = User(username='******', email="*****@*****.**") cls.user2.save() # Create instances on the tenant that point to the users on public with tenant_context(cls.tenant): cls.d1 = ModelWithFkToPublicUser(user=cls.user1) cls.d1.save() cls.d2 = ModelWithFkToPublicUser(user=cls.user2) cls.d2.save()
def setUpClass(cls): super().setUpClass() settings.SHARED_APPS = ('django_tenants', 'customers') settings.TENANT_APPS = ('dts_test_app', 'django.contrib.contenttypes', 'django.contrib.auth', ) settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS cls.sync_shared() cls.public_tenant = get_tenant_model()(schema_name=get_public_schema_name()) cls.public_tenant.save() cls.public_domain = get_tenant_domain_model()(tenant=cls.public_tenant, domain='test.com') cls.public_domain.save()
def setUpClass(cls): super(TenantDataAndSettingsTest, cls).setUpClass() settings.SHARED_APPS = ('django_tenants', 'customers') settings.TENANT_APPS = ('dts_test_app', 'django.contrib.contenttypes', 'django.contrib.auth', ) settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS cls.sync_shared() tenant = get_tenant_model()(schema_name=get_public_schema_name()) tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='test.com') domain.save()
def test_switching_tenant_without_previous_tenant(self): tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() connection.tenant = None with tenant_context(tenant): DummyModel(name="No exception please").save() connection.tenant = None with schema_context(tenant.schema_name): DummyModel(name="Survived it!").save()
def setUp(self): super().setUp() self.factory = RequestFactory() self.tm = TenantMainMiddleware(lambda r: r) print(settings.INSTALLED_APPS) self.public_tenant = get_tenant_model()(schema_name=get_public_schema_name(), type='public') self.public_tenant.save() self.public_domain = get_tenant_domain_model()(domain='test.com', tenant=self.public_tenant) self.public_domain.save() self.tenant_domain = 'tenant.test.com' self.tenant = get_tenant_model()(schema_name='test') self.tenant.save() self.domain = get_tenant_domain_model()(tenant=self.tenant, domain=self.tenant_domain) self.domain.save() self.tenant_domain2 = 'tenant2.test.com' self.tenant2 = get_tenant_model()(schema_name='test2', type='type2') self.tenant2.save() self.domain2 = get_tenant_domain_model()(tenant=self.tenant2, domain=self.tenant_domain2) self.domain2.save()
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 setUpClass(cls): cls.sync_shared() cls.add_allowed_test_domain() cls.tenant = get_tenant_model()(schema_name=cls.get_test_schema_name()) cls.setup_tenant(cls.tenant) cls.tenant.save(verbosity=cls.get_verbosity()) # Set up domain tenant_domain = cls.get_test_tenant_domain() cls.domain = get_tenant_domain_model()(tenant=cls.tenant, domain=tenant_domain) cls.setup_domain(cls.domain) cls.domain.save() connection.set_tenant(cls.tenant)
def __call__(self, request): # Short circuit if tenant is already set by another middleware. # This allows for multiple tenant-resolving middleware chained together. if hasattr(request, "tenant"): return connection.set_schema_to_public() urlconf = None tenant_model = get_tenant_model() domain_model = get_tenant_domain_model() hostname = self.hostname_from_request(request) subfolder_prefix_path = "/{}/".format(get_subfolder_prefix()) # We are in the public tenant if not request.path.startswith(subfolder_prefix_path): try: tenant = tenant_model.objects.get( schema_name=get_public_schema_name()) except tenant_model.DoesNotExist: raise self.TENANT_NOT_FOUND_EXCEPTION( "Unable to find public tenant") request.urlconf = settings.PUBLIC_SCHEMA_URLCONF # We are in a specific tenant else: path_chunks = request.path[len(subfolder_prefix_path):].split("/") tenant_subfolder = path_chunks[0] try: tenant = self.get_tenant(domain_model=domain_model, hostname=tenant_subfolder) except domain_model.DoesNotExist: return self.no_tenant_found(request, hostname) tenant.domain_subfolder = tenant_subfolder urlconf = get_subfolder_urlconf(tenant) tenant.domain_url = hostname request.tenant = tenant connection.set_tenant(request.tenant) clear_url_caches( ) # Required to remove previous tenant prefix from cache, if present if urlconf: request.urlconf = urlconf set_urlconf(urlconf) return self.get_response(request)
def ready(self): from django.db import connections # Test for configuration recommendations. These are best practices, # they avoid hard to find bugs and unexpected behaviour. if not hasattr(settings, 'TENANT_APPS'): raise ImproperlyConfigured('TENANT_APPS setting not set') if not settings.TENANT_APPS: raise ImproperlyConfigured("TENANT_APPS is empty. " "Maybe you don't need this app?") if not hasattr(settings, 'TENANT_MODEL'): raise ImproperlyConfigured('TENANT_MODEL setting not set') if not hasattr(settings, 'TENANT_SESSION_KEY'): setattr(settings, 'TENANT_SESSION_KEY', 'tenant_schema') if not hasattr(settings, 'TENANT_SELECTION_METHOD'): setattr(settings, 'TENANT_SELECTION_METHOD', 'domain') if not hasattr(settings, 'TENANT_DATABASE'): setattr(settings, 'TENANT_DATABASE', 'default') if 'django_tenants.routers.TenantSyncRouter' not in settings.DATABASE_ROUTERS: raise ImproperlyConfigured("DATABASE_ROUTERS setting must contain " "'django_tenants.routers.TenantSyncRouter'.") if hasattr(settings, 'PG_EXTRA_SEARCH_PATHS'): if get_public_schema_name() in settings.PG_EXTRA_SEARCH_PATHS: raise ImproperlyConfigured( "%s can not be included on PG_EXTRA_SEARCH_PATHS." % get_public_schema_name()) # make sure no tenant schema is in settings.PG_EXTRA_SEARCH_PATHS # first check that the model table is created model = get_tenant_model() c = connections[DEFAULT_DB_ALIAS].cursor() c.execute( 'SELECT 1 FROM information_schema.tables WHERE table_name = %s;', [model._meta.db_table] ) if c.fetchone(): invalid_schemas = set(settings.PG_EXTRA_SEARCH_PATHS).intersection( model.objects.all().values_list('schema_name', flat=True)) if invalid_schemas: raise ImproperlyConfigured( "Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS." % list(invalid_schemas))
def test_content_types_is_not_mandatory(self): """ Tests that even if content types is in SHARED_APPS, it's not required in TENANT_APPS. """ tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='something.test.com') domain.save() shared_tables = self.get_tables_list_in_schema(get_public_schema_name()) tenant_tables = self.get_tables_list_in_schema(tenant.schema_name) self.assertEqual(2+1 + self.MIGRATION_TABLE_SIZE, len(shared_tables)) self.assertIn('django_session', tenant_tables) self.assertEqual(1+self.MIGRATION_TABLE_SIZE, len(tenant_tables)) self.assertIn('django_session', tenant_tables)
def handle(self, *args, **options): if not options.get('schema_name'): TenantModel = get_tenant_model() all_tenants = TenantModel.objects.all().order_by("schema_name") schema_name_start = options.get('schema_name_start') execute = False for tenant in all_tenants: if schema_name_start and schema_name_start != tenant.schema_name and execute is False: # print "skipe: {}".format(tenant.schema_name) continue else: execute = True if execute: self.on_execute_command(tenant, args, options) else: super(SingleOrAllTenantWrappedCommand, self).handle(*args, **options)
def store_tenant(self, clone_schema_from, **fields): connections[DEFAULT_DB_ALIAS].set_schema_to_public() cursor = connections[settings.TENANT_DATABASE].cursor() 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: return None
def test_tenant_apps_and_shared_apps_can_have_the_same_apps(self): """ Tests that both SHARED_APPS and TENANT_APPS can have apps in common. In this case they should get synced to both tenant and public schemas. """ tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='arbitrary.test.com') domain.save() shared_tables = self.get_tables_list_in_schema(get_public_schema_name()) tenant_tables = self.get_tables_list_in_schema(tenant.schema_name) self.assertEqual(2+6+1+1+self.MIGRATION_TABLE_SIZE, len(shared_tables)) self.assertIn('django_session', shared_tables) self.assertEqual(1+self.MIGRATION_TABLE_SIZE, len(tenant_tables)) self.assertIn('django_session', tenant_tables)
def test_tenant_apps_does_not_sync_shared_apps(self): """ Tests that if an app is in TENANT_APPS, it does not get synced to the public schema. """ tenant = get_tenant_model()(schema_name='test') tenant.save() domain = get_tenant_domain_model()(tenant=tenant, domain='arbitrary.test.com') domain.save() tenant_tables = self.get_tables_list_in_schema(tenant.schema_name) self.assertEqual(1+self.MIGRATION_TABLE_SIZE, len(tenant_tables)) self.assertIn('django_session', tenant_tables) connection.set_schema_to_public() domain.delete() tenant.delete(force_drop=True)
def setUpClass(cls): # This replaces TestCase.setUpClass so that we can do some setup in # different schemas. # It also drops the check whether the database supports transactions. cls.sync_shared() EmailTemplate.objects.get_or_create(name='audit/staff_member/invite') EmailTemplate.objects.get_or_create(name='audit/engagement/submit_to_auditor') TenantModel = get_tenant_model() try: cls.tenant = TenantModel.objects.get(schema_name=SCHEMA_NAME) except TenantModel.DoesNotExist: cls.tenant = TenantModel(schema_name=SCHEMA_NAME) cls.tenant.save(verbosity=0) cls.tenant.business_area_code = 'ZZZ' # Make sure country has a short code, it affects some results cls.tenant.country_short_code = 'TST' cls.tenant.save(verbosity=0) cls.domain = get_tenant_domain_model().objects.get_or_create(domain=TENANT_DOMAIN, tenant=cls.tenant) try: cls.tenant.counters except ObjectDoesNotExist: WorkspaceCounter.objects.create(workspace=cls.tenant) cls.cls_atomics = cls._enter_atomics() # Load fixtures for shared schema cls._load_fixtures() connection.set_tenant(cls.tenant) # Load fixtures for tenant schema cls._load_fixtures() try: cls.setUpTestData() except Exception: cls._rollback_atomics(cls.cls_atomics) raise