Routing
Routing is the process of deciding to which tenant an incoming request belongs, and activating it for the rest of the request/response cycle. This is typically done via middleware and this package provides three routing mechanisms: domain, header and session routing.
The goal of these middleware is to augment the request object with tenant information. When these middleware are used the request will contain a tenant property with an instance of either the tenant model or the class django_pgschemas.schema.Schema.
Domain routing¶
Tenants will have one or many domains (or subdomains), but each domain will correspond to only one tenant.
In this mechanism we use a database table to control domains per tenant. This domain model can be defined like this:
from django_pgschemas.routing.models import DomainModel
class Domain(DomainModel):
pass
And added to the tenant settings like this:
TENANTS = {
"public": {
"APPS": [
"django.contrib.contenttypes",
"django.contrib.staticfiles",
"django_pgschemas",
"tenants",
],
},
"www": {
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"main",
],
"DOMAINS": ["mydomain.com"],
"URLCONF": "main.urls",
},
"blog": {
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"blog",
],
"DOMAINS": ["blog.mydomain.com", "help.mydomain.com"],
"URLCONF": "blog.urls",
},
"default": {
"TENANT_MODEL": "tenants.Tenant",
"DOMAIN_MODEL": "tenants.Domain",
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"customers",
],
"URLCONF": "customers.urls",
}
}
Then you can assign domains to tenants:
>>> from tenants.models import Tenant, Domain
>>> tenant = Tenant.objects.create(schema_name="tenant_1")
>>> Domain.objects.create(tenant=tenant, domain="tenant1.mydomain.com")
Note
Notice that the public schema doesn't have DOMAINS configured. This is intentional. Attempting to add this key would result in an ImproperlyConfigured error. The public schema is non-routable by design.
Finally add DomainRoutingMiddleware to the top of the middleware stack, so that all subsequent middleware can benefit from the added tenant.
MIDDLEWARE = (
"django_pgschemas.routing.middleware.DomainRoutingMiddleware",
# other middleware
)
Subfolder routing¶
It is also possible to use subfolder routing, instead of using domains/subdomains. In this case all tenants would share the same domain, but with a different "folder" component at the beginning of the requested path. The domain model supports this by default and allows for multiple combinations:
>>> from tenants.models import Tenant, Domain
>>> tenant = Tenant.objects.create(schema_name="tenant_1")
>>> Domain.objects.create(
... tenant=tenant,
... domain="tenant1.mydomain.com",
... is_primary=True,
... )
>>> Domain.objects.create(
... tenant=tenant,
... domain="tenants.mydomain.com",
... folder="tenant1",
... )
Warning
Subfolder routing is currently not supported for static tenants.
For a special case with subfolder routing please see fallback domains.
Header routing¶
In this mechanism a request header is defined to pass the tenant database ID or the schema name.
Warning
Header routing is an unauthenticated tenant switch. Anyone who can set
PGSCHEMAS_TENANT_HEADER can activate that tenant. Only use this behind a
trusted gateway (for example mTLS, private network, or an API gateway that
strips/overrides the header). Never expose it as a public client-controlled
header on the open internet.
Static tenants can be routed using the HEADER key in the TENANTS settings:
TENANTS = {
"public": {
"APPS": [
"django.contrib.contenttypes",
"django.contrib.staticfiles",
"django_pgschemas",
"tenants",
],
},
"www": {
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"main",
],
"HEADER": "main",
"URLCONF": "main.urls",
},
"blog": {
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"blog",
],
"HEADER": "blog",
"URLCONF": "blog.urls",
},
"default": {
"TENANT_MODEL": "tenants.Tenant",
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"customers",
],
"URLCONF": "customers.urls",
}
}
Note
Notice that the public schema doesn't have HEADER configured. This is intentional. Attempting to add this key would result in an ImproperlyConfigured error. The public schema is non-routable by design.
Then add HeadersRoutingMiddleware to the top of the middleware stack.
MIDDLEWARE = (
"django_pgschemas.routing.middleware.HeadersRoutingMiddleware",
# other middleware
)
Session routing¶
In this mechanism a session key is defined to store the tenant database ID or the schema name.
Warning
Session routing trusts whatever value is already stored in the session. If
tenants are bound to authenticated users, place
SessionRoutingMiddleware after both SessionMiddleware and
AuthenticationMiddleware, and only write the tenant session key from
code that has already authorized that user for that tenant.
Static tenants can be routed using the SESSION_KEY key in the TENANTS settings:
TENANTS = {
"public": {
"APPS": [
"django.contrib.contenttypes",
"django.contrib.staticfiles",
"django_pgschemas",
"tenants",
],
},
"www": {
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"main",
],
"SESSION_KEY": "main",
"URLCONF": "main.urls",
},
"blog": {
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"blog",
],
"SESSION_KEY": "blog",
"URLCONF": "blog.urls",
},
"default": {
"TENANT_MODEL": "tenants.Tenant",
"APPS": [
"django.contrib.auth",
"django.contrib.sessions",
"customers",
],
"URLCONF": "customers.urls",
}
}
Note
Notice that the public schema doesn't have SESSION_KEY configured. This is intentional. Attempting to add this key would result in an ImproperlyConfigured error. The public schema is non-routable by design.
Then add SessionRoutingMiddleware to the top of the middleware stack, but after the session middleware.