Esempio n. 1
0
    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()
Esempio n. 2
0
    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 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')
Esempio n. 4
0
    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]
Esempio n. 5
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 = ('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)
Esempio n. 6
0
 def store_tenant_domain(self, **fields):
     try:
         domain = get_tenant_domain_model().objects.create(**fields)
         domain.save()
     except exceptions.ValidationError as e:
         self.stderr.write("Error: %s" % '; '.join(e.messages))
         return None
     except IntegrityError:
         return None
Esempio n. 7
0
    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()
Esempio n. 8
0
    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))
Esempio n. 9
0
    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()
Esempio n. 10
0
    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)
Esempio n. 11
0
    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 process_request(self, request):
        connection.set_schema_to_public()
        hostname_without_port = remove_www_and_dev(request.get_host().split(':')[0])

        try:
            domain = get_tenant_domain_model().objects.select_related('tenant').get(domain=hostname_without_port)
            request.tenant = domain.tenant
        except utils.DatabaseError:
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
            return
        except get_tenant_domain_model().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
Esempio n. 13
0
 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()
Esempio n. 14
0
    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)
Esempio n. 15
0
    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))
Esempio n. 16
0
    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()
Esempio n. 17
0
    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()
Esempio n. 18
0
    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)
Esempio n. 19
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.
        """
        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)
Esempio n. 20
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)

        domain_model = get_tenant_domain_model()
        try:
            domain = self.get_domain(domain_model, hostname)
            tenant = domain.tenant
            if not tenant:
                raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for hostname "%s"' % hostname)
        except domain_model.DoesNotExist:
            raise self.DOMAIN_NOT_FOUND_EXCEPTION('No domain for hostname "%s"' % hostname)

        # domain.tenant.domain_url = hostname
        # domain.tenant.current_domain = domain
        # request.tenant = domain.tenant
        # request.tenant_current_domain = domain

        # set 'current_domain' in 'tenant' to use across your application, e.g:
        # ...
        # from django.db import connection
        # if connection.tenant.current_domain.id == any_id:
        #    do ...
        # ...
        tenant.current_domain = domain
        tenant.domain_url = hostname
        request.tenant = tenant
        request.tenant_current_domain = domain

        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
Esempio n. 21
0
    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)
Esempio n. 22
0
    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
Esempio n. 23
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.
        """
        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)
Esempio n. 24
0
    def setUpClass(cls):
        super().setUpClass()

        # reset owner for cls.tenant b/c we'd like for testing all tenants
        # to be owned by the same user
        cls.tenant.owner = cls.tester
        cls.tenant.name = 'For testing purposes'
        cls.tenant.save()

        # we give this 2nd user access to the tenant
        cls.tester2 = UserFactory()

        with utils.schema_context('public'):
            cls.tenant2 = utils.get_tenant_model()(
                schema_name='my_other_tenant', owner=cls.tester)
            cls.tenant2.save()

            cls.domain2 = utils.get_tenant_domain_model()(
                tenant=cls.tenant2, domain='other.example.com')
            cls.domain2.save()
Esempio n. 25
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)

        domain_model = get_tenant_domain_model()
        try:
            tenant = self.get_tenant(domain_model, hostname)
        except domain_model.DoesNotExist:
            raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for hostname "%s"' % hostname)

        tenant.domain_url = hostname
        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
Esempio n. 26
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)

        domain_model = get_tenant_domain_model()
        try:
            tenant = self.get_tenant(domain_model, hostname)
        except domain_model.DoesNotExist:
            raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for hostname "%s"' % hostname)

        tenant.domain_url = hostname
        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
            set_urlconf(request.urlconf)
