Project

General

Profile

0005-New-TenantMiddleware-which-try-to-find-tenants-based.patch

Benjamin Dauvergne, 20 August 2014 03:23 PM

Download (4.39 KB)

View differences:

Subject: [PATCH 05/10] 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()).
 entrouvert/djommon/multitenant/middleware.py |   79 +++++++++++++++++++++-----
 1 file changed, 66 insertions(+), 13 deletions(-)
entrouvert/djommon/multitenant/middleware.py
1 1
import os
2 2
import json
3
import glob
3 4
from django.conf import settings, UserSettingsHolder
4

  
5
from tenant_schemas.middleware import TenantMiddleware
5
from django.db import connection
6
from django.http import Http404
7
from django.contrib.contenttypes.models import ContentType
8
from tenant_schemas.utils import get_tenant_model, remove_www_and_dev, get_public_schema_name
6 9

  
7 10
SENTINEL = object()
8 11

  
9
class EOTenantMiddleware(TenantMiddleware):
10
    def __init__(self, *args, **kwargs):
11
        self.wrapped = settings._wrapped
12
class TenantNotFound(RuntimeError):
13
    pass
14

  
15
class TenantMiddleware(object):
16
    """
17
    This middleware should be placed at the very top of the middleware stack.
18
    Selects the proper database schema using the request host. Can fail in
19
    various ways which is better than corrupting or revealing data...
20
    """
21
    @classmethod
22
    def base(cls):
23
        return settings.TENANT_BASE
24

  
25
    @classmethod
26
    def hostname2schema(cls, hostname):
27
        '''Convert hostname to PostgreSQL schema name'''
28
        if hostname in getattr(settings, 'TENANT_MAPPING', {}):
29
            return settings.TENANT_MAPPING[hostname]
30
        return hostname.replace('.', '_').replace('-', '_')
31

  
32
    @classmethod
33
    def get_tenant_by_hostname(cls, hostname):
34
        '''Retrieve a tenant object for this hostname'''
35
        schema = cls.hostname2schema(hostname)
36
        p = os.path.join(cls.base(), schema)
37
        if not os.path.exists(p):
38
            raise TenantNotFound
39
        return get_tenant_model()(schema_name=schema, domain_url=hostname)
40

  
41
    @classmethod
42
    def get_tenants(cls):
43
        self = cls()
44
        for path in glob.glob(os.path.join(cls.base(), '*')):
45
            hostname = os.path.basename(path)
46
            yield get_tenant_model()(
47
                    schema_name=self.hostname2schema(hostname),
48
                    domain_url=hostname)
12 49

  
13 50
    def process_request(self, request):
14
        super(EOTenantMiddleware, self).process_request(request)
15
        override = UserSettingsHolder(self.wrapped)
16
        for client_settings in request.tenant.clientsetting_set.all():
17
            setattr(override, client_settings.name, client_settings.json)
18
        settings._wrapped = override
51
        # connection needs first to be at the public schema, as this is where the
52
        # tenant informations are saved
53
        connection.set_schema_to_public()
54
        hostname_without_port = remove_www_and_dev(request.get_host().split(':')[0])
55

  
56
        try:
57
            request.tenant = self.get_tenant_by_hostname(hostname_without_port)
58
        except TenantNotFound:
59
            raise Http404
60
        connection.set_tenant(request.tenant)
61

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

  
70
        # do we have a public-specific token?
71
        if hasattr(settings, 'PUBLIC_SCHEMA_URLCONF') and request.tenant.schema_name == get_public_schema_name():
72
            request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
73

  
74

  
19 75

  
20
    def process_response(self, request, response):
21
        settings._wrapped = self.wrapped
22
        return response
23 76

  
24 77
class DictAdapter(dict):
25 78
    def __init__(self, wrapped):
26
-