def get_tenant_from_options_or_interactive(self, **options): TenantModel = get_tenant_model() all_tenants = TenantModel.objects.all() if not all_tenants: raise CommandError("""There are no tenants in the system. To learn how create a tenant, see: https://django-tenant-schemas.readthedocs.io/en/latest/use.html#creating-a-tenant""" ) if options.get('schema_name'): tenant_schema = options['schema_name'] else: while True: tenant_schema = input( "Enter Tenant Schema ('?' to list schemas): ") if tenant_schema == '?': print('\n'.join([ "%s - %s" % ( t.schema_name, t.domain_url, ) for t in all_tenants ])) else: break if tenant_schema not in [t.schema_name for t in all_tenants]: raise CommandError("Invalid tenant schema, '%s'" % (tenant_schema, )) return TenantModel.objects.get(schema_name=tenant_schema)
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 handle(self, *args, **options): columns = ('schema_name', 'domain_url') TenantModel = get_tenant_model() all_tenants = TenantModel.objects.values_list(*columns) out = csv.writer(sys.stdout, dialect=csv.excel_tab) for tenant in all_tenants: out.writerow(tenant)
def test_public_schema_on_extra_search_paths(self): TenantModel = get_tenant_model() TenantModel.objects.create( schema_name='demo1', domain_url='demo1.example.com') TenantModel.objects.create( schema_name='demo2', domain_url='demo2.example.com') self.assertBestPractice([ Critical("public can not be included on PG_EXTRA_SEARCH_PATHS."), Critical("Do not include tenant schemas (demo1, demo2) on PG_EXTRA_SEARCH_PATHS."), ])
def setUpClass(cls): cls.sync_shared() cls.add_allowed_test_domain() tenant_domain = 'tenant.test.com' cls.tenant = get_tenant_model()(domain_url=tenant_domain, schema_name='test') cls.tenant.save( verbosity=0 ) # todo: is there any way to get the verbosity from the test command here? connection.set_tenant(cls.tenant)
def setUpClass(cls): cls.sync_shared() cls.add_allowed_test_domain() tenant_domain = 'tenant.test.com' TenantModel = get_tenant_model() try: cls.tenant = TenantModel.objects.get(domain_url=tenant_domain, schema_name='test') except: cls.tenant = TenantModel(domain_url=tenant_domain, schema_name='test') cls.tenant.save(verbosity=0) connection.set_tenant(cls.tenant)
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) from wagtail.wagtailcustomers.models import Customer as customer if not customer.objects.filter( schema_name=get_public_schema_name()).exists(): customer(domain_url='tuiuiu.io', schema_name=get_public_schema_name(), name='tuiuiu.io', description='tuiuiu.io').save() from django.contrib.auth.models import User as user if not user.objects.filter(username='******').exists(): user.objects.create_superuser('admin', '*****@*****.**', 'admin')
def process_request(self, request): # Connection needs first to be at the public schema, as this is where # the tenant metadata is stored. connection.set_schema_to_public() hostname = self.hostname_from_request(request) TenantModel = get_tenant_model() try: # get_tenant must be implemented by extending this class. tenant = self.get_tenant(TenantModel, hostname, request) assert isinstance(tenant, TenantModel) except TenantModel.DoesNotExist: raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for {!r}'.format( request.get_host())) except AssertionError: raise self.TENANT_NOT_FOUND_EXCEPTION('Invalid tenant {!r}'.format( request.tenant)) request.tenant = tenant connection.set_tenant(request.tenant) # Content type can no longer be cached as public and tenant schemas # have different models. If someone wants to change this, the cache # needs to be separated between public and shared schemas. If this # cache isn't cleared, this can cause permission problems. For example, # on public, a particular model has id 14, but on the tenants it has # the id 15. if 14 is cached instead of 15, the permissions for the # wrong model will be fetched. ContentType.objects.clear_cache() # Do we have a public-specific urlconf? if hasattr( settings, 'PUBLIC_SCHEMA_URLCONF' ) and request.tenant.schema_name == get_public_schema_name(): request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
def best_practice(app_configs, **kwargs): """ Test for configuration recommendations. These are best practices, they avoid hard to find bugs and unexpected behaviour. """ if app_configs is None: app_configs = apps.get_app_configs() # Take the app_configs and turn them into *old style* application names. # This is what we expect in the SHARED_APPS and TENANT_APPS settings. INSTALLED_APPS = [ config.name for config in app_configs ] if not hasattr(settings, 'TENANT_APPS'): return [Critical('TENANT_APPS setting not set')] if not hasattr(settings, 'TENANT_MODEL'): return [Critical('TENANT_MODEL setting not set')] if not hasattr(settings, 'SHARED_APPS'): return [Critical('SHARED_APPS setting not set')] if 'wagtail.wagtailtenant.routers.TenantSyncRouter' not in settings.DATABASE_ROUTERS: return [ Critical("DATABASE_ROUTERS setting must contain " "'wagtail.wagtailtenant.routers.TenantSyncRouter'.") ] errors = [] django_index = next(i for i, s in enumerate(INSTALLED_APPS) if s.startswith('django.')) if INSTALLED_APPS.index('wagtail.wagtailtenant') > django_index: errors.append( Warning("You should put 'wagtail.wagtailtenant' before any django " "core applications in INSTALLED_APPS.", obj="django.conf.settings", hint="This is necessary to overwrite built-in django " "management commands with their schema-aware " "implementations.", id="wagtailtenant.W001")) if not settings.TENANT_APPS: errors.append( Error("TENANT_APPS is empty.", hint="Maybe you don't need this app?", id="wagtailtenant.E001")) if hasattr(settings, 'PG_EXTRA_SEARCH_PATHS'): if get_public_schema_name() in settings.PG_EXTRA_SEARCH_PATHS: errors.append(Critical( "%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: errors.append(Critical( "Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS." % ", ".join(sorted(invalid_schemas)))) if not settings.SHARED_APPS: errors.append( Warning("SHARED_APPS is empty.", id="wagtailtenant.W002")) if not set(settings.TENANT_APPS).issubset(INSTALLED_APPS): delta = set(settings.TENANT_APPS).difference(INSTALLED_APPS) errors.append( Error("You have TENANT_APPS that are not in INSTALLED_APPS", hint=[a for a in settings.TENANT_APPS if a in delta], id="wagtailtenant.E002")) if not set(settings.SHARED_APPS).issubset(INSTALLED_APPS): delta = set(settings.SHARED_APPS).difference(INSTALLED_APPS) errors.append( Error("You have SHARED_APPS that are not in INSTALLED_APPS", hint=[a for a in settings.SHARED_APPS if a in delta], id="wagtailtenant.E003")) if not isinstance(default_storage, TenantStorageMixin): errors.append(Warning( "Your default storage engine is not tenant aware.", hint="Set settings.DEFAULT_FILE_STORAGE to " "'wagtail.wagtailtenant.storage.TenantFileSystemStorage'", id="wagtailtenant.W003" )) return errors
def test_tenant_survives_after_method2(self): # The same tenant still exists even after the previous method call self.assertEquals(1, get_tenant_model().objects.all().count())
def test_tenant_survives_after_method1(self): # There is one tenant in the database, the one created by TenantTestCase self.assertEquals(1, get_tenant_model().objects.all().count())