Beispiel #1
0
    def setUpClass(cls):
        super(SharedAuthTest, cls).setUpClass()
        settings.SHARED_APPS = (
            'tenant_schemas',
            '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(domain_url='test.com',
               schema_name=get_public_schema_name()).save(
                   verbosity=cls.get_verbosity())

        # Create a tenant
        cls.tenant = Tenant(domain_url='tenant.test.com', schema_name='tenant')
        cls.tenant.save(verbosity=cls.get_verbosity())

        # 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 _get_tenant(self, request, allow_public=None):
        tenant_id = request.session.get('active_tenant')
        tenant = None
        TenantModel = get_tenant_model()
        if tenant_id is not None:
            try:
                tenant = TenantModel.objects.get(pk=tenant_id)
            except TenantModel.DoesNotExist:
                del request.session['active_tenant']
                tenant = None
        if tenant is None:
            tenant = connection.tenant

        if tenant.schema_name == get_public_schema_name():
            allow_public = allow_public if allow_public is not None else (
                self.model._meta.app_label in app_labels(settings.TENANT_APPS))
            if not allow_public:
                return None
            tenant = TenantModel.objects.exclude(
                schema_name=get_public_schema_name()).first()
            if tenant:
                self.message_user(request,
                                  "This model does not exist in public schema."
                                  " Falling into any first tenant.",
                                  level=messages.WARNING)

        return tenant
Beispiel #3
0
 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()
     self.set_settings_schema(self.schema_name)
