Skip to content

Commit

Permalink
Merge pull request #108 from mateuspadua/master
Browse files Browse the repository at this point in the history
Specializing static and media based on tenants
  • Loading branch information
tomturner committed Nov 27, 2016
2 parents 3822ea0 + 7286eb5 commit edc9e33
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 4 deletions.
1 change: 0 additions & 1 deletion django_tenants/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def ready(self):
if not hasattr(settings, 'TENANT_MODEL'):
raise ImproperlyConfigured('TENANT_MODEL setting not set')


if 'django_tenants.routers.TenantSyncRouter' not in settings.DATABASE_ROUTERS:
raise ImproperlyConfigured("DATABASE_ROUTERS setting must contain "
"'django_tenants.routers.TenantSyncRouter'.")
Expand Down
Empty file.
79 changes: 79 additions & 0 deletions django_tenants/files/storages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import os
from django.utils._os import safe_join
from django.db import connection
from django.core.files.storage import FileSystemStorage
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from django.utils.encoding import filepath_to_uri
from django.utils.six.moves.urllib.parse import urljoin


__all__ = (
'TenantStorageMixin',
'TenantFileSystemStorage',
)


class TenantStorageMixin(object):

def path(self, name):
"""
To static_files is the destination path to collectstatic
To media_files is the destination path to upload files
"""
if name is None:
name = ''
try:
if '%s' in self.location:
location = safe_join(self.location % connection.schema_name)
else:
location = safe_join(self.location, connection.schema_name)
except AttributeError:
location = self.location

path = safe_join(location, name)
return path


class TenantFileSystemStorage(TenantStorageMixin, FileSystemStorage):
"""
Implementation that extends core Django's FileSystemStorage.
"""

def __init__(self, location=None, base_url=None, *args, **kwargs):
super(TenantFileSystemStorage, self).__init__(location, base_url, *args, **kwargs)
if hasattr(settings, "MULTITENANT_RELATIVE_MEDIA_ROOT") and \
settings.MULTITENANT_RELATIVE_MEDIA_ROOT:
self.location = os.path.join(self.location,
settings.MULTITENANT_RELATIVE_MEDIA_ROOT)

relative_base_url = settings.MULTITENANT_RELATIVE_MEDIA_ROOT
if not relative_base_url.endswith('/'):
relative_base_url += '/'
self.base_url += relative_base_url

"""
def path(self, name):
if not hasattr(settings, "MULTITENANT_RELATIVE_MEDIA_ROOT") or \
not settings.MULTITENANT_RELATIVE_MEDIA_ROOT:
raise ImproperlyConfigured("You're using the TenantFileSystemStorage "
"without having set the MULTITENANT_RELATIVE_MEDIA_ROOT "
"setting to a relative filesystem path as from MEDIA_ROOT.")
return super(TenantFileSystemStorage, self).path(name)
"""

def url(self, name):
if self.base_url is None:
raise ValueError("This file is not accessible via a URL.")

try:
if '%s' in self.base_url:
base_url = self.base_url % connection.schema_name
else:
base_url = u"{0}{1}/".format(self.base_url, connection.schema_name)
except AttributeError:
base_url = self.base_url

return urljoin(base_url, filepath_to_uri(name))
7 changes: 7 additions & 0 deletions django_tenants/management/commands/collectstatic_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import TenantWrappedCommand
from django.contrib.staticfiles.management.commands import collectstatic


class Command(TenantWrappedCommand):
COMMAND = collectstatic.Command
13 changes: 13 additions & 0 deletions django_tenants/management/commands/compress_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os
from . import TenantWrappedCommand
from compressor.management.commands import compress
from django.conf import settings


class Command(TenantWrappedCommand):
COMMAND = compress.Command

def get_tenant_from_options_or_interactive(self, **options):
r = super(Command, self).get_tenant_from_options_or_interactive(**options)
settings.COMPRESS_OUTPUT_DIR = os.path.join(options['schema_name'], "CACHE")
return r
Empty file.
117 changes: 117 additions & 0 deletions django_tenants/staticfiles/finders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
import os
from django.contrib.staticfiles.finders import FileSystemFinder
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.db import connection
from collections import OrderedDict
from django.core.exceptions import ImproperlyConfigured
from django_tenants.utils import get_tenant_model


class TenantFileSystemFinder(FileSystemFinder):
def __init__(self, app_names=None, *args, **kwargs):

self.locations = []
self.storages = OrderedDict()

TenantModel = get_tenant_model()
all_tenants = TenantModel.objects.values_list('schema_name', flat=True)
# print "=========== all_tenants ============"
# print all_tenants

try:
CURRENT_SCHEMA_TO_SERVER_STATICFILES = \
settings.CURRENT_SCHEMA_TO_SERVER_STATICFILES
except AttributeError:
raise ImproperlyConfigured('To use %s.%s you must '
'define the CURRENT_SCHEMA_TO_SERVER_STATICFILES '
'in your settings' %
(__name__, TenantFileSystemFinder.__name__))

if not CURRENT_SCHEMA_TO_SERVER_STATICFILES:
raise ImproperlyConfigured(
"Your CURRENT_SCHEMA_TO_SERVER_STATICFILES setting can't be empty; "
"it must have the value of the schema_name in which you are "
"currently working on")
else:
if CURRENT_SCHEMA_TO_SERVER_STATICFILES not in all_tenants:
raise ImproperlyConfigured(
"The value of CURRENT_SCHEMA_TO_SERVER_STATICFILES setting "
"doesnt correspond to a valid schema_name tentant")