Esempio n. 27
0
    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.
        """
        settings.SHARED_APPS = ('django_tenants',
                                'customers',
                                'django.contrib.auth',
                                'django.contrib.contenttypes', )
        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()

        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)
Esempio n. 28
0
    def process_request(self, request):
        connection.set_schema_to_public()

        hostname = self.hostname_from_request(request)
        domain_model = get_tenant_domain_model()

        try:
            tenant = self.get_tenant(domain_model, hostname)
            request.tenant = tenant
            connection.set_tenant(request.tenant)

        except (domain_model.DoesNotExist, AssertionError):
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
            set_urlconf(request.urlconf)
            return

        if hasattr(
                settings, 'PUBLIC_SCHEMA_URLCONF'
        ) and request.tenant.schema_name == get_public_schema_name():
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
            set_urlconf(request.urlconf)
Esempio n. 29
0
    def test_signal_on_tenant_create(self):
        """
        Since migrate gets called on creating of a tenant, check
        the signal gets sent.
        """
        executor = get_executor()

        tenant = get_tenant_model()(schema_name='test')
        with catch_signal(schema_migrated) as handler:
            tenant.save()

        if executor == 'simple':
            handler.assert_called_once_with(schema_name='test',
                                            sender=mock.ANY,
                                            signal=schema_migrated)
        elif executor == 'multiprocessing':
            # migrations run in a different process, therefore signal
            # will get sent in a different process as well
            handler.assert_not_called()

        domain = get_tenant_domain_model()(tenant=tenant,
                                           domain='something.test.com')
        domain.save()
        self.created = [domain, tenant]
Esempio n. 30
0
    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.
        """
        settings.SHARED_APPS = (
            'django_tenants',
            'customers',
            'django.contrib.auth',
            'django.contrib.contenttypes',
        )
        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()

        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)
Esempio n. 31
0
 def __call__(self, scope):
     if "headers" not in scope:
         raise ValueError(
             "MTSchemaMiddleware was passed a scope that did not have a headers key "
             "(make sure it is only passed HTTP or WebSocket connections)")
     for key, value in scope.get('headers', []):
         if key == b'host':
             hostname = value.decode('ascii').split(':')[0]
             from django_tenants.utils import get_tenant_domain_model
             domain_model = get_tenant_domain_model()
             domain = domain_model.objects.select_related('tenant')\
                 .get(domain=hostname)
             try:
                 tenant = domain.tenant
                 schema_name = tenant.schema_name
             except domain_model.DoesNotExist:
                 raise Http404('No tenant for hostname "%s"' % hostname)
             break
     else:
         raise ValueError(
             "The headers key in the scope is invalid. " +
             "(make sure it is passed valid HTTP or WebSocket connections)")
     return self.inner(
         dict(scope, schema_name=schema_name, multitenant=True))
