Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #10256 -- Corrected the interaction of extra(select=) with valu…

…es() and values_list() where an explicit list of columns is requested.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9837 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 58ea6d45618cb8b5bd4306c395d7bb8860f9a842 1 parent 8883787
Russell Keith-Magee authored February 16, 2009
52  django/db/models/query.py
@@ -727,12 +727,15 @@ def __init__(self, *args, **kwargs):
727 727
         # names of the model fields to select.
728 728
 
729 729
     def iterator(self):
730  
-        if (not self.extra_names and
731  
-            len(self.field_names) != len(self.model._meta.fields)):
  730
+        # Purge any extra columns that haven't been explicitly asked for
  731
+        if self.extra_names is not None:
732 732
             self.query.trim_extra_select(self.extra_names)
733  
-        names = self.query.extra_select.keys() + self.field_names
734  
-        names.extend(self.query.aggregate_select.keys())
735 733
 
  734
+        extra_names = self.query.extra_select.keys()
  735
+        field_names = self.field_names
  736
+        aggregate_names = self.query.aggregate_select.keys()
  737
+
  738
+        names = extra_names + field_names + aggregate_names
736 739
         for row in self.query.results_iter():
737 740
             yield dict(zip(names, row))
738 741
 
@@ -745,29 +748,30 @@ def _setup_query(self):
745 748
         instance.
746 749
         """
747 750
         self.query.clear_select_fields()
748  
-        self.extra_names = []
749  
-        self.aggregate_names = []
750 751
 
751 752
         if self._fields:
  753
+            self.extra_names = []
  754
+            self.aggregate_names = []
752 755
             if not self.query.extra_select and not self.query.aggregate_select:
753  
-                field_names = list(self._fields)
  756
+                self.field_names = list(self._fields)
754 757
             else:
755  
-                field_names = []
  758
+                self.query.default_cols = False
  759
+                self.field_names = []
756 760
                 for f in self._fields:
757 761
                     if self.query.extra_select.has_key(f):
758 762
                         self.extra_names.append(f)
759 763
                     elif self.query.aggregate_select.has_key(f):
760 764
                         self.aggregate_names.append(f)
761 765
                     else:
762  
-                        field_names.append(f)
  766
+                        self.field_names.append(f)
763 767
         else:
764 768
             # Default to all fields.
765  
-            field_names = [f.attname for f in self.model._meta.fields]
  769
+            self.extra_names = None
  770
+            self.field_names = [f.attname for f in self.model._meta.fields]
  771
+            self.aggregate_names = None
766 772
 
767 773
         self.query.select = []
768  
-        self.query.add_fields(field_names, False)
769  
-        self.query.default_cols = False
770  
-        self.field_names = field_names
  774
+        self.query.add_fields(self.field_names, False)
771 775
 
772 776
     def _clone(self, klass=None, setup=False, **kwargs):
773 777
         """
@@ -817,7 +821,8 @@ def as_sql(self):
817 821
 
818 822
 class ValuesListQuerySet(ValuesQuerySet):
819 823
     def iterator(self):
820  
-        self.query.trim_extra_select(self.extra_names)
  824
+        if self.extra_names is not None:
  825
+            self.query.trim_extra_select(self.extra_names)
821 826
         if self.flat and len(self._fields) == 1:
822 827
             for row in self.query.results_iter():
823 828
                 yield row[0]
@@ -825,13 +830,24 @@ def iterator(self):
825 830
             for row in self.query.results_iter():
826 831
                 yield tuple(row)
827 832
         else:
828  
-            # When extra(select=...) is involved, the extra cols come are
829  
-            # always at the start of the row, so we need to reorder the fields
  833
+            # When extra(select=...) or an annotation is involved, the extra cols are
  834
+            # always at the start of the row, and we need to reorder the fields
830 835
             # to match the order in self._fields.
831  
-            names = self.query.extra_select.keys() + self.field_names + self.query.aggregate_select.keys()
  836
+            extra_names = self.query.extra_select.keys()
  837
+            field_names = self.field_names
  838
+            aggregate_names = self.query.aggregate_select.keys()
  839
+            names = extra_names + field_names + aggregate_names
  840
+
  841
+            # If a field list has been specified, use it. Otherwise, use the
  842
+            # full list of fields, including extras and aggregates.
  843
+            if self._fields:
  844
+                fields = self._fields
  845
+            else:
  846
+                fields = names
  847