schema_name = connection.schema_name if connection.schema_name != "public" \
else CURRENT_SCHEMA_TO_SERVER_STATICFILES

"""
if not hasattr(settings, "MULTITENANT_RELATIVE_STATIC_ROOT") or \
not settings.MULTITENANT_RELATIVE_STATIC_ROOT:
raise ImproperlyConfigured("You're using the TenantStaticFilesStorage "
"without having set the MULTITENANT_RELATIVE_STATIC_ROOT "
"setting to a filesystem path.")
"""

multitenant_relative_static_root = ""
if hasattr(settings, "MULTITENANT_RELATIVE_STATIC_ROOT"):
if '%s' in settings.MULTITENANT_RELATIVE_STATIC_ROOT:
multitenant_relative_static_root = \
settings.MULTITENANT_RELATIVE_STATIC_ROOT % \
schema_name
else:
multitenant_relative_static_root = \
os.path.join(settings.MULTITENANT_RELATIVE_STATIC_ROOT,
schema_name)
else:
multitenant_relative_static_root = schema_name

multitenant_static_root = os.path.join(settings.STATIC_ROOT,
multitenant_relative_static_root)

# print "multitenant_relative_static_root"
# print multitenant_relative_static_root
# print "multitenant_static_root"
# print multitenant_static_root

try:
multitenant_staticfiles_dirs = settings.MULTITENANT_STATICFILES_DIRS
except AttributeError:
raise ImproperlyConfigured('To use %s.%s you must '
'define the MULTITENANT_STATICFILES_DIRS '
'in your settings' %
(__name__, TenantFileSystemFinder.__name__))

if not isinstance(multitenant_staticfiles_dirs, (list, tuple)):
raise ImproperlyConfigured(
"Your MULTITENANT_STATICFILES_DIRS setting is not a tuple or list; "
"perhaps you forgot a trailing comma?")

tenant_paths = []
for template_dir in multitenant_staticfiles_dirs:
if '%s' in template_dir:
tenant_paths.append(template_dir % schema_name)
else:
tenant_paths.append(os.path.join(template_dir, schema_name))

dirs = tenant_paths

for root in dirs:
if isinstance(root, (list, tuple)):
prefix, root = root
else:
prefix = ''

# print "=========================================="
# print os.path.abspath(multitenant_static_root)
# print os.path.abspath(root)

if os.path.abspath(multitenant_static_root) == os.path.abspath(root):
raise ImproperlyConfigured(
"The MULTITENANT_STATICFILES_DIRS setting should "
"not contain the (STATIC_ROOT + MULTITENANT_RELATIVE_STATIC_ROOT) path")
if (prefix, root) not in self.locations:
self.locations.append((prefix, root))

for prefix, root in self.locations:
filesystem_storage = FileSystemStorage(location=root)
filesystem_storage.prefix = prefix
self.storages[root] = filesystem_storage
26 changes: 26 additions & 0 deletions django_tenants/staticfiles/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import os
from django.contrib.staticfiles.storage import StaticFilesStorage
from django_tenants.files.storages import TenantStorageMixin
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured


class TenantStaticFilesStorage(TenantStorageMixin, StaticFilesStorage):
"""
Implementation that extends core Django's StaticFilesStorage.
"""

def __init__(self, location=None, base_url=None, *args, **kwargs):
super(TenantStaticFilesStorage, self).__init__(location, base_url, *args, **kwargs)
if hasattr(settings, "MULTITENANT_RELATIVE_STATIC_ROOT"):
self.location = os.path.join(self.location, settings.MULTITENANT_RELATIVE_STATIC_ROOT)

def path(self, name):
"""
if not hasattr(settings, "MULTITENANT_RELATIVE_STATIC_ROOT") or \
not settings.MULTITENANT_RELATIVE_STATIC_ROOT:
raise ImproperlyConfigured("You're using the TenantStaticFilesStorage "
"without having set the MULTITENANT_RELATIVE_STATIC_ROOT "
"setting to a filesystem path.")
"""
return super(TenantStaticFilesStorage, self).path(name)
6 changes: 3 additions & 3 deletions django_tenants/template_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import hashlib
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import ImproperlyConfigured, SuspiciousFileOperation
from django.template.base import Template
from django.utils.encoding import force_bytes
from django.utils._os import safe_join
Expand All @@ -21,7 +21,6 @@
from django.template.exceptions import TemplateDoesNotExist



class CachedLoader(BaseLoader):
is_usable = True

Expand Down Expand Up @@ -112,6 +111,7 @@ def get_template_sources(template_name, template_dirs=None):
except AttributeError:
raise ImproperlyConfigured('To use %s.%s you must define the MULTITENANT_TEMPLATE_DIRS' %
(__name__, FilesystemLoader.__name__))

for template_dir in template_dirs:
try:
if '%s' in template_dir:
Expand All @@ -121,7 +121,7 @@ def get_template_sources(template_name, template_dirs=None):
except UnicodeDecodeError:
# The template dir name was a bytestring that wasn't valid UTF-8.
raise
except ValueError:
except (SuspiciousFileOperation, ValueError):
# The joined path was located outside of this particular
# template_dir (it might be inside another one, so this isn't
# fatal).
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Contents
use
examples
templates
static
test
involved
credits
Loading

0 comments on commit edc9e33

Please sign in to comment.