Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specializing static and media based on tenants #108

Merged
merged 6 commits into from
Nov 27, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
86 changes: 86 additions & 0 deletions django_tenants/files/storages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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"):
self.location = os.path.join(self.location,
settings.MULTITENANT_RELATIVE_MEDIA_ROOT)

if hasattr(settings, "MULTITENANT_RELATIVE_MEDIA_URL") and \
settings.MULTITENANT_RELATIVE_MEDIA_URL:
relative_base_url = settings.MULTITENANT_RELATIVE_MEDIA_URL
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.")

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

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