Project

General

Profile

« Previous | Next » 

Revision aae80ef0

Added by Benjamin Dauvergne over 10 years ago

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

View differences:

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