예제 #1
0
    def get_tenant_from_options_or_interactive(self, **options):
        TenantModel = get_tenant_model()
        all_tenants = TenantModel.objects.all()

        if not all_tenants:
            raise CommandError("""There are no tenants in the system.
To learn how create a tenant, see:
https://django-tenant-schemas.readthedocs.io/en/latest/use.html#creating-a-tenant"""
                               )

        if options.get('schema_name'):
            tenant_schema = options['schema_name']
        else:
            while True:
                tenant_schema = input(
                    "Enter Tenant Schema ('?' to list schemas): ")
                if tenant_schema == '?':
                    print('\n'.join([
                        "%s - %s" % (
                            t.schema_name,
                            t.domain_url,
                        ) for t in all_tenants
                    ]))
                else:
                    break

        if tenant_schema not in [t.schema_name for t in all_tenants]:
            raise CommandError("Invalid tenant schema, '%s'" %
                               (tenant_schema, ))

        return TenantModel.objects.get(schema_name=tenant_schema)
예제 #2
0
 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)
예제 #3
0
    def handle(self, *args, **options):
        columns = ('schema_name', 'domain_url')

        TenantModel = get_tenant_model()
        all_tenants = TenantModel.objects.values_list(*columns)

        out = csv.writer(sys.stdout, dialect=csv.excel_tab)
        for tenant in all_tenants:
            out.writerow(tenant)
예제 #4
0
 def test_public_schema_on_extra_search_paths(self):
     TenantModel = get_tenant_model()
     TenantModel.objects.create(
         schema_name='demo1', domain_url='demo1.example.com')
     TenantModel.objects.create(
         schema_name='demo2', domain_url='demo2.example.com')
     self.assertBestPractice([
         Critical("public can not be included on PG_EXTRA_SEARCH_PATHS."),
         Critical("Do not include tenant schemas (demo1, demo2) on PG_EXTRA_SEARCH_PATHS."),
     ])
예제 #5
0
    def setUpClass(cls):
        cls.sync_shared()
        cls.add_allowed_test_domain()
        tenant_domain = 'tenant.test.com'
        cls.tenant = get_tenant_model()(domain_url=tenant_domain,
                                        schema_name='test')
        cls.tenant.save(
            verbosity=0
        )  # todo: is there any way to get the verbosity from the test command here?

        connection.set_tenant(cls.tenant)
예제 #6
0
    def setUpClass(cls):
        cls.sync_shared()
        cls.add_allowed_test_domain()
        tenant_domain = 'tenant.test.com'

        TenantModel = get_tenant_model()
        try:
            cls.tenant = TenantModel.objects.get(domain_url=tenant_domain,
                                                 schema_name='test')
        except:
            cls.tenant = TenantModel(domain_url=tenant_domain,
                                     schema_name='test')
            cls.tenant.save(verbosity=0)

        connection.set_tenant(cls.tenant)
예제 #7
0
    def handle(self, *args, **options):
        super(Command, self).handle(*args, **options)
        self.PUBLIC_SCHEMA_NAME = get_public_schema_name()

        executor = get_executor(codename=self.executor)(self.args,
                                                        self.options)

        if self.sync_public and not self.schema_name:
            self.schema_name = self.PUBLIC_SCHEMA_NAME

        if self.sync_public:
            executor.run_migrations(tenants=[self.schema_name])

        if self.sync_tenant:
            if self.schema_name and self.schema_name != self.PUBLIC_SCHEMA_NAME:
                if not schema_exists(self.schema_name):
                    raise MigrationSchemaMissing(
                        'Schema "{}" does not exist'.format(self.schema_name))
                else:
                    tenants = [self.schema_name]
            else:
                tenants = get_tenant_model().objects.exclude(
                    schema_name=get_public_schema_name()).values_list(
                        'schema_name', flat=True)

            executor.run_migrations(tenants=tenants)

            from wagtail.wagtailcustomers.models import Customer as customer
            if not customer.objects.filter(
                    schema_name=get_public_schema_name()).exists():
                customer(domain_url='tuiuiu.io',
                         schema_name=get_public_schema_name(),
                         name='tuiuiu.io',
                         description='tuiuiu.io').save()

            from django.contrib.auth.models import User as user
            if not user.objects.filter(username='******').exists():
                user.objects.create_superuser('admin', '*****@*****.**',
                                              'admin')