Esempio n. 32
0
    def test_tenant_schema_behaves_as_decorator(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

        @tenant_context(tenant)
        def test_tenant_func():
            DummyModel(name='No exception please').save()

        test_tenant_func()

        connection.tenant = None

        @schema_context(tenant.schema_name)
        def test_schema_func():
            DummyModel(name='Survived it!').save()

        test_schema_func()

        self.created = [domain, tenant]
Esempio n. 33
0
    def setUp(self) -> None:
        self.factory = TenantRequestFactory(self.tenant)
        self.view = views.ListTenants.as_view()
        self.url = '/api/v2/internal/tenants/'
        self.user = CustUser.objects.create_user(username='******')
        # get_tenant_domain_model().objects.create(
        #     domain='test.domain.url', tenant=self.tenant, is_primary=True
        # )

        with schema_context(get_public_schema_name()):
            self.tenant1 = Tenant(name='TEST1', schema_name='test1')
            self.tenant1.auto_create_schema = False

            self.tenant2 = Tenant(name='TEST2', schema_name='test2')
            self.tenant2.auto_create_schema = False

            self.tenant3 = Tenant(
                name='all',
                schema_name=get_public_schema_name(),
            )
            self.tenant3.auto_create_schema = False

            self.tenant1.save()
            self.tenant2.save()
            self.tenant3.save()
            get_tenant_domain_model().objects.create(domain='test1.domain.url',
                                                     tenant=self.tenant1,
                                                     is_primary=True)
            get_tenant_domain_model().objects.create(domain='test2.domain.url',
                                                     tenant=self.tenant2,
                                                     is_primary=True)
            get_tenant_domain_model().objects.create(domain='domain.url',
                                                     tenant=self.tenant3,
                                                     is_primary=True)
            self.supertenant_superuser = CustUser.objects.create_user(
                username='******', is_superuser=True)
            self.supertenant_user = CustUser.objects.create_user(
                username='******')
Esempio n. 34
0
class Command(BaseCommand):
    help = 'Create a tenant'

    # Only use editable fields
    # noinspection PyProtectedMember
    tenant_fields = [
        field for field in get_tenant_model()._meta.fields
        if field.editable and not field.primary_key
    ]
    # noinspection PyProtectedMember
    domain_fields = [
        field for field in get_tenant_domain_model()._meta.fields
        if field.editable and not field.primary_key
    ]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def add_arguments(self, parser):

        for field in self.tenant_fields:
            parser.add_argument('--%s' % field.attname,
                                help='Specifies the %s for tenant.' %
                                field.attname)

        for field in self.domain_fields:
            parser.add_argument(
                '--domain-%s' % field.attname,
                help="Specifies the %s for the tenant's domain." %
                field.attname)

        parser.add_argument('-s',
                            action="store_true",
                            help='Create a superuser afterwards.')

    def handle(self, *args, **options):

        tenant_data = {}
        for field in self.tenant_fields:
            input_value = options.get(field.attname, None)
            tenant_data[field.attname] = input_value

        domain_data = {}
        for field in self.domain_fields:
            input_value = options.get('domain_%s' % field.attname, None)
            domain_data[field.attname] = input_value

        while True:
            for field in self.tenant_fields:
                if tenant_data.get(field.attname, '') == '':
                    input_msg = field.verbose_name
                    default = field.get_default()
                    if default:
                        input_msg = "%s (leave blank to use '%s')" % (
                            input_msg, default)

                    input_value = input(force_str(
                        '%s: ' % input_msg)) or default
                    tenant_data[field.attname] = input_value
            tenant = self.store_tenant(**tenant_data)
            if tenant is not None:
                break
            tenant_data = {}

        while True:
            domain_data['tenant_id'] = tenant.pk
            for field in self.domain_fields:
                if domain_data.get(field.attname, '') == '':
                    input_msg = field.verbose_name
                    default = field.get_default()
                    if default:
                        input_msg = "%s (leave blank to use '%s')" % (
                            input_msg, default)

                    input_value = input(force_str(
                        '%s: ' % input_msg)) or default
                    domain_data[field.attname] = input_value
            domain = self.store_tenant_domain(**domain_data)
            if domain is not None:
                break
            domain_data = {}

        if options.get('s', None):
            self.stdout.write("Create superuser for %s" %
                              tenant_data['schema_name'])
            call_command('create_tenant_superuser',
                         schema_name=tenant_data['schema_name'],
                         interactive=True)

    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 store_tenant_domain(self, **fields):
        try:
            domain = get_tenant_domain_model().objects.create(**fields)
            domain.save()
            return domain
        except exceptions.ValidationError as e:
            self.stderr.write("Error: %s" % '; '.join(e.messages))
            return None
        except IntegrityError:
            return None
Esempio n. 35
0
    def test_files_are_saved_under_subdirectories_per_tenant(self):
        # Create new tenants
        tenant1 = utils.get_tenant_model()(schema_name="tenant1")
        tenant1.save()

        domain1 = utils.get_tenant_domain_model()(
            tenant=tenant1, domain="something.test.com"
        )
        domain1.save()

        tenant2 = utils.get_tenant_model()(schema_name="tenant2")
        tenant2.save()

        domain2 = utils.get_tenant_domain_model()(tenant=tenant2, domain="example.com")
        domain2.save()

        connection.set_schema_to_public()

        # This file should be saved on the public schema
        public_file_name = self.storage.save("hello_world.txt", ContentFile("Hello World"))
        public_os_path = self.storage.path(public_file_name)
        public_url = self.storage.url(public_file_name)

        # Switch to tenant1
        with utils.tenant_context(tenant1):
            storage = self.storage_class(
                location="{}/{}".format(self.temp_dir, connection.schema_name), base_url="/test_media_url/"
            )
            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):
            storage = self.storage_class(
                location="{}/{}".format(self.temp_dir, connection.schema_name), base_url="/test_media_url/"
            )
            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("public/%s" % public_file_name)
        )
        self.assertTrue(t1_os_path.endswith("tenant1/%s" % t1_file_name))
        self.assertTrue(t2_os_path.endswith("tenant2/%s" % t2_file_name))

        # Assert urls are correct
        self.assertEqual(public_url, "/test_media_url/public/%s" % public_file_name)
        self.assertEqual(t1_url, "/test_media_url/tenant1/%s" % t1_file_name)
        self.assertEqual(t2_url, "/test_media_url/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")