+
832 848
             for row in self.query.results_iter():
833 849
                 data = dict(zip(names, row))
834  
-                yield tuple([data[f] for f in self._fields])
  850
+                yield tuple([data[f] for f in fields])
835 851
 
836 852
     def _clone(self, *args, **kwargs):
837 853
         clone = super(ValuesListQuerySet, self)._clone(*args, **kwargs)
2  tests/regressiontests/aggregation_regress/models.py
@@ -91,7 +91,7 @@ def __unicode__(self):
91 91
 >>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2).items())
92 92
 [('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)]
93 93
 
94  
-# The order of the values, annotate and extra clauses doesn't matter
  94
+# The order of the (empty) values, annotate and extra clauses doesn't matter
95 95
 >>> sorted(Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).items())
96 96
 [('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)]
97 97
 
76  tests/regressiontests/extra_regress/models.py
@@ -30,6 +30,11 @@ class Order(models.Model):
30 30
     created_by = models.ForeignKey(User)
31 31
     text = models.TextField()
32 32
 
  33
+class TestObject(models.Model):
  34
+    first = models.CharField(max_length=20)
  35
+    second = models.CharField(max_length=20)
  36
+    third = models.CharField(max_length=20)
  37
+
33 38
 __test__ = {"API_TESTS": """
34 39
 # Regression tests for #7314 and #7372
35 40
 
@@ -115,4 +120,75 @@ class Order(models.Model):
115 120
 # cause incorrect SQL to be produced otherwise.
116 121
 >>> RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month')
117 122
 [datetime.datetime(2008, 9, 1, 0, 0)]
  123
+
  124
+# Regression test for #10256... If there is a values() clause, Extra columns are
  125
+# only returned if they are explicitly mentioned.
  126
+>>> TestObject(first='first', second='second', third='third').save()
  127
+
  128
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values()
  129
+[{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': 1, 'first': u'first'}]
  130
+
  131
+# Extra clauses after an empty values clause are still included
  132
+>>> TestObject.objects.values().extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third'))))
  133
+[{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': 1, 'first': u'first'}]
  134
+
  135
+# Extra columns are ignored if not mentioned in the values() clause
  136
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('first', 'second')
  137
+[{'second': u'second', 'first': u'first'}]
  138
+
  139
+# Extra columns after a non-empty values() clause are ignored
  140
+>>> TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third'))))
  141
+[{'second': u'second', 'first': u'first'}]
  142
+
  143
+# Extra columns can be partially returned
  144
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('first', 'second', 'foo')
  145
+[{'second': u'second', 'foo': u'first', 'first': u'first'}]
  146
+
  147
+# Also works if only extra columns are included
  148
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('foo', 'whiz')
  149
+[{'foo': u'first', 'whiz': u'third'}]
  150
+
  151
+# Values list works the same way
  152
+# All columns are returned for an empty values_list()
  153
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list()
  154
+[(u'first', u'second', u'third', 1, u'first', u'second', u'third')]
  155
+
  156
+# Extra columns after an empty values_list() are still included
  157
+>>> TestObject.objects.values_list().extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third'))))
  158
+[(u'first', u'second', u'third', 1, u'first', u'second', u'third')]
  159
+
  160
+# Extra columns ignored completely if not mentioned in values_list()
  161
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first', 'second')
  162
+[(u'first', u'second')]
  163
+
  164
+# Extra columns after a non-empty values_list() clause are ignored completely
  165
+>>> TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third'))))
  166
+[(u'first', u'second')]
  167
+
  168
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('second', flat=True)
  169
+[u'second']
  170
+
  171
+# Only the extra columns specified in the values_list() are returned
  172
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first', 'second', 'whiz')
  173
+[(u'first', u'second', u'third')]
  174
+
  175
+# ...also works if only extra columns are included
  176
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('foo','whiz')
  177
+[(u'first', u'third')]
  178
+
  179
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz', flat=True)
  180
+[u'third']
  181
+
  182
+# ... and values are returned in the order they are specified
  183
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz','foo')
  184
+[(u'third', u'first')]
  185
+
  186
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first','id')
  187
+[(u'first', 1)]
  188
+
  189
+>>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')
  190
+[(u'third', u'first', u'second', 1)]
  191
+
118 192
 """}
  193
+
  194
+

0 notes on commit 58ea6d4

Please sign in to comment.
Something went wrong with that request. Please try again.