Revision aae80ef0
Added by Benjamin Dauvergne over 10 years ago
entrouvert/djommon/multitenant/middleware.py | ||
---|---|---|
1 | 1 |
import os |
2 | 2 |
import json |
3 |
import glob |
|
3 | 4 |
|
4 | 5 |
from django.conf import settings, UserSettingsHolder |
5 |
|
|
6 |
from tenant_schemas.middleware import TenantMiddleware |
|
6 |
from django.db import connection |
|
7 |
from django.http import Http404 |
|
8 |
from django.contrib.contenttypes.models import ContentType |
|
9 |
from tenant_schemas.utils import get_tenant_model, remove_www_and_dev, get_public_schema_name |
|
7 | 10 |
|
8 | 11 |
SENTINEL = object() |
9 | 12 |
|
10 |
class EOTenantMiddleware(TenantMiddleware): |
|
11 |
def __init__(self, *args, **kwargs): |
|
12 |
self.wrapped = settings._wrapped |
|
13 |
class TenantNotFound(RuntimeError): |
|
14 |
pass |
|
15 |
|
|
16 |
class TenantMiddleware(object): |
|
17 |
""" |
|
18 |
This middleware should be placed at the very top of the middleware stack. |
|
19 |
Selects the proper database schema using the request host. Can fail in |
|
20 |
various ways which is better than corrupting or revealing data... |
|
21 |
""" |
|
22 |
@classmethod |
|
23 |
def base(cls): |
|
24 |
return settings.TENANT_BASE |
|
25 |
|
|
26 |
@classmethod |
|
27 |
def hostname2schema(cls, hostname): |
|
28 |
'''Convert hostname to PostgreSQL schema name''' |
|
29 |
if hostname in getattr(settings, 'TENANT_MAPPING', {}): |
|
30 |
return settings.TENANT_MAPPING[hostname] |
|
31 |
return hostname.replace('.', '_').replace('-', '_') |
|
32 |
|
|
33 |
@classmethod |
|
34 |
def get_tenant_by_hostname(cls, hostname): |
|
35 |
'''Retrieve a tenant object for this hostname''' |
|
36 |
schema = cls.hostname2schema(hostname) |
|
37 |
p = os.path.join(cls.base(), schema) |
|
38 |
if not os.path.exists(p): |
|
39 |
raise TenantNotFound |
|
40 |
return get_tenant_model()(schema_name=schema, domain_url=hostname) |
|
41 |
|
|
42 |
@classmethod |
|
43 |
def get_tenants(cls): |
|
44 |
self = cls() |
|
45 |
for path in glob.glob(os.path.join(cls.base(), '*')): |
|
46 |
hostname = os.path.basename(path) |
|
47 |
yield get_tenant_model()( |
|
48 |
schema_name=self.hostname2schema(hostname), |
|
49 |
domain_url=hostname) |
|
13 | 50 |
|
14 | 51 |
def process_request(self, request): |
15 |
super(EOTenantMiddleware, self).process_request(request) |
|
16 |
override = UserSettingsHolder(self.wrapped) |
|
17 |
for client_settings in request.tenant.clientsetting_set.all(): |
|
18 |
setattr(override, client_settings.name, client_settings.json) |
|
19 |
settings._wrapped = override |
|
52 |
# connection needs first to be at the public schema, as this is where the |
|
53 |
# tenant informations are saved |
|
54 |
connection.set_schema_to_public() |
|
55 |
hostname_without_port = remove_www_and_dev(request.get_host().split(':')[0]) |
|
56 |
|
|
57 |
try: |
|
58 |
request.tenant = self.get_tenant_by_hostname(hostname_without_port) |
|
59 |
except TenantNotFound: |
|
60 |
raise Http404 |
|
61 |
connection.set_tenant(request.tenant) |
|
62 |
|
|
63 |
# content type can no longer be cached as public and tenant schemas have different |
|
64 |
# models. if someone wants to change this, the cache needs to be separated between |
|
65 |
# public and shared schemas. if this cache isn't cleared, this can cause permission |
|
66 |
# problems. for example, on public, a particular model has id 14, but on the tenants |
|
67 |
# it has the id 15. if 14 is cached instead of 15, the permissions for the wrong |
|
68 |
# model will be fetched. |
|
69 |
ContentType.objects.clear_cache() |
|
70 |
|
|
71 |
# do we have a public-specific token? |
|
72 |
if hasattr(settings, 'PUBLIC_SCHEMA_URLCONF') and request.tenant.schema_name == get_public_schema_name(): |
|
73 |
request.urlconf = settings.PUBLIC_SCHEMA_URLCONF |
|
74 |
|
|
75 |
|
|
20 | 76 |
|
21 |
def process_response(self, request, response): |
|
22 |
settings._wrapped = self.wrapped |
|
23 |
return response |
|
24 | 77 |
|
25 | 78 |
class TenantSettingBaseMiddleware(object): |
26 | 79 |
'''Base middleware classe for loading settings based on tenants |
Also available in: Unified diff
New TenantMiddleware which try to find tenants based on the filesystem
If path <settings.TENANT_BASE>/<hostname>/schema exists, read this file an build
tenant modle with Tenant(domain_url=<hostname>, schema_name=file(<path>).read()).
refs #5106