#216 Use self._state.db instead of `using` kwarg in model methods.

Thanks to @vdboor for the suggestion.

Also:
- Add missing `using` kwargs in query_translate functions.
- Add a couple unit tests for non-default database functionality.
fix_request_path_info
Austin Matsick 2016-05-30 18:55:03 -05:00
parent 2f11cb6ffd
commit 4aece2b5d3
5 changed files with 66 additions and 22 deletions

View File

@ -87,12 +87,12 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
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)."""
using = kwargs.get('using', DEFAULT_DB_ALIAS) using = kwargs.get('using', self._state.db or DEFAULT_DB_ALIAS)
self.pre_save_polymorphic(using=using) 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, using=DEFAULT_DB_ALIAS): def get_real_instance_class(self):
""" """
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
@ -105,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.db_manager(using).get_for_id(self.polymorphic_ctype_id).model_class() model = ContentType.objects.db_manager(self._state.db).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
@ -120,28 +120,28 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
)) ))
return model return model
def get_real_concrete_instance_class_id(self, using=DEFAULT_DB_ALIAS): def get_real_concrete_instance_class_id(self):
model_class = self.get_real_instance_class(using=using) model_class = self.get_real_instance_class()
if model_class is None: if model_class is None:
return None return None
return ContentType.objects.db_manager(using).get_for_model(model_class, for_concrete_model=True).pk return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).pk
def get_real_concrete_instance_class(self, using=DEFAULT_DB_ALIAS): def get_real_concrete_instance_class(self):
model_class = self.get_real_instance_class(using=using) model_class = self.get_real_instance_class()
if model_class is None: if model_class is None:
return None return None
return ContentType.objects.db_manager(using).get_for_model(model_class, for_concrete_model=True).model_class() return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).model_class()
def get_real_instance(self, using=DEFAULT_DB_ALIAS): def get_real_instance(self):
"""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
and all fields may be retrieved with this method. and all fields may be retrieved with this method.
Each method call executes one db query (if necessary).""" Each method call executes one db query (if necessary)."""
real_model = self.get_real_instance_class(using=using) 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.db_manager(using).get(pk=self.pk) return real_model.objects.db_manager(self._state.db).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

@ -112,8 +112,8 @@ class PolymorphicQuerySet(QuerySet):
def _filter_or_exclude(self, negate, *args, **kwargs): def _filter_or_exclude(self, negate, *args, **kwargs):
"We override this internal Django functon as it is used for all filter member functions." "We override this internal Django functon as it is used for all filter member functions."
translate_polymorphic_filter_definitions_in_args(self.model, args) # the Q objects translate_polymorphic_filter_definitions_in_args(self.model, args, using=self._db) # the Q objects
additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs) # filter_field='data' additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs, using=self._db) # filter_field='data'
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs) return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
def order_by(self, *args, **kwargs): def order_by(self, *args, **kwargs):
@ -325,8 +325,8 @@ class PolymorphicQuerySet(QuerySet):
results[base_object.pk] = base_object results[base_object.pk] = base_object
else: else:
real_concrete_class = base_object.get_real_instance_class(using=self._db) real_concrete_class = base_object.get_real_instance_class()
real_concrete_class_id = base_object.get_real_concrete_instance_class_id(using=self._db) real_concrete_class_id = base_object.get_real_concrete_instance_class_id()
if real_concrete_class_id is None: if real_concrete_class_id is None:
# Dealing with a stale content type # Dealing with a stale content type
@ -345,7 +345,7 @@ class PolymorphicQuerySet(QuerySet):
# Then we copy the extra() select fields from the base objects to the real objects. # Then we copy the extra() select fields from the base objects to the real objects.
# TODO: defer(), only(): support for these would be around here # TODO: defer(), only(): support for these would be around here
for real_concrete_class, idlist in idlist_per_model.items(): for real_concrete_class, idlist in idlist_per_model.items():
real_objects = real_concrete_class.base_objects.filter(**{ real_objects = real_concrete_class.base_objects.db_manager(self._db).filter(**{
('%s__in' % pk_name): idlist, ('%s__in' % pk_name): idlist,
}) })
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
@ -372,7 +372,7 @@ class PolymorphicQuerySet(QuerySet):
for real_object in real_objects: for real_object in real_objects:
o_pk = getattr(real_object, pk_name) o_pk = getattr(real_object, pk_name)
real_class = real_object.get_real_instance_class(using=self._db) real_class = real_object.get_real_instance_class()
# If the real class is a proxy, upcast it # If the real class is a proxy, upcast it
if real_class != real_concrete_class: if real_class != real_concrete_class:

View File