Beispiel #4
0
    def save(self, verbosity=1, *args, **kwargs):

        is_new = self.pk is None
        if not self.db_string:
            self.db_string = get_db_string(self.schema_name)
        db = self.db_string

        from django.db import connection
        if db:
            connection = connections[db]

        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,
                                   db=db)
            except Exception as e:
                # We failed creating the tenant, delete what we created and
                # re-raise the exception
                self.delete(force_drop=True, using=db)
                raise
            else:
                post_schema_sync.send(sender=TenantMixin, tenant=self)
    def setUpClass(cls):
        super(SharedAuthTest, cls).setUpClass()
        settings.SHARED_APPS = ('tenant_schemas',
                                '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(domain_url='test.com', schema_name=get_public_schema_name()).save()

        # Create a tenant
        cls.tenant = Tenant(domain_url='tenant.test.com', schema_name='tenant')
        cls.tenant.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 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()
     self.set_settings_schema(self.schema_name)
    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 sync_shared(cls, db=None):
     if not db:
         call_command('migrate_schemas',
                      schema_name=get_public_schema_name(),
                      interactive=False,
                      verbosity=cls.get_verbosity(),
                      run_syncdb=True)
     call_command('migrate_schemas',
                      schema_name=get_public_schema_name(),
                      interactive=False,
                      verbosity=cls.get_verbosity(),
                      run_syncdb=True,
                      db=db)
 def sync_shared(cls):
     if django.VERSION >= (1, 7, 0):
         call_command('migrate_schemas',
                      schema_name=get_public_schema_name(),
                      interactive=False,
                      verbosity=cls.get_verbosity())
     else:
         call_command('sync_schemas',
                      schema_name=get_public_schema_name(),
                      tenant=False,
                      public=True,
                      interactive=False,
                      migrate_all=True,
                      verbosity=cls.get_verbosity())
Beispiel #10
0
 def get_tenant(self, model, hostname, request):
     try:
         schema = model.objects.get(schema_name=get_public_schema_name())
     except ObjectDoesNotExist as ex:
         schema = model.objects.create(
             domain_url=hostname,
             schema_name=get_public_schema_name(),
             tenant_name=TenantConstants.DEFAULT_TENANT_NAME)
         schema.save()
     authentication = request.META.get('HTTP_AUTHORIZATION')
     if authentication is not None and "Bearer " in authentication:
         tenant = JWTUtils.decode_access_token(authentication, False)
         tenant_name = tenant.get("tenant")
         schema = model.objects.get(tenant_name=tenant_name)
     return schema
    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:
            self.create_schema(check_if_exists=True, verbosity=verbosity)
            post_schema_sync.send(sender=TenantMixin, tenant=self)
    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:
            self.create_schema(check_if_exists=True, verbosity=verbosity)
            post_schema_sync.send(sender=TenantMixin, tenant=self)
 def get_tenant(self, model, hostname, request):
     try:
         public_schema = model.objects.get(schema_name=get_public_schema_name())
     except ObjectDoesNotExist:
         public_schema = model.objects.create(
             domain_url=hostname,
             schema_name=get_public_schema_name(),
             tenant_name=get_public_schema_name().capitalize(),
             paid_until=date.today() + relativedelta(months=+1),
             on_trial=True)
     public_schema.save()
     x_request_id = request.META.get('HTTP_X_REQUEST_ID', public_schema.tenant_uuid)
     tenant_model = model.objects.get(tenant_uuid=x_request_id)
     print(tenant_model, public_schema)
     return tenant_model if not None else public_schema
    def setUpClass(cls):
        settings.TENANT_MODEL = 'tenant_schemas.Tenant'
        settings.SHARED_APPS = ('tenant_schemas', )
        settings.TENANT_APPS = ('dts_test_app',
                                'django.contrib.contenttypes',
                                'django.contrib.auth', )
        settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS

        # Django calls syncdb by default for the test database, but we want
        # a blank public schema for this set of tests.
        connection.set_schema_to_public()
        cursor = connection.cursor()
        cursor.execute('DROP SCHEMA %s CASCADE; CREATE SCHEMA %s;'
                       % (get_public_schema_name(), get_public_schema_name(), ))
        super(BaseTestCase, cls).setUpClass()
Beispiel #15
0
    def setUpClass(cls):
        settings.TENANT_MODEL = 'tenant_schemas.Tenant'
        settings.SHARED_APPS = ('tenant_schemas', )
        settings.TENANT_APPS = ('dts_test_app',
                                'django.contrib.contenttypes',
                                'django.contrib.auth', )
        settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS

        # Django calls syncdb by default for the test database, but we want
        # a blank public schema for this set of tests.
        connection.set_schema_to_public()
        cursor = connection.cursor()
        cursor.execute('DROP SCHEMA IF EXISTS %s CASCADE; CREATE SCHEMA %s;'
                       % (get_public_schema_name(), get_public_schema_name()))
        super(BaseTestCase, cls).setUpClass()
 def sync_shared(cls):
     if django.VERSION >= (1, 7, 0):
         call_command('migrate_schemas',
                      schema_name=get_public_schema_name(),
                      interactive=False,
                      verbosity=cls.get_verbosity(),
                      run_syncdb=True)
     else:
         call_command('sync_schemas',
                      schema_name=get_public_schema_name(),
                      tenant=False,
                      public=True,
                      interactive=False,
                      migrate_all=True,
                      verbosity=cls.get_verbosity())
Beispiel #17
0
    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 handle_noargs(self, **options):
        # todo: awful lot of duplication from sync_schemas
        sync_tenant = options.get('tenant')
        sync_public = options.get('shared')
        schema_name = options.get('schema_name')
        self.installed_apps = settings.INSTALLED_APPS
        self.options = options

        if sync_public and schema_name:
            raise CommandError("schema should only be used with the --tenant switch.")
        if not hasattr(settings, 'TENANT_APPS') and sync_tenant:
            raise CommandError("No setting found for TENANT_APPS")
        if not hasattr(settings, 'SHARED_APPS') and sync_public:
            raise CommandError("No setting found for SHARED_APPS")

        if not sync_public and not sync_tenant and not schema_name:
            # no options set, sync both
            sync_tenant = True
            sync_public = True

        if schema_name:
            if schema_name == get_public_schema_name():
                sync_public = True
            else:
                sync_tenant = True

        if hasattr(settings, 'TENANT_APPS'):
            self.tenant_apps = settings.TENANT_APPS
        if hasattr(settings, 'SHARED_APPS'):
            self.shared_apps = settings.SHARED_APPS

        if sync_public:
            self.migrate_public_apps()
        if sync_tenant:
            self.migrate_tenant_apps(schema_name)
Beispiel #19
0
    def migrate_tenant_apps(self, schema_name=None):
        self._save_south_settings()

        apps = self.tenant_apps or self.installed_apps
        self._set_managed_apps(included_apps=apps,
                               excluded_apps=self.shared_apps)

        if schema_name:
            self._notice("=== Running migrate for schema: %s" % schema_name)
            connection.set_schema_to_public()
            tenant = get_tenant_model().objects.get(schema_name=schema_name)
            self._migrate_schema(tenant)
        else:
            all_tenants = get_tenant_model().objects.exclude(
                schema_name=get_public_schema_name())
            if not all_tenants:
                self._notice("No tenants found")

            for tenant in all_tenants:
                Migrations._dependencies_done = False  # very important, the dependencies need to be purged from cache
                self._notice("=== Running migrate for schema %s" %
                             tenant.schema_name)
                self._migrate_schema(tenant)

        self._restore_south_settings()
    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 tenant_schemas.utils import get_public_schema_name, app_labels
        from tenant_schemas.postgresql_backend.base import DatabaseWrapper as TenantDbWrapper

        db_engine = settings.DATABASES[db]["ENGINE"]
        if not (
            db_engine == "tenant_schemas.postgresql_backend"
            or issubclass(getattr(load_backend(db_engine), "DatabaseWrapper"), TenantDbWrapper)
        ):
            return None

        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 handle(self, *args, **options):
        self.sync_tenant = options.get('tenant')
        self.sync_public = options.get('shared')
        self.schema_name = options.get('schema_name')
        self.executor = options.get('executor')
        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
Beispiel #22
0
    def delete(self, request, name=None):
        schemas = list(Tenant.objects.all().values_list('schema_name',
                                                        flat=True))
        schemas.remove(get_public_schema_name())
        if name:
            try:
                probe = admin_models.Probe.objects.get(name=name)
                mt = admin_models.MetricTemplate.objects.filter(
                    probekey=admin_models.ProbeHistory.objects.get(
                        name=probe.name,
                        package__version=probe.package.version))
                if len(mt) == 0:
                    for schema in schemas:
                        # need to iterate through schemas because of foreign
                        # key in Metric model
                        with schema_context(schema):
                            admin_models.ProbeHistory.objects.filter(
                                object_id=probe).delete()
                    probe.delete()
                    return Response(status=status.HTTP_204_NO_CONTENT)
                else:
                    return Response(
                        {
                            'detail':
                            'You cannot delete Probe that is associated '
                            'to metric templates!'
                        },
                        status=status.HTTP_400_BAD_REQUEST)

            except admin_models.Probe.DoesNotExist:
                raise NotFound(status=404, detail='Probe not found')

        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)
