From 6aca03c24d769bafc8f04100a33f7fa2371a7d04 Mon Sep 17 00:00:00 2001
From: Alex Gaynor
Date: Sat, 6 Jun 2009 18:01:43 +0000
Subject: [PATCH] [soc2009/multidb] Merged up to trunk r10931. Resolved the
slight merge conflict
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@10933 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
.../admin/templates/admin/pagination.html | 2 +-
django/db/models/query.py | 33 ++++++-
django/db/models/sql/query.py | 16 +++-
tests/modeltests/defer/models.py | 92 ++++++++++++++++++-
tests/regressiontests/defer_regress/models.py | 22 ++++-
tests/regressiontests/queries/models.py | 27 ++++++
6 files changed, 185 insertions(+), 7 deletions(-)
diff --git a/django/contrib/admin/templates/admin/pagination.html b/django/contrib/admin/templates/admin/pagination.html
index aaba97fdb7..358813290c 100644
--- a/django/contrib/admin/templates/admin/pagination.html
+++ b/django/contrib/admin/templates/admin/pagination.html
@@ -8,5 +8,5 @@
{% endif %}
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
{% if show_all_url %} {% trans 'Show all' %}{% endif %}
-{% if cl.formset and cl.result_count %}{% endif %}
+{% if cl.formset and cl.result_count %}{% endif %}
diff --git a/django/db/models/query.py b/django/db/models/query.py
index a4fd172ac2..89768ef023 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -7,6 +7,8 @@ try:
except NameError:
from sets import Set as set # Python 2.3 fallback
+from copy import deepcopy
+
from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
from django.db.models.aggregates import Aggregate
from django.db.models.fields import DateField
@@ -43,6 +45,17 @@ class QuerySet(object):
# PYTHON MAGIC METHODS #
########################
+ def __deepcopy__(self, memo):
+ """
+ Deep copy of a QuerySet doesn't populate the cache
+ """
+ obj_dict = deepcopy(self.__dict__, memo)
+ obj_dict['_iter'] = None
+
+ obj = self.__class__()
+ obj.__dict__.update(obj_dict)
+ return obj
+
def __getstate__(self):
"""
Allows the QuerySet to be pickled.
@@ -193,7 +206,25 @@ class QuerySet(object):
index_start = len(extra_select)
aggregate_start = index_start + len(self.model._meta.fields)
- load_fields = only_load.get(self.model)
+ load_fields = []
+ # If only/defer clauses have been specified,
+ # build the list of fields that are to be loaded.
+ if only_load:
+ for field, model in self.model._meta.get_fields_with_model():
+ if model is None:
+ model = self.model
+ if field == self.model._meta.pk:
+ # Record the index of the primary key when it is found
+ pk_idx = len(load_fields)
+ try:
+ if field.name in only_load[model]:
+ # Add a field that has been explicitly included
+ load_fields.append(field.name)
+ except KeyError:
+ # Model wasn't explicitly listed in the only_load table
+ # Therefore, we need to load all fields from this model
+ load_fields.append(field.name)
+
skip = None
if load_fields and not fill_cache:
# Some fields have been deferred, so we have to initialise
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 02c3c732e5..2033bee371 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -635,10 +635,10 @@ class BaseQuery(object):
# models.
workset = {}
for model, values in seen.iteritems():
- for field, f_model in model._meta.get_fields_with_model():
+ for field in model._meta.local_fields:
if field in values:
continue
- add_to_dict(workset, f_model or model, field)
+ add_to_dict(workset, model, field)
for model, values in must_include.iteritems():
# If we haven't included a model in workset, we don't add the
# corresponding must_include fields for that model, since an
@@ -657,6 +657,12 @@ class BaseQuery(object):
# included any fields, we have to make sure it's mentioned
# so that only the "must include" fields are pulled in.
seen[model] = values
+ # Now ensure that every model in the inheritance chain is mentioned
+ # in the parent list. Again, it must be mentioned to ensure that
+ # only "must include" fields are pulled in.
+ for model in orig_opts.get_parent_list():
+ if model not in seen:
+ seen[model] = set()
for model, values in seen.iteritems():
callback(target, model, values)
@@ -1619,10 +1625,14 @@ class BaseQuery(object):
entry.negate()
self.where.add(entry, AND)
break
- elif not (lookup_type == 'in' and not value) and field.null:
+ elif not (lookup_type == 'in'
+ and not hasattr(value, 'as_sql')
+ and not hasattr(value, '_as_sql')
+ and not value) and field.null:
# Leaky abstraction artifact: We have to specifically
# exclude the "foo__in=[]" case from this handling, because
# it's short-circuited in the Where class.
+ # We also need to handle the case where a subquery is provided
entry = self.where_class()
entry.add((Constraint(alias, col, None), 'isnull', True), AND)
entry.negate()
diff --git a/tests/modeltests/defer/models.py b/tests/modeltests/defer/models.py
index ce65065d40..96eb427811 100644
--- a/tests/modeltests/defer/models.py
+++ b/tests/modeltests/defer/models.py
@@ -17,6 +17,12 @@ class Primary(models.Model):
def __unicode__(self):
return self.name
+class Child(Primary):
+ pass
+
+class BigChild(Primary):
+ other = models.CharField(max_length=50)
+
def count_delayed_fields(obj, debug=False):
"""
Returns the number of delayed attributes on the given model instance.
@@ -33,7 +39,7 @@ def count_delayed_fields(obj, debug=False):
__test__ = {"API_TEST": """
To all outward appearances, instances with deferred fields look the same as
-normal instances when we examine attribut values. Therefore we test for the
+normal instances when we examine attribute values. Therefore we test for the
number of deferred fields on returned instances (by poking at the internals),
as a way to observe what is going on.
@@ -98,5 +104,89 @@ Using defer() and only() with get() is also valid.
>>> Primary.objects.all()
[]
+# Regression for #10572 - A subclass with no extra fields can defer fields from the base class
+>>> _ = Child.objects.create(name="c1", value="foo", related=s1)
+
+# You can defer a field on a baseclass when the subclass has no fields
+>>> obj = Child.objects.defer("value").get(name="c1")
+>>> count_delayed_fields(obj)
+1
+>>> obj.name
+u"c1"
+>>> obj.value
+u"foo"
+>>> obj.name = "c2"
+>>> obj.save()
+
+# You can retrive a single column on a base class with no fields
+>>> obj = Child.objects.only("name").get(name="c2")
+>>> count_delayed_fields(obj)
+3
+>>> obj.name
+u"c2"
+>>> obj.value
+u"foo"
+>>> obj.name = "cc"
+>>> obj.save()
+
+>>> _ = BigChild.objects.create(name="b1", value="foo", related=s1, other="bar")
+
+# You can defer a field on a baseclass
+>>> obj = BigChild.objects.defer("value").get(name="b1")
+>>> count_delayed_fields(obj)
+1
+>>> obj.name
+u"b1"
+>>> obj.value
+u"foo"
+>>> obj.other
+u"bar"
+>>> obj.name = "b2"
+>>> obj.save()
+
+# You can defer a field on a subclass
+>>> obj = BigChild.objects.defer("other").get(name="b2")
+>>> count_delayed_fields(obj)
+1
+>>> obj.name
+u"b2"
+>>> obj.value
+u"foo"
+>>> obj.other
+u"bar"
+>>> obj.name = "b3"
+>>> obj.save()
+
+# You can retrieve a single field on a baseclass
+>>> obj = BigChild.objects.only("name").get(name="b3")
+>>> count_delayed_fields(obj)
+4
+>>> obj.name
+u"b3"
+>>> obj.value
+u"foo"
+>>> obj.other
+u"bar"
+>>> obj.name = "b4"
+>>> obj.save()
+
+# You can retrieve a single field on a baseclass
+>>> obj = BigChild.objects.only("other").get(name="b4")
+>>> count_delayed_fields(obj)
+4
+>>> obj.name
+u"b4"
+>>> obj.value
+u"foo"
+>>> obj.other
+u"bar"
+>>> obj.name = "bb"
+>>> obj.save()
+
+# Finally, we need to flush the app cache for the defer module.
+# Using only/defer creates some artifical entries in the app cache
+# that messes up later tests. Purge all entries, just to be sure.
+>>> from django.db.models.loading import cache
+>>> cache.app_models['defer'] = {}
"""}
diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py
index 11ce1557fe..da9822ab88 100644
--- a/tests/regressiontests/defer_regress/models.py
+++ b/tests/regressiontests/defer_regress/models.py
@@ -84,7 +84,8 @@ Some further checks for select_related() and inherited model behaviour
(regression for #10710).
>>> c1 = Child.objects.create(name="c1", value=42)
->>> obj = Leaf.objects.create(name="l1", child=c1)
+>>> c2 = Child.objects.create(name="c2", value=37)
+>>> obj = Leaf.objects.create(name="l1", child=c1, second_child=c2)
>>> obj = Leaf.objects.only("name", "child").select_related()[0]
>>> obj.child.name
@@ -101,5 +102,24 @@ types as their non-deferred versions (bug #10738).
>>> c1 is c2 is c3
True
+# Regression for #10733 - only() can be used on a model with two foreign keys.
+>>> results = Leaf.objects.all().only('name', 'child', 'second_child').select_related()
+>>> results[0].child.name
+u'c1'
+>>> results[0].second_child.name
+u'c2'
+
+>>> results = Leaf.objects.all().only('name', 'child', 'second_child', 'child__name', 'second_child__name').select_related()
+>>> results[0].child.name
+u'c1'
+>>> results[0].second_child.name
+u'c2'
+
+# Finally, we need to flush the app cache for the defer module.
+# Using only/defer creates some artifical entries in the app cache
+# that messes up later tests. Purge all entries, just to be sure.
+>>> from django.db.models.loading import cache
+>>> cache.app_models['defer_regress'] = {}
+
"""
}
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index b5fa377496..3649d27171 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -1143,6 +1143,33 @@ True
>>> r.save()
>>> Ranking.objects.all()
[, , ]
+
+# Regression test for #10742:
+# Queries used in an __in clause don't execute subqueries
+
+>>> subq = Author.objects.filter(num__lt=3000)
+>>> qs = Author.objects.filter(pk__in=subq)
+>>> list(qs)
+[, ]
+# The subquery result cache should not be populated
+>>> subq._result_cache is None
+True
+
+>>> subq = Author.objects.filter(num__lt=3000)
+>>> qs = Author.objects.exclude(pk__in=subq)
+>>> list(qs)
+[, ]
+# The subquery result cache should not be populated
+>>> subq._result_cache is None
+True
+
+>>> subq = Author.objects.filter(num__lt=3000)
+>>> list(Author.objects.filter(Q(pk__in=subq) & Q(name='a1')))
+[]
+# The subquery result cache should not be populated
+>>> subq._result_cache is None
+True
+
"""}
# In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__