@ -36,7 +36,7 @@ from functools import reduce
# functionality to filters and Q objects. # functionality to filters and Q objects.
# Probably a more general queryset enhancement class could be made out of them. # Probably a more general queryset enhancement class could be made out of them.
def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs): def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs, using=DEFAULT_DB_ALIAS):
""" """
Translate the keyword argument list for PolymorphicQuerySet.filter() Translate the keyword argument list for PolymorphicQuerySet.filter()
@ -51,7 +51,6 @@ 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, using=using) new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val, using=using)
@ -90,7 +89,7 @@ def translate_polymorphic_Q_object(queryset_model, potential_q_object, using=DEF
return potential_q_object return potential_q_object
def translate_polymorphic_filter_definitions_in_args(queryset_model, args): def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using=DEFAULT_DB_ALIAS):
""" """
Translate the non-keyword argument list for PolymorphicQuerySet.filter() Translate the non-keyword argument list for PolymorphicQuerySet.filter()
@ -104,7 +103,7 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args):
""" """
for q in args: for q in args:
translate_polymorphic_Q_object(queryset_model, q) translate_polymorphic_Q_object(queryset_model, q, using=using)
def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS): def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS):

View File

@ -430,6 +430,8 @@ class PolymorphicTests(TestCase):
The test suite The test suite
""" """
multi_db = True
def test_annotate_aggregate_order(self): def test_annotate_aggregate_order(self):
# create a blog of type BlogA # create a blog of type BlogA
# create two blog entries in BlogA # create two blog entries in BlogA
@ -1162,6 +1164,45 @@ class PolymorphicTests(TestCase):
result = DateModel.objects.annotate(val=DateTime('date', 'day', utc)) result = DateModel.objects.annotate(val=DateTime('date', 'day', utc))
self.assertEqual(list(result), []) self.assertEqual(list(result), [])
def test_save_to_non_default_database(self):
Model2A.objects.db_manager('secondary').create(field1='A1')
Model2C(field1='C1', field2='C2', field3='C3').save(using='secondary')
Model2B.objects.create(field1='B1', field2='B2')
Model2D(field1='D1', field2='D2', field3='D3', field4='D4').save('secondary')
default_objects = list(Model2A.objects.order_by('id'))
self.assertEqual(len(default_objects), 2)
self.assertEqual(repr(default_objects[0]), '<Model2B: id 1, field1 (CharField), field2 (CharField)>')
self.assertEqual(repr(default_objects[1]), '<Model2D: id 2, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
secondary_objects = list(Model2A.objects.db_manager('secondary').order_by('id'))
self.assertEqual(len(secondary_objects), 2)
self.assertEqual(repr(secondary_objects[0]), '<Model2A: id 1, field1 (CharField)>')
self.assertEqual(repr(secondary_objects[1]), '<Model2C: id 2, field1 (CharField), field2 (CharField), field3 (CharField)>')
def test_instance_of_filter_on_non_default_database(self):
Base.objects.db_manager('secondary').create(field_b='B1')
ModelX.objects.db_manager('secondary').create(field_b='B', field_x='X')
ModelY.objects.db_manager('secondary').create(field_b='Y', field_y='Y')
objects = Base.objects.db_manager('secondary').filter(instance_of=Base)
self.assertEqual(len(objects), 3)
self.assertEqual(repr(objects[0]), '<Base: id 1, field_b (CharField)>')
self.assertEqual(repr(objects[1]), '<ModelX: id 2, field_b (CharField), field_x (CharField)>')
self.assertEqual(repr(objects[2]), '<ModelY: id 3, field_b (CharField), field_y (CharField)>')
objects = Base.objects.db_manager('secondary').filter(instance_of=ModelX)
self.assertEqual(len(objects), 1)
self.assertEqual(repr(objects[0]), '<ModelX: id 2, field_b (CharField), field_x (CharField)>')
objects = Base.objects.db_manager('secondary').filter(instance_of=ModelY)
self.assertEqual(len(objects), 1)
self.assertEqual(repr(objects[0]), '<ModelY: id 3, field_b (CharField), field_y (CharField)>')
objects = Base.objects.db_manager('secondary').filter(Q(instance_of=ModelX) | Q(instance_of=ModelY))
self.assertEqual(len(objects), 2)
self.assertEqual(repr(objects[0]), '<ModelX: id 2, field_b (CharField), field_x (CharField)>')
self.assertEqual(repr(objects[1]), '<ModelY: id 3, field_b (CharField), field_y (CharField)>')
class RegressionTests(TestCase): class RegressionTests(TestCase):

View File

@ -25,6 +25,10 @@ if not settings.configured:
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:' 'NAME': ':memory:'
},
'secondary': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:'
} }
}, },
TEMPLATE_LOADERS=( TEMPLATE_LOADERS=(