Using PUBLIC_SCHEMA_URLCONF in django-tenant-schemas

Viewed 1930

I am trying to have django-tenant-schemas re-route users if they visit the base domain using the optional setting PUBLIC_SCHEMA_URLCONF. Whenever I visit the base url I get this response:

enter image description here

I'm hoping someone can tell me what the value of PUBLIC_SCHEMA_URLCONF should be based on my project structure or if anything else might be wrong. I want to use the urls from public_website when people try to access the base domain.

My project directory looks like this:

website
 ├──approvals
 ├──batches
 ├──customauth
 ├──email_portal
 ├──exports
 ├──file_downloads
 ├──password_reset
 ├──payroll
 ├──payroll_codes
 ├──reports
 ├──scheduling
 ├──shifts
 ├──static
 ├──templates
 ├──website
 |   ├──migrations
 |   ├──static
 |   ├──templates
 |   └──settings
 |       ├──__init__.py
 |       ├──base.py
 |       ├──prod.py
 |       └──dev.py
 ├──scheduling
 ├──public_website
 |   ├──__init__.py
 |   └──urls.py
 └──manage.py

And I want PUBLIC_SCHEMA_URLCONF to refer to the urls in public_website, which look like:

from django.conf.urls import include, url

import website.views as website_views
from django.contrib import admin
from django.http import HttpResponse

url_patterns = [
    url(r'^$', lambda request: HttpResponse('ok')),
    url(r'^admin/login/', website_views.Login.as_view()),
    url(r'^admin/', include(admin.site.urls))  # user authentication urls
]

Here are the relevant bits in my settings:

DJANGO_APPS = (
    'jet',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',
    'django.contrib.admin',
    'django_user_agents',
    'django_ses',
    # 'admin_reorder'
)

MY_APPS_WITH_MODELS = (
    'customauth',
    'payroll_codes',
    'scheduling',
    'payroll',
    'shifts',
    'email_portal',
    'tutor_training_tracker'
)

MY_APPS_WITHOUT_MODELS = (
    'exports',
    'reports',
    'file_downloads',
    'batches',
    'approvals'
)

SHARED_APPS = (
    'tenant_schemas',
    'website',
    'public_website'
)
TENANT_APPS = DJANGO_APPS + MY_APPS_WITH_MODELS
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + DJANGO_APPS + MY_APPS_WITHOUT_MODELS + MY_APPS_WITH_MODELS))
ROOT_URLCONF = 'website.urls'
PUBLIC_SCHEMA_URLCONF = 'public_website.urls'
TENANT_MODEL = "website.Client"
DEFAULT_FILE_STORAGE = "tenant_schemas.storage.TenantFileSystemStorage"

Any help would be greatly appreciated.

Solution:

(Thanks to Thomas for writing the middleware for me)

Add custom middleware class:

from django.conf import settings
from django.db import connection
from tenant_schemas.middleware import MIDDLEWARE_MIXIN
from tenant_schemas.utils import remove_www, get_tenant_model, get_public_schema_name


class CustomTenantMiddleware(MIDDLEWARE_MIXIN):
    def get_tenant(self, model, hostname, request):
        return model.objects.get(domain_url=hostname)

    def hostname_from_request(self, request):
        """ Extracts hostname from request. Used for custom requests filtering.
           By default removes the request's port and common prefixes.
       """
        return remove_www(request.get_host().split(':')[0]).lower()

    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)
            request.tenant = tenant
            connection.set_tenant(request.tenant)

        except (TenantModel.DoesNotExist, AssertionError):
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
            request.public_tenant = True
            return

        if hasattr(settings, 'PUBLIC_SCHEMA_URLCONF') and request.tenant.schema_name == get_public_schema_name():
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF

and reference it in settings:

MIDDLEWARE_CLASSES = (
    # 'tenant_schemas.middleware.TenantMiddleware',
    'website.middleware.CustomTenantMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django_user_agents.middleware.UserAgentMiddleware',
    'admin_reorder.middleware.ModelAdminReorder',
    'website.middleware.CustomTenantMiddleware',
    # 'djadmin.middleware.DJMiddleware',
)
2 Answers

Make sure you have installed latest version of django-tenants and these settings are set in settings.py:

SHOW_PUBLIC_IF_NO_TENANT_FOUND = True # I wonder why this setting is not meintioned in docs.

PUBLIC_SCHEMA_URLCONF = 'project_name.urls_public'

Related