Esempio n. 36
0
class Command(BaseCommand):
    help = 'Clones a tenant'

    # Only use editable fields
    tenant_fields = [field for field in get_tenant_model()._meta.fields
                     if field.editable and not field.primary_key]
    # noinspection PyProtectedMember
    domain_fields = [field for field in get_tenant_domain_model()._meta.fields
                     if field.editable and not field.primary_key]

    def add_arguments(self, parser):

        parser.add_argument('--clone_from',
                            help='Specifies which schema to clone.')

        parser.add_argument('--clone_tenant_fields',
                            help='Clone the tenant fields.')

        parser.add_argument('--db_user',
                            help='the user for the database the default is postgres')

        for field in self.tenant_fields:
            parser.add_argument('--%s' % field.attname,
                                help='Specifies the %s for tenant.' % field.attname)

        for field in self.domain_fields:
            parser.add_argument('--domain-%s' % field.attname,
                                help="Specifies the %s for the tenant's domain." % field.attname)

        parser.add_argument('-s', action="store_true",
                            help='Create a superuser afterwards.')

    def _input(self, question):
        """Wrapper around 'input' for overriding while testing"""
        return input(question)

    def handle(self, *args, **options):
        tenant_model = get_tenant_model()
        all_tenants = tenant_model.objects.all()
        tenant_data = {}
        for field in self.tenant_fields:
            input_value = options.get(field.attname, None)
            tenant_data[field.attname] = input_value

        domain_data = {}
        for field in self.domain_fields:
            input_value = options.get('domain_%s' % field.attname, None)
            domain_data[field.attname] = input_value

        clone_schema_from = options.get('clone_from')
        while clone_schema_from == '' or clone_schema_from is None:

            while True:
                clone_schema_from = self._input("Clone Tenant Schema ('?' to list schemas): ")
                if clone_schema_from == '?':
                    self.stdout.write('\n'.join(["%s" % t.schema_name for t in all_tenants]))
                else:
                    break

        clone_tenant_fields = options.get('clone_tenant_fields')
        while clone_tenant_fields is None or clone_tenant_fields.lower() not in ['no', 'yes', 'true', 'false']:
            clone_tenant_fields = self._input("Clone Tenant tenant fields: ")

        if clone_tenant_fields.lower() in ['yes', 'true']:
            new_schema_name = options.get('schema_name')
            while new_schema_name == '' or new_schema_name is None:
                new_schema_name = self._input("New tenant schema name: ")
            tenant_data['schema_name'] = new_schema_name

            tenant = self.store_tenant(clone_schema_from=clone_schema_from,
                                       clone_tenant_fields=True,
                                       **tenant_data)
        else:
            while True:
                for field in self.tenant_fields:
                    if tenant_data.get(field.attname, '') == '':
                        input_msg = field.verbose_name
                        default = field.get_default()
                        if default:
                            input_msg = "%s (leave blank to use '%s')" % (input_msg, default)

                        input_value = self._input(force_str('%s: ' % input_msg)) or default
                        tenant_data[field.attname] = input_value
                tenant = self.store_tenant(clone_schema_from=clone_schema_from,
                                           clone_tenant_fields=False,
                                           **tenant_data)
                if tenant is not None:
                    break
                tenant_data = {}

        while True:
            domain_data['tenant_id'] = tenant.id
            for field in self.domain_fields:
                if domain_data.get(field.attname, '') == '':
                    input_msg = field.verbose_name
                    default = field.get_default()
                    if default:
                        input_msg = "%s (leave blank to use '%s')" % (input_msg, default)

                    input_value = self._input(force_str('%s: ' % input_msg)) or default
                    domain_data[field.attname] = input_value
            domain = self.store_tenant_domain(**domain_data)
            if domain is not None:
                break
            domain_data = {}

    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 store_tenant_domain(self, **fields):
        try:
            domain = get_tenant_domain_model().objects.create(**fields)
            domain.save()
            return domain
        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
Esempio n. 37
0
    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')
