#216 Allow ContentType queries to be performed on non-default databases.

fix_request_path_info
Austin Matsick 2016-05-27 20:35:39 -05:00
parent 1c4facef4e
commit 343aa41ec1
3 changed files with 27 additions and 22 deletions

View File

@ -16,6 +16,7 @@ Please see LICENSE and AUTHORS for more information.
from __future__ import absolute_import from __future__ import absolute_import
from django.db import models from django.db import models
from django.db.utils import DEFAULT_DB_ALIAS
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.utils import six 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): def translate_polymorphic_Q_object(self_class, q):
return 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. """Normally not needed.
This function may be called manually in special use-cases. When the object 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. 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) (used by PolymorphicQuerySet._get_real_instances)
""" """
if not self.polymorphic_ctype_id: 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 pre_save_polymorphic.alters_data = True
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
"""Overridden model save function which supports the polymorphism """Overridden model save function which supports the polymorphism
functionality (through pre_save_polymorphic).""" 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) return super(PolymorphicModel, self).save(*args, **kwargs)
save.alters_data = True save.alters_data = True
def get_real_instance_class(self): def get_real_instance_class(self, using=DEFAULT_DB_ALIAS):
""" """
Normally not needed. Normally not needed.
If a non-polymorphic manager (like base_objects) has been used to 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; # 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. # when the content type record still exists but no longer refers to an existing model.
try: 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: except AttributeError:
# Django <1.6 workaround # Django <1.6 workaround
return None return None
@ -118,19 +120,19 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
)) ))
return 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() model_class = self.get_real_instance_class()
if model_class is None: if model_class is None:
return 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() model_class = self.get_real_instance_class()
if model_class is None: if model_class is None:
return 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. """Normally not needed.
If a non-polymorphic manager (like base_objects) has been used to If a non-polymorphic manager (like base_objects) has been used to
retrieve objects, then the complete object with it's real class/type 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() real_model = self.get_real_instance_class()
if real_model == self.__class__: if real_model == self.__class__:
return self 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): def __init__(self, * args, ** kwargs):
"""Replace Django's inheritance accessor member functions for our model """Replace Django's inheritance accessor member functions for our model

View File

@ -309,8 +309,9 @@ class PolymorphicQuerySet(QuerySet):
# - also record the correct result order in "ordered_id_list" # - also record the correct result order in "ordered_id_list"
# - store objects that already have the correct class into "results" # - store objects that already have the correct class into "results"
base_result_objects_by_id = {} base_result_objects_by_id = {}
self_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=False).pk content_type_manager = ContentType.objects.db_manager(self._db)
self_concrete_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=True).pk 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: for base_object in base_result_objects:
ordered_id_list.append(base_object.pk) ordered_id_list.append(base_object.pk)
@ -335,7 +336,7 @@ class PolymorphicQuerySet(QuerySet):
# upcast it and put it in the results # upcast it and put it in the results
results[base_object.pk] = transmogrify(real_concrete_class, base_object) results[base_object.pk] = transmogrify(real_concrete_class, base_object)
else: 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)) 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) # For each model in "idlist_per_model" request its objects (the real model)

View File

@ -8,6 +8,7 @@ import django
from django.db import models from django.db import models
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Q, FieldDoesNotExist from django.db.models import Q, FieldDoesNotExist
from django.db.utils import DEFAULT_DB_ALIAS
from django.db.models.fields.related import RelatedField from django.db.models.fields.related import RelatedField
if django.VERSION < (1, 6): 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. Returns: a list of non-keyword-arguments (Q objects) to be added to the filter() query.
""" """
additional_args = [] additional_args = []
using = kwargs.get('using', DEFAULT_DB_ALIAS)
for field_path, val in kwargs.copy().items(): # Python 3 needs copy 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: if type(new_expr) == tuple:
# replace kwargs element # replace kwargs element
@ -66,7 +68,7 @@ def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs):
return additional_args 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): def tree_node_correct_field_specs(my_model, node):
" process all children of this Q node " " process all children of this Q node "
for i in range(len(node.children)): 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: if type(child) == tuple:
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB ) # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
key, val = child 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: if new_expr:
node.children[i] = new_expr node.children[i] = new_expr
else: else:
@ -105,7 +107,7 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args):
translate_polymorphic_Q_object(queryset_model, q) 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 Translate a keyword argument (field_path=field_val), as used for
PolymorphicQuerySet.filter()-like functions (and Q objects). 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, # handle instance_of expressions or alternatively,
# if this is a normal Django filter expression, return None # if this is a normal Django filter expression, return None
if field_path == 'instance_of': 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': 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: elif not '___' in field_path:
return None # no change return None # no change
@ -229,7 +231,7 @@ def translate_polymorphic_field_path(queryset_model, field_path):
return newpath 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 Helper function for instance_of / not_instance_of
Creates and returns a Q object that filters for the models in modellist, 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' assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
def q_class_with_subclasses(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__(): for subclass in model.__subclasses__():
q = q | q_class_with_subclasses(subclass) q = q | q_class_with_subclasses(subclass)
return q return q