From 6637ee87441ebd6911952ea1b6745ad39b9ec2aa Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sat, 8 Feb 2014 16:58:12 -0500 Subject: [PATCH] Initial try at porting to Django 1.7 --- tenancy/__init__.py | 6 +- tenancy/management/__init__.py | 3 +- tenancy/models.py | 22 +++-- tenancy/signals.py | 8 +- tenancy/tests/models.py | 4 +- tenancy/tests/test_signals.py | 4 +- tenancy/utils.py | 150 ++++++++++++++++++++++----------- test_settings/__init__.py | 3 +- 8 files changed, 125 insertions(+), 75 deletions(-) diff --git a/tenancy/__init__.py b/tenancy/__init__.py index d885fc9..2c1fb90 100644 --- a/tenancy/__init__.py +++ b/tenancy/__init__.py @@ -6,15 +6,13 @@ def get_tenant_model(seed_cache=True): from django.core.exceptions import ImproperlyConfigured - from django.db.models import get_model from .models import AbstractTenant from .settings import TENANT_MODEL + from .utils import get_model app_label, object_name = TENANT_MODEL.split('.') model_name = object_name.lower() - tenant_model = get_model( - app_label, model_name, seed_cache=seed_cache, only_installed=False - ) + tenant_model = get_model(app_label, model_name, seed_cache=seed_cache) if tenant_model is None: raise ImproperlyConfigured( "TENANCY_TENANT_MODEL refers to model '%s.%s' that has not " diff --git a/tenancy/management/__init__.py b/tenancy/management/__init__.py index 5a95eb9..2c5e38b 100644 --- a/tenancy/management/__init__.py +++ b/tenancy/management/__init__.py @@ -15,8 +15,7 @@ from django.dispatch.dispatcher import receiver from .. import signals -from ..utils import (allow_migrate, disconnect_signals, receivers_for_model, - remove_from_app_cache) +from ..utils import allow_migrate def create_tenant_schema(tenant, using=None): diff --git a/tenancy/models.py b/tenancy/models.py index 86ed9b2..f4eeb2c 100644 --- a/tenancy/models.py +++ b/tenancy/models.py @@ -16,7 +16,6 @@ from django.db.models.deletion import DO_NOTHING from django.db.models.fields import Field from django.db.models.fields.related import add_lazy_relation -from django.db.models.loading import get_model from django.dispatch.dispatcher import receiver from django.utils.six import itervalues, string_types, with_metaclass from django.utils.six.moves import copyreg @@ -25,8 +24,8 @@ from .management import create_tenant_schema, drop_tenant_schema from .managers import (AbstractTenantManager, TenantManager, TenantModelManagerDescriptor) -from .utils import (clear_opts_related_cache, disconnect_signals, model_name, - receivers_for_model, remove_from_app_cache) +from .utils import (clear_opts_related_cache, disconnect_signals, get_model, + model_name, receivers_for_model, remove_from_apps_registry) class TenantModels(object): @@ -263,7 +262,7 @@ def __new__(cls, name, bases, attrs): through._meta.auto_created): # Replace the automatically created intermediary model # by a TenantModelBase instance. - remove_from_app_cache(through) + remove_from_apps_registry(through) # Make sure to clear the referenced model cache if # we have contributed to it already. if not isinstance(to, string_types): @@ -302,7 +301,7 @@ def validate_related_name(cls, field, rel_to, model): if (related_name is not None and not (field.rel.is_hidden() or '%(class)s' in related_name)): del cls.references[model] - remove_from_app_cache(model, quiet=True) + remove_from_apps_registry(model, quiet=True) raise ImproperlyConfigured( "Since `%s.%s` is originating from an instance " "of `TenantModelBase` and not pointing to one " @@ -322,7 +321,7 @@ def validate_through(cls, field, rel_to, model): add_lazy_relation(model, field, through, cls.validate_through) elif not isinstance(through, cls): del cls.references[model] - remove_from_app_cache(model, quiet=True) + remove_from_apps_registry(model, quiet=True) raise ImproperlyConfigured( "Since `%s.%s` is originating from an instance of " "`TenantModelBase` its `through` option must also be pointing " @@ -481,10 +480,7 @@ def for_tenant(self, tenant): name = reference.object_name_for_tenant(tenant) # Return the already cached model instead of creating a new one. - model = get_model( - opts.app_label, name.lower(), - only_installed=False - ) + model = get_model(opts.app_label, name.lower()) if model: return model @@ -494,6 +490,8 @@ def for_tenant(self, tenant): reference.Meta, # TODO: Use `db_schema` once django #6148 is fixed. db_table=db_schema_table(tenant, self._meta.db_table), + verbose_name=opts.verbose_name, + verbose_name_plural=opts.verbose_name_plural ) } @@ -532,7 +530,7 @@ def destroy(self): """ if not issubclass(self, TenantSpecificModel): raise ValueError('Can only be called on tenant specific model.') - remove_from_app_cache(self) + remove_from_apps_registry(self) if not self._meta.proxy: # Some fields (GenericForeignKey, ImageField) attach (pre|post)_init # signals to their associated model even if they are abstract. @@ -602,7 +600,7 @@ def validate_not_to_tenant_model(field, to, model): if isinstance(to, string_types): add_lazy_relation(model, field, to, validate_not_to_tenant_model) elif isinstance(to, TenantModelBase): - remove_from_app_cache(model, quiet=True) + remove_from_apps_registry(model, quiet=True) raise ImproperlyConfigured( "`%s.%s`'s `to` option` can't point to an instance of " "`TenantModelBase` since it's not one itself." % ( diff --git a/tenancy/signals.py b/tenancy/signals.py index 56cf63c..01cb6ad 100644 --- a/tenancy/signals.py +++ b/tenancy/signals.py @@ -1,18 +1,16 @@ from __future__ import unicode_literals from django.core.signals import Signal -from django.db.models.loading import get_model from django.db.models.signals import class_prepared +from .utils import get_model + def lazy_class_prepared(app_label, object_name, callback): """ Lazily execute a callback upon model class preparation. """ - model = get_model( - app_label, object_name.lower(), - seed_cache=False, only_installed=False - ) + model = get_model(app_label, object_name.lower()) if model: callback(model) else: diff --git a/tenancy/tests/models.py b/tenancy/tests/models.py index f69a2d1..1cedc77 100644 --- a/tenancy/tests/models.py +++ b/tenancy/tests/models.py @@ -75,9 +75,9 @@ class PostInitFieldsModel(TenantModel): content_object = GenericForeignKey('content_type', 'object_id') try: if django.VERSION >= (1, 6): - from django.utils.image import Image as _ + from django.utils.image import Image else: - from PIL import Image as _ + from PIL import Image except (ImportError, ImproperlyConfigured): pass else: diff --git a/tenancy/tests/test_signals.py b/tenancy/tests/test_signals.py index 05e0fc7..0cc16ce 100644 --- a/tenancy/tests/test_signals.py +++ b/tenancy/tests/test_signals.py @@ -5,7 +5,7 @@ from .. import get_tenant_model from ..signals import lazy_class_prepared -from ..utils import remove_from_app_cache +from ..utils import remove_from_apps_registry def prepare_model(): @@ -13,7 +13,7 @@ class NotPreparedYet(models.Model): class Meta: app_label = 'tenancy' managed = True - remove_from_app_cache(NotPreparedYet) + remove_from_apps_registry(NotPreparedYet) return NotPreparedYet diff --git a/tenancy/utils.py b/tenancy/utils.py index f5c5ece..765a6f2 100644 --- a/tenancy/utils.py +++ b/tenancy/utils.py @@ -1,13 +1,11 @@ from __future__ import unicode_literals -import imp from contextlib import contextmanager from itertools import chain import django from django.db import connections, models, router from django.db.models.base import ModelBase -from django.db.models.loading import cache as app_cache from django.dispatch.dispatcher import _make_id @@ -24,57 +22,115 @@ def allow_migrate(model): yield db -@contextmanager -def app_cache_lock(): - try: - imp.acquire_lock() - yield - finally: - imp.release_lock() +def _unrefer_model(model): + disconnect_signals(model) + opts = model._meta + fields_with_model = chain( + opts.get_fields_with_model(), + opts.get_m2m_with_model() + ) + for field, field_model in fields_with_model: + rel = field.rel + if field_model is None and rel: + to = rel.to + if isinstance(to, ModelBase): + clear_opts_related_cache(to) + rel_is_hidden = rel.is_hidden() + # An accessor is added to related classes if they are not + # hidden. However o2o fields *always* add an accessor + # even if the relationship is hidden. + o2o = isinstance(field, models.OneToOneField) + if not rel_is_hidden or o2o: + try: + delattr(to, field.related.get_accessor_name()) + except AttributeError: + # Hidden related names are not respected for o2o + # thus a tenant models with a o2o pointing to + # a non-tenant one would have a class for multiple + # tenant thus the attribute might be attempted + # to be deleted multiple times. + if not (o2o and rel_is_hidden): + raise -def remove_from_app_cache(model_class, quiet=False): - opts = model_class._meta - with app_cache_lock(): - app_models = app_cache.app_models.get(opts.app_label, None) - if app_models is None: +if django.VERSION >= (1, 7): + from django.apps.registry import apps + + def get_model(app_label, model_name, seed_cache=False): + try: + return apps.get_app_config(app_label).get_model(model_name) + except LookupError: + pass + + @contextmanager + def app_registry_lock(): + with apps._lock: + yield + + def _get_app_models(app_label): + return apps.get_app(app_label).models + + def _pop_from_apps_registry(app_label, model_name, quiet): + try: + app_config = apps.get_app_config(app_label) + except LookupError: + if quiet: + return + raise ValueError( + "Unregistered app %s" % app_label + ) + try: + return app_config.models.pop(model_name) + except KeyError: if quiet: return - else: - raise ValueError( - "No cached models for app %s" % opts.app_label - ) - model = app_models.pop(model_name(opts), None) - if model is None: + raise ValueError( + "%s.%s is not registered" % (app_label, model_name) + ) + +else: + import imp + from django.db.models.loading import cache + + def get_model(app_label, model_name, seed_cache=False): + return cache.get_model( + app_label, model_name, seed_cache=seed_cache, only_installed=False + ) + + @contextmanager + def app_registry_lock(): + try: + imp.acquire_lock() + yield + finally: + imp.release_lock() + + def _pop_from_apps_registry(app_label, model_name, quiet): + try: + app_models = cache.app_models[app_label] + except LookupError: + if quiet: + return + raise ValueError( + "Unregistered app %s" % app_label + ) + try: + model = app_models.pop(model_name) + except KeyError: if quiet: return - else: - raise ValueError("%r is not cached" % model_class) - app_cache._get_models_cache.clear() - disconnect_signals(model) - for field, field_model in chain(opts.get_fields_with_model(), - opts.get_m2m_with_model()): - rel = field.rel - if field_model is None and rel: - to = rel.to - if isinstance(to, ModelBase): - clear_opts_related_cache(to) - rel_is_hidden = rel.is_hidden() - # An accessor is added to related classes if they are not - # hidden. However o2o fields *always* add an accessor - # even if the relationship is hidden. - o2o = isinstance(field, models.OneToOneField) - if not rel_is_hidden or o2o: - try: - delattr(to, field.related.get_accessor_name()) - except AttributeError: - # Hidden related names are not respected for o2o - # thus a tenant models with a o2o pointing to - # a non-tenant one would have a class for multiple - # tenant thus the attribute might be attempted - # to be deleted multiple times. - if not (o2o and rel_is_hidden): - raise + raise ValueError( + "%s.%s is not registered" % (app_label, model_name) + ) + cache._get_models_cache.clear() + return model + +def remove_from_apps_registry(model_class, quiet=False): + opts = model_class._meta + with app_registry_lock(): + model = _pop_from_apps_registry(opts.app_label, model_name(opts), quiet) + if model: + _unrefer_model(model) model_sender_signals = ( diff --git a/test_settings/__init__.py b/test_settings/__init__.py index f9bf903..0b2b3b9 100644 --- a/test_settings/__init__.py +++ b/test_settings/__init__.py @@ -13,4 +13,5 @@ except ImportError: pass else: - INSTALLED_APPS.append('mutant') + pass + #INSTALLED_APPS.append('mutant')