#216 Allow ContentType queries to be performed on non-default databases.
parent
1c4facef4e
commit
343aa41ec1
|
|
@ -16,6 +16,7 @@ Please see LICENSE and AUTHORS for more information.
|
|||
from __future__ import absolute_import
|
||||
|
||||
from django.db import models
|
||||
from django.db.utils import DEFAULT_DB_ALIAS
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import six
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
|||
def translate_polymorphic_Q_object(self_class, q):
|
||||
return translate_polymorphic_Q_object(self_class, q)
|
||||
|
||||
def pre_save_polymorphic(self):
|
||||
def pre_save_polymorphic(self, using=DEFAULT_DB_ALIAS):
|
||||
"""Normally not needed.
|
||||
This function may be called manually in special use-cases. When the object
|
||||
is saved for the first time, we store its real class in polymorphic_ctype.
|
||||
|
|
@ -80,17 +81,18 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
|||
(used by PolymorphicQuerySet._get_real_instances)
|
||||
"""
|
||||
if not self.polymorphic_ctype_id:
|
||||
self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False)
|
||||
self.polymorphic_ctype = ContentType.objects.db_manager(using).get_for_model(self, for_concrete_model=False)
|
||||
pre_save_polymorphic.alters_data = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Overridden model save function which supports the polymorphism
|
||||
functionality (through pre_save_polymorphic)."""
|
||||
self.pre_save_polymorphic()
|
||||
using = kwargs.get('using', DEFAULT_DB_ALIAS)
|
||||
self.pre_save_polymorphic(using=using)
|
||||
return super(PolymorphicModel, self).save(*args, **kwargs)
|
||||
save.alters_data = True
|
||||
|
||||
def get_real_instance_class(self):
|
||||
def get_real_instance_class(self, using=DEFAULT_DB_ALIAS):
|
||||
"""
|
||||
Normally not needed.
|
||||
If a non-polymorphic manager (like base_objects) has been used to
|
||||
|
|
@ -103,7 +105,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
|||
# Note that model_class() can return None for stale content types;
|
||||
# when the content type record still exists but no longer refers to an existing model.
|
||||
try:
|
||||
model = ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class()
|
||||
model = ContentType.objects.db_manager(using).get_for_id(self.polymorphic_ctype_id).model_class()
|
||||
except AttributeError:
|
||||
# Django <1.6 workaround
|
||||
return None
|
||||
|
|
@ -118,19 +120,19 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
|||
))
|
||||
return model
|
||||
|
||||
def get_real_concrete_instance_class_id(self):
|
||||
def get_real_concrete_instance_class_id(self, using=DEFAULT_DB_ALIAS):
|
||||
model_class = self.get_real_instance_class()
|
||||
if model_class is None:
|
||||
return None
|
||||
return ContentType.objects.get_for_model(model_class, for_concrete_model=True).pk
|
||||
return ContentType.objects.db_manager(using).get_for_model(model_class, for_concrete_model=True).pk
|
||||
|
||||
def get_real_concrete_instance_class(self):
|
||||
def get_real_concrete_instance_class(self, using=DEFAULT_DB_ALIAS):
|
||||
model_class = self.get_real_instance_class()
|
||||
if model_class is None:
|
||||
return None
|
||||
return ContentType.objects.get_for_model(model_class, for_concrete_model=True).model_class()
|
||||
return ContentType.objects.db_manager(using).get_for_model(model_class, for_concrete_model=True).model_class()
|
||||
|
||||
def get_real_instance(self):
|
||||
def get_real_instance(self, using=DEFAULT_DB_ALIAS):
|
||||
"""Normally not needed.
|
||||
If a non-polymorphic manager (like base_objects) has been used to
|
||||
retrieve objects, then the complete object with it's real class/type
|
||||
|
|
@ -139,7 +141,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
|||
real_model = self.get_real_instance_class()
|
||||
if real_model == self.__class__:
|
||||
return self
|
||||
return real_model.objects.get(pk=self.pk)
|
||||
return real_model.objects.db_manager(using).get(pk=self.pk)
|
||||
|
||||
def __init__(self, * args, ** kwargs):
|
||||
"""Replace Django's inheritance accessor member functions for our model
|
||||
|
|
|
|||
|
|
@ -309,8 +309,9 @@ class PolymorphicQuerySet(QuerySet):
|
|||
# - also record the correct result order in "ordered_id_list"
|
||||
# - store objects that already have the correct class into "results"
|
||||
base_result_objects_by_id = {}
|
||||
self_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=False).pk
|
||||
self_concrete_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=True).pk
|
||||
content_type_manager = ContentType.objects.db_manager(self._db)
|
||||
self_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=False).pk
|
||||
self_concrete_model_class_id = content_type_manager.get_for_model(self.model, for_concrete_model=True).pk
|
||||
|
||||
for base_object in base_result_objects:
|
||||
ordered_id_list.append(base_object.pk)
|
||||
|
|
@ -335,7 +336,7 @@ class PolymorphicQuerySet(QuerySet):
|
|||
# upcast it and put it in the results
|
||||
results[base_object.pk] = transmogrify(real_concrete_class, base_object)
|
||||
else:
|
||||
real_concrete_class = ContentType.objects.get_for_id(real_concrete_class_id).model_class()
|
||||
real_concrete_class = content_type_manager.get_for_id(real_concrete_class_id).model_class()
|
||||
idlist_per_model[real_concrete_class].append(getattr(base_object, pk_name))
|
||||
|
||||
# For each model in "idlist_per_model" request its objects (the real model)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import django
|
|||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q, FieldDoesNotExist
|
||||
from django.db.utils import DEFAULT_DB_ALIAS
|
||||
|
||||
from django.db.models.fields.related import RelatedField
|
||||
if django.VERSION < (1, 6):
|
||||
|
|
@ -50,9 +51,10 @@ def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs):
|
|||
Returns: a list of non-keyword-arguments (Q objects) to be added to the filter() query.
|
||||
"""
|
||||
additional_args = []
|
||||
using = kwargs.get('using', DEFAULT_DB_ALIAS)
|
||||
for field_path, val in kwargs.copy().items(): # Python 3 needs copy
|
||||
|
||||
new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val)
|
||||
new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val, using=using)
|
||||
|
||||
if type(new_expr) == tuple:
|
||||
# replace kwargs element
|
||||
|
|
@ -66,7 +68,7 @@ def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs):
|
|||
return additional_args
|
||||
|
||||
|
||||
def translate_polymorphic_Q_object(queryset_model, potential_q_object):
|
||||
def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEFAULT_DB_ALIAS):
|
||||
def tree_node_correct_field_specs(my_model, node):
|
||||
" process all children of this Q node "
|
||||
for i in range(len(node.children)):
|
||||
|
|
@ -75,7 +77,7 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object):
|
|||
if type(child) == tuple:
|
||||
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
||||
key, val = child
|
||||
new_expr = _translate_polymorphic_filter_definition(my_model, key, val)
|
||||
new_expr = _translate_polymorphic_filter_definition(my_model, key, val, using=using)
|
||||
if new_expr:
|
||||
node.children[i] = new_expr
|
||||
else:
|
||||
|
|
@ -105,7 +107,7 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args):
|
|||
translate_polymorphic_Q_object(queryset_model, q)
|
||||
|
||||
|
||||
def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val):
|
||||
def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS):
|
||||
"""
|
||||
Translate a keyword argument (field_path=field_val), as used for
|
||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||
|
|
@ -120,9 +122,9 @@ def _translate_polymorphic_filter_definition(queryset_model, field_path, field_v
|
|||
# handle instance_of expressions or alternatively,
|
||||
# if this is a normal Django filter expression, return None
|
||||
if field_path == 'instance_of':
|
||||
return _create_model_filter_Q(field_val)
|
||||
return _create_model_filter_Q(field_val, using=using)
|
||||
elif field_path == 'not_instance_of':
|
||||
return _create_model_filter_Q(field_val, not_instance_of=True)
|
||||
return _create_model_filter_Q(field_val, not_instance_of=True, using=using)
|
||||
elif not '___' in field_path:
|
||||
return None # no change
|
||||
|
||||
|
|
@ -229,7 +231,7 @@ def translate_polymorphic_field_path(queryset_model, field_path):
|
|||
return newpath
|
||||
|
||||
|
||||
def _create_model_filter_Q(modellist, not_instance_of=False):
|
||||
def _create_model_filter_Q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS):
|
||||
"""
|
||||
Helper function for instance_of / not_instance_of
|
||||
Creates and returns a Q object that filters for the models in modellist,
|
||||
|
|
@ -254,7 +256,7 @@ def _create_model_filter_Q(modellist, not_instance_of=False):
|
|||
assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
|
||||
|
||||
def q_class_with_subclasses(model):
|
||||
q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model, for_concrete_model=False))
|
||||
q = Q(polymorphic_ctype=ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False))
|
||||
for subclass in model.__subclasses__():
|
||||
q = q | q_class_with_subclasses(subclass)
|
||||
return q
|
||||
|
|
|
|||
Loading…
Reference in New Issue