def save(self, verbosity=1, *args, **kwargs): is_new = self.pk is None if is_new and connection.schema_name != get_public_schema_name(): raise Exception("Can't create tenant outside the public schema. " "Current schema is %s." % connection.schema_name) elif not is_new and connection.schema_name not in ( self.schema_name, get_public_schema_name()): raise Exception("Can't update tenant outside it's own schema or " "the public schema. Current schema is %s." % connection.schema_name) super(TenantMixin, self).save(*args, **kwargs) if is_new and self.auto_create_schema: try: self.create_schema(check_if_exists=True, verbosity=verbosity) except: # We failed creating the tenant, delete what we created and # re-raise the exception connection.set_schema_to_public() self.delete(force_drop=True) raise else: post_schema_sync.send(sender=TenantMixin, tenant=self)
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: request.tenant = TenantModel.objects.get(domain_url=hostname) connection.set_tenant(request.tenant) except TenantModel.DoesNotExist: raise self.TENANT_NOT_FOUND_EXCEPTION( 'No tenant for hostname "%s"' % hostname) # 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 handle(self, *args, **options): self.sync_tenant = options.get('tenant') self.sync_public = options.get('shared') self.schema_name = options.get('schema_name') self.installed_apps = settings.INSTALLED_APPS self.args = args self.options = options if self.schema_name: if self.sync_public: raise CommandError( "schema should only be used with the --tenant switch.") elif self.schema_name == get_public_schema_name(): self.sync_public = True else: self.sync_tenant = True elif not self.sync_public and not self.sync_tenant: # no options set, sync both self.sync_tenant = True self.sync_public = True if hasattr(settings, 'TENANT_APPS'): self.tenant_apps = settings.TENANT_APPS if hasattr(settings, 'SHARED_APPS'): self.shared_apps = settings.SHARED_APPS
def handle(self, *args, **options): super(Command, 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 process_request(self, request): try: super(TenantMiddleware, self).process_request(request) except self.TENANT_NOT_FOUND_EXCEPTION: connection.set_schema_to_public() schema_name = request.META.get('HTTP_DOMAIN', request.COOKIES.get('domain', None)) schema_name = schema_name or get_public_schema_name() TenantModel = get_tenant_model() try: request.tenant = TenantModel.objects.get( schema_name=schema_name) connection.set_tenant(request.tenant) except TenantModel.DoesNotExist: raise self.TENANT_NOT_FOUND_EXCEPTION( 'No tenant for domain"%s"' % schema_name) ContentType.objects.clear_cache() if hasattr( settings, 'PUBLIC_SCHEMA_URLCONF' ) and request.tenant.schema_name == get_public_schema_name(): request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
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): self.delete_schema() super(TenantMixin, self).delete(*args, **kwargs)
def handle(self, *args, **options): """ Iterates a command over all registered schemata. """ if options['schema_name']: # only run on a particular schema connection.set_schema_to_public() self.execute_command( get_tenant_model().objects.get( schema_name=options['schema_name']), self.COMMAND_NAME, *args, **options) else: for tenant in get_tenant_model().objects.all(): if not (options['skip_public'] and tenant.schema_name == get_public_schema_name()): self.execute_command(tenant, self.COMMAND_NAME, *args, **options)
def allow_migrate(self, db, app_label, model_name=None, **hints): # the imports below need to be done here else django <1.5 goes crazy # https://code.djangoproject.com/ticket/20704 from django.db import connection from tenants.utils import get_public_schema_name, app_labels if isinstance(app_label, ModelBase): # In django <1.7 the `app_label` parameter is actually `model` app_label = app_label._meta.app_label if connection.schema_name == get_public_schema_name(): if app_label not in app_labels(settings.SHARED_APPS): return False else: if app_label not in app_labels(settings.TENANT_APPS): return False return None
def set_schema_to_public(self): """ Instructs to stay in the common 'public' schema. """ self.tenant = FakeTenant(schema_name=get_public_schema_name()) self.schema_name = get_public_schema_name()