Beispiel #23
0
    def migrate_tenant_apps(self, schema_name=None):
        self._save_south_settings()

        apps = self.tenant_apps or self.installed_apps
        self._set_managed_apps(included_apps=apps,
                               excluded_apps=self.shared_apps)

        migrate_command = MigrateCommand()
        if schema_name:
            print self.style.NOTICE("=== Running migrate for schema: %s" %
                                    schema_name)
            connection.set_schema_to_public()
            sync_tenant = get_tenant_model().objects.filter(
                schema_name=schema_name).get()
            connection.set_tenant(sync_tenant, include_public=False)
            migrate_command.execute(**self.options)
        else:
            public_schema_name = get_public_schema_name()
            tenant_schemas_count = get_tenant_model().objects.exclude(
                schema_name=public_schema_name).count()
            if not tenant_schemas_count:
                print self.style.NOTICE("No tenants found")

            for tenant_schema in get_tenant_model().objects.exclude(
                    schema_name=public_schema_name).all():
                Migrations._dependencies_done = False  # very important, the dependencies need to be purged from cache
                print self.style.NOTICE("=== Running migrate for schema %s" %
                                        tenant_schema.schema_name)
                connection.set_tenant(tenant_schema, include_public=False)
                migrate_command.execute(**self.options)

        self._restore_south_settings()
    def process_request(self, request, *args, **kwargs):

        db = self.get_database(request)
        connections[db].set_schema_to_public()

        request_cfg.db = db
        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
        connections[db].set_tenant(request.tenant)

        # 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