Esempio n. 38
0
class Command(BaseCommand):
    help = 'Clones a tenant'

    # Only use editable fields
    tenant_fields = [
        field for field in get_tenant_model()._meta.fields
        if field.editable and not field.primary_key
    ]
    domain_fields = [
        field for field in get_tenant_domain_model()._meta.fields
        if field.editable and not field.primary_key
    ]

    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)

        self.option_list = ()

        if django.VERSION <= (1, 10, 0):
            self.option_list = BaseCommand.option_list

        self.option_list += (make_option(
            '--clone_from', help='Specifies which schema to clone.'), )

        for field in self.tenant_fields:
            self.option_list += (make_option(
                '--%s' % field.name,
                help='Specifies the %s for tenant.' % field.name), )

        for field in self.domain_fields:
            self.option_list += (make_option(
                '--%s' % field.name,
                help="Specifies the %s for the tenant's domain." %
                field.name), )

    def handle(self, *args, **options):

        tenant_data = {}
        for field in self.tenant_fields:
            input_value = options.get(field.name, None)
            tenant_data[field.name] = input_value

        domain_data = {}
        for field in self.domain_fields:
            input_value = options.get(field.name, None)
            domain_data[field.name] = input_value

        clone_schema_from = options.get('clone_from')
        while clone_schema_from == '' or clone_schema_from is None:
            clone_schema_from = input(force_str('Clone schema from: '))

        tenant = None
        while True:
            for field in self.tenant_fields:
                if tenant_data.get(field.name, '') == '':
                    input_msg = field.verbose_name
                    default = field.get_default()
                    if default:
                        input_msg = "%s (leave blank to use '%s')" % (
                            input_msg, default)

                    input_value = input(force_str(
                        '%s: ' % input_msg)) or default
                    tenant_data[field.name] = input_value
            tenant = self.store_tenant(clone_schema_from, **tenant_data)
            if tenant is not None:
                break
            tenant_data = {}

        while True:
            domain_data['tenant'] = tenant
            for field in self.domain_fields:
                if domain_data.get(field.name, '') == '':
                    input_msg = field.verbose_name
                    default = field.get_default()
                    if default:
                        input_msg = "%s (leave blank to use '%s')" % (
                            input_msg, default)

                    input_value = input(force_str(
                        '%s: ' % input_msg)) or default
                    domain_data[field.name] = input_value
            domain = self.store_tenant_domain(**domain_data)
            if domain is not None:
                break
            domain_data = {}

    def store_tenant(self, clone_schema_from, **fields):
        connection.set_schema_to_public()
        cursor = connection.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 store_tenant_domain(self, **fields):
        try:
            domain = get_tenant_domain_model().objects.create(**fields)
            domain.save()
            return domain
        except exceptions.ValidationError as e:
            self.stderr.write("Error: %s" % '; '.join(e.messages))
            return None
        except IntegrityError:
            return None
Esempio n. 39
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)
        print('IN PROCESS REQUEST - METHOD = ', request.method)
        print('IN PROCESS REQUEST - META = ', request.META)
        print('IN PROCESS REQUEST - HEADERS = ', request.headers)

        tenant_id = request.headers.get('x-tenant-id')
        print('X TENTANT = ', tenant_id)

        domain_model = get_tenant_domain_model()

        if request.method != 'OPTIONS' and request.method != 'GET':
            try:
                tenant_id = request.headers.get('X-TENANT-ID')
                print('TENANT ID = ', tenant_id)
                tenant_model = get_tenant_model()
                if tenant_model.objects.filter(tenant_id=tenant_id).exists():
                    tenant = tenant_model.objects.get(tenant_id=tenant_id)
                    print('TENANT = ', tenant.__dict__)
                    tenant.domain_url = hostname
                    request.tenant = tenant

                    connection.set_tenant(request.tenant)
                else:
                    print('NO TENANT')
                    return HttpResponse('Unauthorized - Invalid Tenant Id',
                                        status=401)
            except domain_model.DoesNotExist:
                raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for id "%s"' %
                                                      tenant_id)

        # if request.method != 'OPTIONS':
        #     try:
        #         tenant_id = request.headers.get('X-TENANT-ID')
        #         print('TENANT ID = ', tenant_id)
        #         tenant_model = get_tenant_model()
        #         tenant = tenant_model.objects.get(tenant_id=tenant_id)
        #         print('TENANT = ', tenant.__dict__)
        #     except domain_model.DoesNotExist:
        #         raise self.TENANT_NOT_FOUND_EXCEPTION(
        #             'No tenant for id "%s"' % tenant_id)

        #     tenant.domain_url = hostname
        #     request.tenant = tenant

        #     connection.set_tenant(request.tenant)

        # ** OLD DOMAIN METHOD***
        # try:
        #     tenant = self.get_tenant(domain_model, hostname)
        #     print('TENANT = ', tenant)
        # except domain_model.DoesNotExist:
        #     raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for hostname "%s"' % hostname)

        # tenant.domain_url = hostname
        # 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
            set_urlconf(request.urlconf)