예제 #8
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:
            # get_tenant must be implemented by extending this class.
            tenant = self.get_tenant(TenantModel, hostname, request)
            assert isinstance(tenant, TenantModel)
        except TenantModel.DoesNotExist:
            raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for {!r}'.format(
                request.get_host()))
        except AssertionError:
            raise self.TENANT_NOT_FOUND_EXCEPTION('Invalid tenant {!r}'.format(
                request.tenant))

        request.tenant = tenant
        connection.set_tenant(request.tenant)

        # Content type can no longer be cached as public and tenant schemas
        # have different models. If someone wants to change this, the cache
        # needs to be separated between public and shared schemas. If this
        # cache isn't cleared, this can cause permission problems. For example,
        # on public, a particular model has id 14, but on the tenants it has
        # the id 15. if 14 is cached instead of 15, the permissions for the
        # wrong model will be fetched.
        ContentType.objects.clear_cache()

        # Do we have a public-specific urlconf?
        if hasattr(
                settings, 'PUBLIC_SCHEMA_URLCONF'
        ) and request.tenant.schema_name == get_public_schema_name():
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
예제 #9
0
def best_practice(app_configs, **kwargs):
    """
    Test for configuration recommendations. These are best practices, they
    avoid hard to find bugs and unexpected behaviour.
    """
    if app_configs is None:
        app_configs = apps.get_app_configs()

    # Take the app_configs and turn them into *old style* application names.
    # This is what we expect in the SHARED_APPS and TENANT_APPS settings.
    INSTALLED_APPS = [
        config.name
        for config in app_configs
    ]

    if not hasattr(settings, 'TENANT_APPS'):
        return [Critical('TENANT_APPS setting not set')]

    if not hasattr(settings, 'TENANT_MODEL'):
        return [Critical('TENANT_MODEL setting not set')]

    if not hasattr(settings, 'SHARED_APPS'):
        return [Critical('SHARED_APPS setting not set')]

    if 'wagtail.wagtailtenant.routers.TenantSyncRouter' not in settings.DATABASE_ROUTERS:
        return [
            Critical("DATABASE_ROUTERS setting must contain "
                     "'wagtail.wagtailtenant.routers.TenantSyncRouter'.")
        ]

    errors = []

    django_index = next(i for i, s in enumerate(INSTALLED_APPS) if s.startswith('django.'))
    if INSTALLED_APPS.index('wagtail.wagtailtenant') > django_index:
        errors.append(
            Warning("You should put 'wagtail.wagtailtenant' before any django "
                    "core applications in INSTALLED_APPS.",
                    obj="django.conf.settings",
                    hint="This is necessary to overwrite built-in django "
                         "management commands with their schema-aware "
                         "implementations.",
                    id="wagtailtenant.W001"))

    if not settings.TENANT_APPS:
        errors.append(
            Error("TENANT_APPS is empty.",
                  hint="Maybe you don't need this app?",
                  id="wagtailtenant.E001"))

    if hasattr(settings, 'PG_EXTRA_SEARCH_PATHS'):
        if get_public_schema_name() in settings.PG_EXTRA_SEARCH_PATHS:
            errors.append(Critical(
                "%s can not be included on PG_EXTRA_SEARCH_PATHS."
                % get_public_schema_name()))

        # make sure no tenant schema is in settings.PG_EXTRA_SEARCH_PATHS
        invalid_schemas = set(settings.PG_EXTRA_SEARCH_PATHS).intersection(
            get_tenant_model().objects.all().values_list('schema_name', flat=True))
        if invalid_schemas:
            errors.append(Critical(
                "Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS."
                % ", ".join(sorted(invalid_schemas))))

    if not settings.SHARED_APPS:
        errors.append(
            Warning("SHARED_APPS is empty.",
                    id="wagtailtenant.W002"))

    if not set(settings.TENANT_APPS).issubset(INSTALLED_APPS):
        delta = set(settings.TENANT_APPS).difference(INSTALLED_APPS)
        errors.append(
            Error("You have TENANT_APPS that are not in INSTALLED_APPS",
                  hint=[a for a in settings.TENANT_APPS if a in delta],
                  id="wagtailtenant.E002"))

    if not set(settings.SHARED_APPS).issubset(INSTALLED_APPS):
        delta = set(settings.SHARED_APPS).difference(INSTALLED_APPS)
        errors.append(
            Error("You have SHARED_APPS that are not in INSTALLED_APPS",
                  hint=[a for a in settings.SHARED_APPS if a in delta],
                  id="wagtailtenant.E003"))

    if not isinstance(default_storage, TenantStorageMixin):
        errors.append(Warning(
            "Your default storage engine is not tenant aware.",
            hint="Set settings.DEFAULT_FILE_STORAGE to "
                 "'wagtail.wagtailtenant.storage.TenantFileSystemStorage'",
            id="wagtailtenant.W003"
        ))

    return errors
예제 #10
0
 def test_tenant_survives_after_method2(self):
     # The same tenant still exists even after the previous method call
     self.assertEquals(1, get_tenant_model().objects.all().count())
예제 #11
0
 def test_tenant_survives_after_method1(self):
     # There is one tenant in the database, the one created by TenantTestCase
     self.assertEquals(1, get_tenant_model().objects.all().count())