Beispiel #25
0
    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 = (
            'tenant_schemas',  # 2 tables
            '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 = Tenant(domain_url='arbitrary.test.com', schema_name='test')
        tenant.save(verbosity=BaseTestCase.get_verbosity())

        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 invite_to_company(self, request):
        if 'emp_id' in request.data:
            emp_id = request.data['emp_id']
        else:
            return Response(data="Required parameter 'emp_id' is missing", status=status.HTTP_400_BAD_REQUEST)

        if 'company_from' in request.data:
            company_from = request.data['company_from']
        else:
            return Response(data="Required parameter 'company_from' is missing", status=status.HTTP_400_BAD_REQUEST)

        if company_from == connection.schema_name:
            return Response(data='Cannot invite employee from same company.', status=status.HTTP_400_BAD_REQUEST)

        with schema_context(get_public_schema_name()):
            if not Company.objects.filter(name=company_from).exists():
                return Response(data='Invalid company name.', status=status.HTTP_400_BAD_REQUEST)

        with schema_context(company_from):
            try:
                employee = Employee.objects.get(id=emp_id)
            except Employee.DoesNotExist:
                return Response(data='Invalid employee id.', status=status.HTTP_400_BAD_REQUEST)

        return Response(data='Employee invited to company.', status=status.HTTP_200_OK)
    def _cursor(self):
        """
        Here it happens. We hope every Django db operation using PostgreSQL
        must go through this to get the cursor handle. We change the path.
        """
        cursor = super(DatabaseWrapper, self)._cursor()

        # Actual search_path modification for the cursor. Database will
        # search schemata from left to right when looking for the object
        # (table, index, sequence, etc.).
        if not self.schema_name:
            raise ImproperlyConfigured(
                "Database schema not set. Did you forget "
                "to call set_schema() or set_tenant()?")
        _check_identifier(self.schema_name)
        public_schema_name = get_public_schema_name()
        search_paths = []

        if self.schema_name == public_schema_name:
            search_paths = [public_schema_name]
        elif self.include_public_schema:
            search_paths = [self.schema_name, public_schema_name]
        else:
            search_paths = [self.schema_name]

        search_paths.extend(EXTRA_SEARCH_PATHS)
        cursor.execute('SET search_path = {0}'.format(','.join(search_paths)))
        return cursor
Beispiel #28
0
    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
Beispiel #29
0
def create_profile(sender, **kwargs):
    from django.db import connection

    if connection.schema_name != get_public_schema_name():
        current_user = kwargs["instance"]

        if kwargs["created"]:
            new_profile = Profile(user=current_user)

            # if user's name matches student number (e.g 9912345), set student number:
            pattern = re.compile(Profile.student_number_regex_string)
            if pattern.match(current_user.get_username()):
                new_profile.student_number = int(current_user.get_username())

            # set first and last name on the profile.  This should be removed and just use the first and last name
            # from the user model! But when first implemented, first and last name weren't included in the the sign up form.
            new_profile.first_name = current_user.first_name
            new_profile.last_name = current_user.last_name

            new_profile.save()

            staff_list = User.objects.filter(is_staff=True)
            notify.send(
                current_user,
                target=new_profile,
                recipient=staff_list[0],
                affected_users=staff_list,
                icon="<i class='fa fa-fw fa-lg fa-user text-success'></i>",
                verb='.  New user registered: ')
Beispiel #30
0
    def delete(self, request, name=None):
        schemas = list(Tenant.objects.all().values_list('schema_name',
                                                        flat=True))
        schemas.remove(get_public_schema_name())
        if name:
            try:
                mt = admin_models.MetricTemplate.objects.get(name=name)
                for schema in schemas:
                    with schema_context(schema):
                        try:
                            admin_models.History.objects.filter(
                                object_id=mt.id,
                                content_type=ContentType.objects.get_for_model(
                                    mt)).delete()
                            m = Metric.objects.get(name=name)
                            TenantHistory.objects.filter(
                                object_id=m.id,
                                content_type=ContentType.objects.get_for_model(
                                    m)).delete()
                            m.delete()
                        except Metric.DoesNotExist:
                            pass

                mt.delete()
                return Response(status=status.HTTP_204_NO_CONTENT)

            except admin_models.MetricTemplate.DoesNotExist:
                raise NotFound(status=404, detail='Metric template not found')

        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)
    def process_request(self, request):
        """
        Resets to public schema

        Some nasty weird bugs happened at the production environment without this call.
        connection.pg_thread.schema_name would already be set and then terrible errors
        would occur. Any idea why? My theory is django implements connection as some sort
        of threading local variable.
        """
        connection.set_schema_to_public()
        request.tenant = self.set_tenant(request.get_host())

        # do we have tenant-specific URLs?
        if (
            hasattr(settings, "PUBLIC_SCHEMA_URL_TOKEN")
            and request.tenant.schema_name == get_public_schema_name()
            and request.path_info[-1] == "/"
        ):
            # we are not at the public schema, manually alter routing to schema-dependent urls
            request.path_info = settings.PUBLIC_SCHEMA_URL_TOKEN + request.path_info

        if SET_TENANT_SITE_DYNAMICALLY and hasattr(request.tenant, "site") and request.tenant.site:
            SITE_THREAD_LOCAL.SITE_ID = request.tenant.site_id
            # dynamically set the site
        else:
            SITE_THREAD_LOCAL.SITE_ID = DEFAULT_SITE_ID
    def sync_tenant_apps(self, schema_name=None):
        ContentType.objects.clear_cache()

        apps = self.tenant_apps or self.installed_apps
        self._set_managed_apps(apps)
        syncdb_command = SyncdbCommand()
        if schema_name:
            print self.style.NOTICE("=== Running syncdb for schema: %s" %
                                    schema_name)
            sync_tenant = get_tenant_model().objects.filter(
                schema_name=schema_name).get()
            connection.set_tenant(sync_tenant, include_public=False)
            syncdb_command.execute(**self.options)
        else:
            public_schema_name = get_public_schema_name()
            tenant_schemas_count = get_tenant_model().objects.exclude(
                schema_name=public_schema_name).count()
            if not tenant_schemas_count:
                print self.style.NOTICE("No tenants found")

            for tenant_schema in get_tenant_model().objects.exclude(
                    schema_name=public_schema_name).all():
                print self.style.NOTICE("=== Running syncdb for schema %s" %
                                        tenant_schema.schema_name)
                connection.set_tenant(tenant_schema, include_public=False)
                syncdb_command.execute(**self.options)
