Revision ccec1ff4
Added by Benjamin Dauvergne about 11 years ago
| entrouvert/djommon/multitenant/management/commands/__init__.py | ||
|---|---|---|
| 1 |
# this file derive from django-tenant-schemas |
|
| 2 |
# Author: Bernardo Pires Carneiro |
|
| 3 |
# Email: carneiro.be@gmail.com |
|
| 4 |
# License: MIT license |
|
| 5 |
# Home-page: http://github.com/bcarneiro/django-tenant-schemas |
|
| 6 |
from optparse import make_option |
|
| 7 |
from django.conf import settings |
|
| 8 |
from django.core.management import call_command, get_commands, load_command_class |
|
| 9 |
from django.core.management.base import BaseCommand, CommandError |
|
| 10 |
from django.db import connection |
|
| 11 |
try: |
|
| 12 |
from django.utils.six.moves import input |
|
| 13 |
except ImportError: |
|
| 14 |
input = raw_input |
|
| 15 |
from tenant_schemas.utils import get_public_schema_name |
|
| 16 |
from entrouvert.djommon.multitenant.middleware import TenantMiddleware |
|
| 17 |
|
|
| 18 |
|
|
| 19 |
class BaseTenantCommand(BaseCommand): |
|
| 20 |
""" |
|
| 21 |
Generic command class useful for iterating any existing command |
|
| 22 |
over all schemata. The actual command name is expected in the |
|
| 23 |
class variable COMMAND_NAME of the subclass. |
|
| 24 |
""" |
|
| 25 |
def __new__(cls, *args, **kwargs): |
|
| 26 |
""" |
|
| 27 |
Sets option_list and help dynamically. |
|
| 28 |
""" |
|
| 29 |
obj = super(BaseTenantCommand, cls).__new__(cls, *args, **kwargs) |
|
| 30 |
|
|
| 31 |
app_name = get_commands()[obj.COMMAND_NAME] |
|
| 32 |
if isinstance(app_name, BaseCommand): |
|
| 33 |
# If the command is already loaded, use it directly. |
|
| 34 |
cmdclass = app_name |
|
| 35 |
else: |
|
| 36 |
cmdclass = load_command_class(app_name, obj.COMMAND_NAME) |
|
| 37 |
|
|
| 38 |
# inherit the options from the original command |
|
| 39 |
obj.option_list = cmdclass.option_list |
|
| 40 |
obj.option_list += ( |
|
| 41 |
make_option("-d", "--domain", dest="domain"),
|
|
| 42 |
) |
|
| 43 |
obj.option_list += ( |
|
| 44 |
make_option("-p", "--skip-public", dest="skip_public", action="store_true", default=False),
|
|
| 45 |
) |
|
| 46 |
|
|
| 47 |
# prepend the command's original help with the info about schemata iteration |
|
| 48 |
obj.help = "Calls %s for all registered schemata. You can use regular %s options. "\ |
|
| 49 |
"Original help for %s: %s" % (obj.COMMAND_NAME, obj.COMMAND_NAME, obj.COMMAND_NAME, |
|
| 50 |
getattr(cmdclass, 'help', 'none')) |
|
| 51 |
return obj |
|
| 52 |
|
|
| 53 |
def execute_command(self, tenant, command_name, *args, **options): |
|
| 54 |
verbosity = int(options.get('verbosity'))
|
|
| 55 |
|
|
| 56 |
if verbosity >= 1: |
|
| 57 |
print() |
|
| 58 |
print(self.style.NOTICE("=== Switching to schema '") \
|
|
| 59 |
+ self.style.SQL_TABLE(tenant.schema_name)\ |
|
| 60 |
+ self.style.NOTICE("' then calling %s:" % command_name))
|
|
| 61 |
|
|
| 62 |
connection.set_tenant(tenant) |
|
| 63 |
|
|
| 64 |
# call the original command with the args it knows |
|
| 65 |
call_command(command_name, *args, **options) |
|
| 66 |
|
|
| 67 |
def handle(self, *args, **options): |
|
| 68 |
""" |
|
| 69 |
Iterates a command over all registered schemata. |
|
| 70 |
""" |
|
| 71 |
if options['domain']: |
|
| 72 |
# only run on a particular schema |
|
| 73 |
connection.set_schema_to_public() |
|
| 74 |
self.execute_command(TenantMiddleware.get_tenant_by_hostname(options['domain']), self.COMMAND_NAME, *args, **options) |
|
| 75 |
else: |
|
| 76 |
for tenant in TenantMiddleware.get_tenants(): |
|
| 77 |
if not(options['skip_public'] and tenant.schema_name == get_public_schema_name()): |
|
| 78 |
self.execute_command(tenant, self.COMMAND_NAME, *args, **options) |
|
| 79 |
|
|
| 80 |
|
|
| 81 |
class InteractiveTenantOption(object): |
|
| 82 |
def __init__(self, *args, **kwargs): |
|
| 83 |
super(InteractiveTenantOption, self).__init__(*args, **kwargs) |
|
| 84 |
self.option_list += ( |
|
| 85 |
make_option("-d", "--domain", dest="domain", help="specify tenant domain"),
|
|
| 86 |
) |
|
| 87 |
|
|
| 88 |
def get_tenant_from_options_or_interactive(self, **options): |
|
| 89 |
all_tenants = list(TenantMiddleware.get_tenants()) |
|
| 90 |
|
|
| 91 |
if not all_tenants: |
|
| 92 |
raise CommandError("""There are no tenants in the system.
|
|
| 93 |
To learn how create a tenant, see: |
|
| 94 |
https://django-tenant-schemas.readthedocs.org/en/latest/use.html#creating-a-tenant""") |
|
| 95 |
|
|
| 96 |
if options.get('domain'):
|
|
| 97 |
tenant_schema = options['domain'] |
|
| 98 |
else: |
|
| 99 |
while True: |
|
| 100 |
tenant_schema = input("Enter Tenant Domain ('?' to list schemas): ")
|
|
| 101 |
if tenant_schema == '?': |
|
| 102 |
print('\n'.join(["%s - %s" % (t.schema_name, t.domain_url,) for t in all_tenants]))
|
|
| 103 |
else: |
|
| 104 |
break |
|
| 105 |
|
|
| 106 |
if tenant_schema not in [t.schema_name for t in all_tenants]: |
|
| 107 |
raise CommandError("Invalid tenant schema, '%s'" % (tenant_schema,))
|
|
| 108 |
|
|
| 109 |
return TenantMiddleware.get_tenant_by_hostname(tenant_schema) |
|
| 110 |
|
|
| 111 |
|
|
| 112 |
class TenantWrappedCommand(InteractiveTenantOption, BaseCommand): |
|
| 113 |
""" |
|
| 114 |
Generic command class useful for running any existing command |
|
| 115 |
on a particular tenant. The actual command name is expected in the |
|
| 116 |
class variable COMMAND_NAME of the subclass. |
|
| 117 |
""" |
|
| 118 |
def __new__(cls, *args, **kwargs): |
|
| 119 |
obj = super(TenantWrappedCommand, cls).__new__(cls, *args, **kwargs) |
|
| 120 |
obj.command_instance = obj.COMMAND() |
|
| 121 |
obj.option_list = obj.command_instance.option_list |
|
| 122 |
return obj |
|
| 123 |
|
|
| 124 |
def handle(self, *args, **options): |
|
| 125 |
tenant = self.get_tenant_from_options_or_interactive(**options) |
|
| 126 |
connection.set_tenant(tenant) |
|
| 127 |
|
|
| 128 |
self.command_instance.execute(*args, **options) |
|
| 129 |
|
|
| 130 |
|
|
| 131 |
class SyncCommon(BaseCommand): |
|
| 132 |
option_list = ( |
|
| 133 |
make_option('--tenant', action='store_true', dest='tenant', default=False,
|
|
| 134 |
help='Tells Django to populate only tenant applications.'), |
|
| 135 |
make_option('--shared', action='store_true', dest='shared', default=False,
|
|
| 136 |
help='Tells Django to populate only shared applications.'), |
|
| 137 |
make_option("-d", "--domain", dest="domain"),
|
|
| 138 |
) |
|
| 139 |
|
|
| 140 |
def handle(self, *args, **options): |
|
| 141 |
self.sync_tenant = options.get('tenant')
|
|
| 142 |
self.sync_public = options.get('shared')
|
|
| 143 |
self.domain = options.get('domain')
|
|
| 144 |
self.installed_apps = settings.INSTALLED_APPS |
|
| 145 |
self.args = args |
|
| 146 |
self.options = options |
|
| 147 |
|
|
| 148 |
if self.schema_name: |
|
| 149 |
if self.sync_public: |
|
| 150 |
raise CommandError("schema should only be used with the --tenant switch.")
|
|
| 151 |
elif self.schema_name == get_public_schema_name(): |
|
| 152 |
self.sync_public = True |
|
| 153 |
else: |
|
| 154 |
self.sync_tenant = True |
|
| 155 |
elif not self.sync_public and not self.sync_tenant: |
|
| 156 |
# no options set, sync both |
|
| 157 |
self.sync_tenant = True |
|
| 158 |
self.sync_public = True |
|
| 159 |
|
|
| 160 |
if hasattr(settings, 'TENANT_APPS'): |
|
| 161 |
self.tenant_apps = settings.TENANT_APPS |
|
| 162 |
if hasattr(settings, 'SHARED_APPS'): |
|
| 163 |
self.shared_apps = settings.SHARED_APPS |
|
| 164 |
|
|
| 165 |
def _notice(self, output): |
|
| 166 |
self.stdout.write(self.style.NOTICE(output)) |
|
Also available in: Unified diff
Import django-tenant-schemas commands to adapt them to our way of managing tenants
refs #5106