Skip to content

Commit

Permalink
Initial try at porting to Django 1.7
Browse files Browse the repository at this point in the history
  • Loading branch information
charettes committed Sep 17, 2014
1 parent 78b6ad6 commit 6637ee8
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 75 deletions.
6 changes: 2 additions & 4 deletions tenancy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
3 changes: 1 addition & 2 deletions tenancy/management/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
22 changes: 10 additions & 12 deletions tenancy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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 "
Expand All @@ -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 "
Expand Down Expand Up @@ -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

Expand All @@ -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
)
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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." % (
Expand Down
8 changes: 3 additions & 5 deletions tenancy/signals.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
4 changes: 2 additions & 2 deletions tenancy/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions tenancy/tests/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

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():
class NotPreparedYet(models.Model):
class Meta:
app_label = 'tenancy'
managed = True
remove_from_app_cache(NotPreparedYet)
remove_from_apps_registry(NotPreparedYet)
return NotPreparedYet


Expand Down
150 changes: 103 additions & 47 deletions tenancy/utils.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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 = (
Expand Down
3 changes: 2 additions & 1 deletion test_settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
except ImportError:
pass
else:
INSTALLED_APPS.append('mutant')
pass
#INSTALLED_APPS.append('mutant')

0 comments on commit 6637ee8

Please sign in to comment.