Beispiel #33
0
    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.get_for_domain(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
Beispiel #34
0
    def test_command(self):
        """
        Tests that tenant_command is capable of wrapping commands
        and its parameters.
        """
        settings.SHARED_APPS = (
            "tenant_schemas",
            "django.contrib.contenttypes",
        )
        settings.TENANT_APPS = ()
        settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS
        self.sync_shared()
        Tenant(
            domain_url="localhost",
            schema_name="public").save(verbosity=BaseTestCase.get_verbosity())

        out = StringIO()
        tenant_command.Command().handle(
            "dumpdata",
            get_public_schema_name(),
            "tenant_schemas",
            natural_foreign=True,
            stdout=out,
        )
        self.assertJSONEqual(
            out.getvalue(),
            [{
                "fields": {
                    "domain_url": "localhost",
                    "schema_name": "public"
                },
                "model": "tenant_schemas.tenant",
                "pk": 1,
            }],
        )
    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)

        # 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
Beispiel #36
0
    def test_command(self):
        """
        Tests that tenant_command is capable of wrapping commands
        and its parameters.
        """
        settings.SHARED_APPS = (
            'tenant_schemas',
            'django.contrib.contenttypes',
        )
        settings.TENANT_APPS = ()
        settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS
        self.sync_shared()
        Tenant(
            domain_url='localhost',
            schema_name='public').save(verbosity=BaseTestCase.get_verbosity())

        out = StringIO()
        call_command('tenant_command',
                     args=('dumpdata', 'tenant_schemas'),
                     natural_foreign=True,
                     schema_name=get_public_schema_name(),
                     stdout=out)
        self.assertEqual(
            json.loads(
                '[{"fields": {"domain_url": "localhost", "schema_name": "public"}, '
                '"model": "tenant_schemas.tenant", "pk": 1}]'),
            json.loads(out.getvalue()))
