PEP8 and typos
This commit is contained in:
parent
419bbd4aa1
commit
f740df449d
|
@ -17,7 +17,7 @@ A schema can be seen as a directory in an operating system, each directory (sche
|
|||
|
||||
Why schemas
|
||||
-----------
|
||||
There are typically three solutions for solving the multinancy problem.
|
||||
There are typically three solutions for solving the multitenancy problem.
|
||||
|
||||
1. Isolated Approach: Separate Databases. Each tenant has it's own database.
|
||||
|
||||
|
@ -30,7 +30,7 @@ This application implements the second approach, which in our opinion, represent
|
|||
* Simplicity: barely make any changes to your current code to support multitenancy. Plus, you only manage one database.
|
||||
* Performance: make use of shared connections, buffers and memory.
|
||||
|
||||
Each solution has it's up and down sides, for a more in-depth discussion, see Microsoft's excelent article on [Multi-Tenant Data Architecture](http://msdn.microsoft.com/en-us/library/aa479086.aspx).
|
||||
Each solution has it's up and down sides, for a more in-depth discussion, see Microsoft's excellent article on [Multi-Tenant Data Architecture](http://msdn.microsoft.com/en-us/library/aa479086.aspx).
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
|
|
@ -14,7 +14,7 @@ A schema can be seen as a directory in an operating system, each directory (sche
|
|||
|
||||
Why schemas?
|
||||
------------
|
||||
There are typically three solutions for solving the multinancy problem.
|
||||
There are typically three solutions for solving the multitenancy problem.
|
||||
|
||||
1. Isolated Approach: Separate Databases. Each tenant has it's own database.
|
||||
|
||||
|
@ -27,7 +27,7 @@ This application implements the second approach, which in our opinion, represent
|
|||
* Simplicity: barely make any changes to your current code to support multitenancy. Plus, you only manage one database.
|
||||
* Performance: make use of shared connections, buffers and memory.
|
||||
|
||||
Each solution has it's up and down sides, for a more in-depth discussion, see Microsoft's excelent article on `Multi-Tenant Data Architecture <http://msdn.microsoft.com/en-us/library/aa479086.aspx>`_.
|
||||
Each solution has it's up and down sides, for a more in-depth discussion, see Microsoft's excellent article on `Multi-Tenant Data Architecture <http://msdn.microsoft.com/en-us/library/aa479086.aspx>`_.
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
|
|
@ -9,7 +9,7 @@ Assuming you have django installed, the first step is to install ``django-tenant
|
|||
|
||||
Basic Settings
|
||||
==============
|
||||
You'll have to make the following modifcations to your ``settings.py`` file.
|
||||
You'll have to make the following modifications to your ``settings.py`` file.
|
||||
|
||||
Your ``DATABASE_ENGINE`` setting needs to be changed to
|
||||
|
||||
|
@ -153,7 +153,7 @@ We override ``south``'s ``syncdb`` and ``migrate`` command, so you'll need to ch
|
|||
|
||||
INSTALLED_APPS = SHARED_APPS + TENANT_APPS + ('tenant_schemas',)
|
||||
|
||||
This makes sure ``tenant_schemas`` is the last on the list and therefore always has precedence when running an overriden command.
|
||||
This makes sure ``tenant_schemas`` is the last on the list and therefore always has precedence when running an overridden command.
|
||||
|
||||
Optional Settings
|
||||
=================
|
||||
|
|
|
@ -59,13 +59,13 @@ You can also use the option ``--tenant`` to only sync tenant apps or ``--shared`
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
./manage.py sync_schemas --shared # will only sync the public schema
|
||||
./manage.py sync_schemas --shared # will only sync the public schema
|
||||
|
||||
We've also packed south's migrate command in a compatible way with this app. It will also respect the ``SHARED_APPS`` and ``TENANT_APPS`` settings, so if you're migrating the ``public`` schema it will only migrate ``SHARED_APPS``. If you're migrating tenants, it will only migrate ``TENANT_APPS``.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./manage.py migrate_schemas
|
||||
./manage.py migrate_schemas
|
||||
|
||||
The options given to ``migrate_schemas`` are also passed to every ``migrate``. Hence you may find handy
|
||||
|
||||
|
|
|
@ -11,12 +11,12 @@ MANAGERS = ADMINS
|
|||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'tenant_schemas.postgresql_backend', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
|
||||
'ENGINE': 'tenant_schemas.postgresql_backend', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
|
||||
'NAME': 'tenant_tutorial', # Or path to database file if using sqlite3.
|
||||
'USER': 'postgres',
|
||||
'PASSWORD': 'root',
|
||||
'HOST': 'localhost', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
|
||||
'PORT': '', # Set to empty string for default.
|
||||
'HOST': 'localhost', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
|
||||
'PORT': '', # Set to empty string for default.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ PUBLIC_SCHEMA_URLCONF = 'tenant_tutorial.urls_public'
|
|||
WSGI_APPLICATION = 'tenant_tutorial.wsgi.application'
|
||||
|
||||
import os
|
||||
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), '..', 'templates').replace('\\','/'),)
|
||||
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), '..', 'templates').replace('\\', '/'),)
|
||||
|
||||
SHARED_APPS = (
|
||||
'tenant_schemas', # mandatory
|
||||
|
|
25
setup.py
25
setup.py
|
@ -2,6 +2,7 @@
|
|||
|
||||
from os.path import exists
|
||||
from version import get_git_version
|
||||
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
|
@ -15,24 +16,24 @@ setup(
|
|||
author='Bernardo Pires Carneiro',
|
||||
author_email='carneiro.be@gmail.com',
|
||||
packages=[
|
||||
'tenant_schemas',
|
||||
'tenant_schemas.postgresql_backend',
|
||||
'tenant_schemas.management',
|
||||
'tenant_schemas.management.commands',
|
||||
'tenant_schemas.templatetags',
|
||||
'tenant_schemas.test',
|
||||
'tenant_schemas.tests',
|
||||
'tenant_schemas',
|
||||
'tenant_schemas.postgresql_backend',
|
||||
'tenant_schemas.management',
|
||||
'tenant_schemas.management.commands',
|
||||
'tenant_schemas.templatetags',
|
||||
'tenant_schemas.test',
|
||||
'tenant_schemas.tests',
|
||||
],
|
||||
scripts=[],
|
||||
url='https://github.com/bcarneiro/django-tenant-schemas',
|
||||
license='MIT',
|
||||
description='Tenant support for Django using PostgreSQL schemas.',
|
||||
long_description=open('README.markdown').read() if exists("README.markdown") else "",
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Framework :: Django',
|
||||
'Programming Language :: Python',
|
||||
],
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Framework :: Django',
|
||||
'Programming Language :: Python',
|
||||
],
|
||||
install_requires=[
|
||||
'Django >= 1.2.0',
|
||||
'psycopg2',
|
||||
|
|
|
@ -6,7 +6,7 @@ from tenant_schemas.utils import get_public_schema_name, get_tenant_model
|
|||
recommended_config = """
|
||||
Warning: You should put 'tenant_schemas' at the end of INSTALLED_APPS like this:
|
||||
INSTALLED_APPS = TENANT_APPS + SHARED_APPS + ('tenant_schemas',)
|
||||
This is neccesary to overwrite built-in django management commands with their schema-aware implementations.
|
||||
This is necessary to overwrite built-in django management commands with their schema-aware implementations.
|
||||
"""
|
||||
# Make a bunch of tests for configuration recommendations
|
||||
# These are best practices basically, to avoid hard to find bugs, unexpected behaviour
|
||||
|
@ -25,8 +25,8 @@ if hasattr(settings, 'PG_EXTRA_SEARCH_PATHS'):
|
|||
|
||||
# make sure no tenant schema is in settings.PG_EXTRA_SEARCH_PATHS
|
||||
invalid_schemas = set(settings.PG_EXTRA_SEARCH_PATHS).intersection(
|
||||
get_tenant_model().objects.all().values_list('schema_name', flat=True))
|
||||
get_tenant_model().objects.all().values_list('schema_name', flat=True))
|
||||
if invalid_schemas:
|
||||
raise ImproperlyConfigured("Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS."
|
||||
raise ImproperlyConfigured("Do not include tenant schemas (%s) on PG_EXTRA_SEARCH_PATHS."
|
||||
% list(invalid_schemas))
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.conf import settings
|
|||
from django.core.management import call_command, get_commands, load_command_class
|
||||
from django.core.management.base import BaseCommand, NoArgsCommand, CommandError
|
||||
from django.db import connection
|
||||
|
||||
try:
|
||||
from django.utils.six.moves import input
|
||||
except ImportError:
|
||||
|
@ -16,6 +17,7 @@ class BaseTenantCommand(BaseCommand):
|
|||
over all schemata. The actual command name is expected in the
|
||||
class variable COMMAND_NAME of the subclass.
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
Sets option_list and help dynamically.
|
||||
|
@ -39,7 +41,7 @@ class BaseTenantCommand(BaseCommand):
|
|||
)
|
||||
|
||||
# prepend the command's original help with the info about schemata iteration
|
||||
obj.help = "Calls %s for all registered schemata. You can use regular %s options. "\
|
||||
obj.help = "Calls %s for all registered schemata. You can use regular %s options. " \
|
||||
"Original help for %s: %s" % (obj.COMMAND_NAME, obj.COMMAND_NAME, obj.COMMAND_NAME,
|
||||
getattr(cmdclass, 'help', 'none'))
|
||||
return obj
|
||||
|
@ -49,9 +51,9 @@ class BaseTenantCommand(BaseCommand):
|
|||
|
||||
if verbosity >= 1:
|
||||
print()
|
||||
print(self.style.NOTICE("=== Switching to schema '") \
|
||||
+ self.style.SQL_TABLE(tenant.schema_name)\
|
||||
+ self.style.NOTICE("' then calling %s:" % command_name))
|
||||
print(self.style.NOTICE("=== Switching to schema '")
|
||||
+ self.style.SQL_TABLE(tenant.schema_name)
|
||||
+ self.style.NOTICE("' then calling %s:" % command_name))
|
||||
|
||||
connection.set_tenant(tenant)
|
||||
|
||||
|
@ -65,10 +67,11 @@ class BaseTenantCommand(BaseCommand):
|
|||
if options['schema_name']:
|
||||
# only run on a particular schema
|
||||
connection.set_schema_to_public()
|
||||
self.execute_command(get_tenant_model().objects.get(schema_name=options['schema_name']), self.COMMAND_NAME, *args, **options)
|
||||
self.execute_command(get_tenant_model().objects.get(schema_name=options['schema_name']), self.COMMAND_NAME,
|
||||
*args, **options)
|
||||
else:
|
||||
for tenant in get_tenant_model().objects.all():
|
||||
if not(options['skip_public'] and tenant.schema_name == get_public_schema_name()):
|
||||
if not (options['skip_public'] and tenant.schema_name == get_public_schema_name()):
|
||||
self.execute_command(tenant, self.COMMAND_NAME, *args, **options)
|
||||
|
||||
|
||||
|
@ -110,6 +113,7 @@ class TenantWrappedCommand(InteractiveTenantOption, BaseCommand):
|
|||
on a particular tenant. The actual command name is expected in the
|
||||
class variable COMMAND_NAME of the subclass.
|
||||
"""
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
obj = super(TenantWrappedCommand, cls).__new__(cls, *args, **kwargs)
|
||||
obj.command_instance = obj.COMMAND()
|
||||
|
|
|
@ -11,7 +11,7 @@ class Command(InteractiveTenantOption, BaseCommand):
|
|||
def run_from_argv(self, argv):
|
||||
"""
|
||||
Changes the option_list to use the options from the wrapped command.
|
||||
Adds schema parameter to specifiy which schema will be used when
|
||||
Adds schema parameter to specify which schema will be used when
|
||||
executing the wrapped command.
|
||||
"""
|
||||
# load the command object.
|
||||
|
|
|
@ -19,7 +19,7 @@ class TenantMiddleware(object):
|
|||
|
||||
def process_request(self, request):
|
||||
# connection needs first to be at the public schema, as this is where the
|
||||
# tenant informations are saved
|
||||
# tenant metadata is stored
|
||||
connection.set_schema_to_public()
|
||||
hostname = self.hostname_from_request(request)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class TenantMixin(models.Model):
|
|||
# removed after tenant remove.
|
||||
|
||||
auto_create_schema = True # set this flag to false on a parent class if
|
||||
# you dont want the schema to be automatically
|
||||
# you don't want the schema to be automatically
|
||||
# created upon save.
|
||||
|
||||
domain_url = models.CharField(max_length=128, unique=True)
|
||||
|
|
|
@ -41,7 +41,6 @@ class DatabaseWrapper(original_backend.DatabaseWrapper):
|
|||
self.set_settings_schema(self.schema_name)
|
||||
self.search_path_set = False
|
||||
|
||||
|
||||
def set_schema(self, schema_name, include_public=True):
|
||||
"""
|
||||
Main API method to current database schema,
|
||||
|
|
|
@ -12,6 +12,7 @@ from django.utils.encoding import force_bytes
|
|||
from django.utils._os import safe_join
|
||||
from django.db import connection
|
||||
|
||||
|
||||
class CachedLoader(BaseLoader):
|
||||
is_usable = True
|
||||
|
||||
|
@ -49,7 +50,8 @@ class CachedLoader(BaseLoader):
|
|||
if template_dirs:
|
||||
# If template directories were specified, use a hash to differentiate
|
||||
if connection.tenant:
|
||||
key = '-'.join([str(connection.tenant.pk), template_name, hashlib.sha1(force_bytes('|'.join(template_dirs))).hexdigest()])
|
||||
key = '-'.join([str(connection.tenant.pk), template_name,
|
||||
hashlib.sha1(force_bytes('|'.join(template_dirs))).hexdigest()])
|
||||
else:
|
||||
key = '-'.join([template_name, hashlib.sha1(force_bytes('|'.join(template_dirs))).hexdigest()])
|
||||
|
||||
|
@ -71,6 +73,7 @@ class CachedLoader(BaseLoader):
|
|||
"Empty the template cache."
|
||||
self.template_cache.clear()
|
||||
|
||||
|
||||
class FilesystemLoader(BaseLoader):
|
||||
is_usable = True
|
||||
|
||||
|
@ -86,7 +89,8 @@ class FilesystemLoader(BaseLoader):
|
|||
try:
|
||||
template_dirs = settings.MULTITENANT_TEMPLATE_DIRS
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured('To use %s.%s you must define the MULTITENANT_TEMPLATE_DIRS' % (__name__, FilesystemLoader.__name__))
|
||||
raise ImproperlyConfigured('To use %s.%s you must define the MULTITENANT_TEMPLATE_DIRS' %
|
||||
(__name__, FilesystemLoader.__name__))
|
||||
for template_dir in template_dirs:
|
||||
try:
|
||||
yield safe_join(template_dir, connection.tenant.domain_url, template_name)
|
||||
|
|
|
@ -22,7 +22,7 @@ class DummyModel(models.Model):
|
|||
Just a test model so we can test manipulating data
|
||||
inside a tenant
|
||||
"""
|
||||
name = models.CharField(max_length=1337) # every dummy should have a pretty name :)
|
||||
name = models.CharField(max_length=1337) # every dummy should have a pretty name :)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
|
|
@ -7,7 +7,7 @@ from ..utils import get_public_schema_name
|
|||
|
||||
|
||||
class BaseTestCase(TransactionTestCase):
|
||||
""" Base testcase that comes packed with overloaded INSTALLED_APPS,
|
||||
""" Base test case that comes packed with overloaded INSTALLED_APPS,
|
||||
custom public tenant, and schemas cleanup on tearDown.
|
||||
"""
|
||||
@classmethod
|
||||
|
|
|
@ -38,6 +38,7 @@ def get_tenant_model():
|
|||
def get_public_schema_name():
|
||||
return getattr(settings, 'PUBLIC_SCHEMA_NAME', 'public')
|
||||
|
||||
|
||||
def get_limit_set_calls():
|
||||
return getattr(settings, 'TENANT_LIMIT_SET_CALLS', False)
|
||||
|
||||
|
|
Loading…
Reference in New Issue