Beispiel #37
0
    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 tenant_schemas.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

        if model_name:
            model = apps.get_model(app_label=app_label, model_name=model_name)
            if hasattr(model, '__schema_name__') and model.__schema_name__:

                conn_schema = connection.tenant.schema_name.strip()
                if conn_schema == 'public' and conn_schema != model.__schema_name__ or \
                   conn_schema != 'public' and model.__schema_name__ == 'public':
                    return False

        return None
    def migrate_tenant_apps(self, schema_name=None):
        self._save_south_settings()

        apps = self.tenant_apps or self.installed_apps
        self._set_managed_apps(included_apps=apps, excluded_apps=self.shared_apps)

        syncdb_command = MigrateCommand()
        if schema_name:
            print self.style.NOTICE("=== Running migrate for schema: %s" % schema_name)
            connection.set_schema_to_public()
            sync_tenant = get_tenant_model().objects.filter(schema_name=schema_name).get()
            connection.set_tenant(sync_tenant, include_public=False)
            syncdb_command.execute(**self.options)
        else:
            public_schema_name = get_public_schema_name()
            tenant_schemas_count = get_tenant_model().objects.exclude(schema_name=public_schema_name).count()
            if not tenant_schemas_count:
                print self.style.NOTICE("No tenants found")

            for tenant_schema in get_tenant_model().objects.exclude(schema_name=public_schema_name).all():
                Migrations._dependencies_done = False  # very important, the dependencies need to be purged from cache
                print self.style.NOTICE("=== Running migrate for schema %s" % tenant_schema.schema_name)
                connection.set_tenant(tenant_schema, include_public=False)
                syncdb_command.execute(**self.options)

        self._restore_south_settings()
Beispiel #39
0
    def process_request(self, request):  # Wei-Lin
        connection.set_schema_to_public()
        hostname_without_port = filter_url_path(request.path)

        if hostname_without_port in self.filter_first_path:
            print("public schema")
        else:

            TenantModel = get_tenant_model()

            try:
                request.tenant = TenantModel.objects.get(
                    schema_name=hostname_without_port)
            except utils.DatabaseError:
                request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
                return
            except TenantModel.DoesNotExist:
                if hostname_without_port in ("127.0.0.1", "localhost"):
                    request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
                    return
                else:
                    raise Http404

            connection.set_tenant(request.tenant)
        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
Beispiel #40
0
    def _hidden_component(self, component):
        if "tenant_schemas" not in settings.INSTALLED_APPS:
            # Only hide components when we are not in multi-tenant mode
            return False

        # For components only registered for specific schemas, return solely
        # based on that option. That is, you can't disable an explicitly
        # registered component for a given schema.
        schemas = self._schemas[component.__class__]
        if schemas:
            if connection.tenant.schema_name in schemas:
                return False
            return True

        application = component.__module__.split(".admin", 1)[0]
        logger.debug("application=%r", application)

        # Before we allow it through, make sure the application is available
        # on this tenant.
        if application in settings.INSTALLED_APPS:
            if connection.tenant.schema_name == get_public_schema_name():
                if application not in settings.SHARED_APPS:
                    return True
            else:
                if application not in settings.TENANT_APPS:
                    return True

        # Lastly, check if the client allows the application in this tenant.
        try:
            return connection.tenant.hidden_component(component)
        except AttributeError:
            return False
Beispiel #41
0
    def _cursor(self):
        """
        Here it happens. We hope every Django db operation using PostgreSQL
        must go through this to get the cursor handle. We change the path.
        """
        cursor = super(DatabaseWrapper, self)._cursor()

        # Actual search_path modification for the cursor. Database will
        # search schemata from left to right when looking for the object
        # (table, index, sequence, etc.).
        if not self.schema_name:
            raise ImproperlyConfigured("Database schema not set. Did you forget "
                                       "to call set_schema() or set_tenant()?")
        _check_identifier(self.schema_name)
        public_schema_name = get_public_schema_name()
        search_paths = []

        if self.schema_name == public_schema_name:
            search_paths = [public_schema_name]
        elif self.include_public_schema:
            search_paths = [self.schema_name, public_schema_name]
        else:
            search_paths = [self.schema_name]

        search_paths.extend(EXTRA_SEARCH_PATHS)
        cursor.execute('SET search_path = {}'.format(','.join(search_paths)))
        return cursor
Beispiel #42
0
    def handle_noargs(self, **options):
        # todo: awful lot of duplication from sync_schemas
        sync_tenant = options.get('tenant')
        sync_public = options.get('shared')
        schema_name = options.get('schema_name')
        self.installed_apps = settings.INSTALLED_APPS
        self.options = options

        if sync_public and schema_name:
            raise CommandError(
                "schema should only be used with the --tenant switch.")

        if not sync_public and not sync_tenant and not schema_name:
            # no options set, sync both
            sync_tenant = True
            sync_public = True

        if schema_name:
            if schema_name == get_public_schema_name():
                sync_public = True
            else:
                sync_tenant = True

        if hasattr(settings, 'TENANT_APPS'):
            self.tenant_apps = settings.TENANT_APPS
        if hasattr(settings, 'SHARED_APPS'):
            self.shared_apps = settings.SHARED_APPS

        if sync_public:
            self.migrate_public_apps()
        if sync_tenant:
            self.migrate_tenant_apps(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 run_migrations(self, tenants):
        public_schema_name = get_public_schema_name()

        if public_schema_name in tenants:
            run_migrations(self.args, self.options, self.codename, public_schema_name)
            tenants.pop(tenants.index(public_schema_name))

        self.run_tenant_migrations(tenants)
 def setUpClass(cls):
     super(TenantDataAndSettingsTest, cls).setUpClass()
     settings.SHARED_APPS = ('tenant_schemas', )
     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(domain_url='test.com', schema_name=get_public_schema_name()).save()
    def get_tenant(self, model, hostname, request):
        try:
            return super(DefaultTenantMiddleware, self).get_tenant(
                model, hostname, request)
        except model.DoesNotExist:
            schema_name = self.DEFAULT_SCHEMA_NAME
            if not schema_name:
                schema_name = get_public_schema_name()

            return model.objects.get(schema_name=schema_name)
 def setUpClass(cls):
     super(RoutesTestCase, cls).setUpClass()
     settings.SHARED_APPS = ('tenant_schemas', )
     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 = Tenant(domain_url='test.com', schema_name=get_public_schema_name())
     cls.public_tenant.save(verbosity=BaseTestCase.get_verbosity())
    def _cursor(self, name=None):
        """
        Here it happens. We hope every Django db operation using PostgreSQL
        must go through this to get the cursor handle. We change the path.
        """
        if name:
            # Only supported and required by Django 1.11 (server-side cursor)
            cursor = super(DatabaseWrapper, self)._cursor(name=name)
        else:
            cursor = super(DatabaseWrapper, self)._cursor()

        # optionally limit the number of executions - under load, the execution
        # of `set search_path` can be quite time consuming
        if (not get_limit_set_calls()) or not self.search_path_set:
            # Actual search_path modification for the cursor. Database will
            # search schemata from left to right when looking for the object
            # (table, index, sequence, etc.).
            if not self.schema_name:
                raise ImproperlyConfigured("Database schema not set. Did you forget "
                                           "to call set_schema() or set_tenant()?")
            _check_schema_name(self.schema_name)
            public_schema_name = get_public_schema_name()
            search_paths = []

            if self.schema_name == public_schema_name:
                search_paths = [public_schema_name]
            elif self.include_public_schema:
                search_paths = [self.schema_name, public_schema_name]
            else:
                search_paths = [self.schema_name]

            search_paths.extend(EXTRA_SEARCH_PATHS)

            if name:
                # Named cursor can only be used once
                cursor_for_search_path = self.connection.cursor()
            else:
                # Reuse
                cursor_for_search_path = cursor

            # In the event that an error already happened in this transaction and we are going
            # to rollback we should just ignore database error when setting the search_path
            # if the next instruction is not a rollback it will just fail also, so
            # we do not have to worry that it's not the good one
            try:
                cursor_for_search_path.execute('SET search_path = {0}'.format(','.join(search_paths)))
            except (django.db.utils.DatabaseError, psycopg2.InternalError):
                self.search_path_set = False
            else:
                self.search_path_set = True

            if name:
                cursor_for_search_path.close()

        return cursor
    def sync_tenant_apps(self, schema_name=None):
        if schema_name:
            tenant = get_tenant_model().objects.filter(schema_name=schema_name).get()
            self._sync_tenant(tenant)
        else:
            all_tenants = get_tenant_model().objects.exclude(schema_name=get_public_schema_name())
            if not all_tenants:
                self._notice("No tenants found!")

            for tenant in all_tenants:
                self._sync_tenant(tenant)
Beispiel #50
0
 def set_tenant(self, tenant, include_public=True):
     """
     Main API method to current database schema,
     but it does not actually modify the db connection.
     """
     self.tenant = tenant
     if tenant is None:
         self.schema_name = get_public_schema_name()
     else:
         self.schema_name = tenant.schema_name
         self.include_public_schema = include_public
    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 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
                self.delete(force_drop=True)
                raise
 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)
Beispiel #54
0
    def save(self, verbosity=1, force_create=False, *args, **kwargs):
        is_new = self.pk is None or force_create

        if is_new and connection.schema_name not in (get_public_schema_name(), settings.DEFAULT_TENANT_SCHEMA):
            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.is_active and self.auto_create_schema:
            try:
                self.create_schema(check_if_exists=True, verbosity=verbosity)
                post_schema_sync.send(sender=TenantMixin, tenant=self)
            except:
                # We failed creating the tenant, delete what we created and
                # re-raise the exception
                self.delete(force_drop=True)
                raise
    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)
Beispiel #56
0
def restore_schema(task, **kwargs):
    """ Switches the schema back to the one from before running the task. """
    from tenant_schemas.utils import get_public_schema_name

    schema_name, include_public = getattr(task,
                                          '_old_schema',
                                          (get_public_schema_name(), True))

    # If the schema names match, don't do anything.
    if connection.schema_name == schema_name:
        return

    connection.set_schema(schema_name, include_public=include_public)
    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 test_command(self):
        """
        Tests that tenant_command is capable of wrapping commands
        and its parameters.
        """
        settings.SHARED_APPS = ('tenant_schemas',
                                'django.contrib.contenttypes', )
        settings.TENANT_APPS = ()
        settings.INSTALLED_APPS = settings.SHARED_APPS + settings.TENANT_APPS
        self.sync_shared()
        Tenant(domain_url='localhost', schema_name='public').save(verbosity=BaseTestCase.get_verbosity())

        out = StringIO()
        if django.VERSION >= (1, 8, 0):
            call_command('tenant_command', args=('dumpdata', 'tenant_schemas'), natural_foreign=True,
                         schema_name=get_public_schema_name(), stdout=out)
        else:
            call_command('tenant_command', 'dumpdata', 'tenant_schemas', natural_foreign=True,
                         schema_name=get_public_schema_name(), stdout=out)
        self.assertItemsEqual(
            json.loads('[{"fields": {"domain_url": "localhost", "schema_name": "public"}, '
                       '"model": "tenant_schemas.tenant", "pk": 1}]'),
            json.loads(out.getvalue()))
    def allow_migrate(self, db, model):
        # 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 tenant_schemas.utils import get_public_schema_name, app_labels

        if connection.schema_name == get_public_schema_name():
            if model._meta.app_label not in app_labels(settings.SHARED_APPS):
                return False
        else:
            if model._meta.app_label not in app_labels(settings.TENANT_APPS):
                return False

        return None
Beispiel #60
0
def switch_schema(task, kwargs, **kw):
    """ Switches schema of the task, before it has been run. """
    # Lazily load needed functions, as they import django model functions which
    # in turn load modules that need settings to be loaded and we can't
    # guarantee this module was loaded when the settings were ready.
    from tenant_schemas.utils import get_public_schema_name, get_tenant_model

    old_schema = (connection.schema_name, connection.include_public_schema)
    setattr(task, '_old_schema', old_schema)

    # Pop it from the kwargs since tasks don't except the additional kwarg.
    # This change is transparent to the system.
    schema = kwargs.pop('_schema_name', get_public_schema_name())

    # If the schema has not changed, don't do anything.
    if connection.schema_name == schema:
        return

    if connection.schema_name != get_public_schema_name():
        connection.set_schema_to_public()

    tenant = get_tenant_model().objects.get(schema_name=schema)
    connection.set_tenant(tenant, include_public=True)