Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #5768 -- Added support for ManyToManyFields and reverse relatio…

…ns in values() and values_list(). Thanks to mrmachine for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14655 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 9b432cb67bfc0648862cafb49868e670583050df 1 parent 7592d68
Carl Meyer authored November 21, 2010
2  django/db/models/query.py
@@ -870,7 +870,7 @@ def _setup_query(self):
870 870
         self.query.select = []
871 871
         if self.extra_names is not None:
872 872
             self.query.set_extra_mask(self.extra_names)
873  
-        self.query.add_fields(self.field_names, False)
  873
+        self.query.add_fields(self.field_names, True)
874 874
         if self.aggregate_names is not None:
875 875
             self.query.set_aggregate_mask(self.aggregate_names)
876 876
 
27  docs/ref/models/querysets.txt
@@ -398,11 +398,8 @@ Example::
398 398
     >>> Blog.objects.values('id', 'name')
399 399
     [{'id': 1, 'name': 'Beatles Blog'}]
400 400
 
401  
-A couple of subtleties that are worth mentioning:
  401
+A few subtleties that are worth mentioning:
402 402
 
403  
-    * The ``values()`` method does not return anything for
404  
-      :class:`~django.db.models.ManyToManyField` attributes and will raise an
405  
-      error if you try to pass in this type of field to it.
406 403
     * If you have a field called ``foo`` that is a
407 404
       :class:`~django.db.models.ForeignKey`, the default ``values()`` call
408 405
       will return a dictionary key called ``foo_id``, since this is the name
@@ -453,6 +450,28 @@ followed (optionally) by any output-affecting methods (such as ``values()``),
453 450
 but it doesn't really matter. This is your chance to really flaunt your
454 451
 individualism.
455 452
 
  453
+.. versionchanged:: 1.3
  454
+
  455
+The ``values()`` method previously did not return anything for
  456
+:class:`~django.db.models.ManyToManyField` attributes and would raise an error
  457
+if you tried to pass this type of field to it.
  458
+
  459
+This restriction has been lifted, and you can now also refer to fields on
  460
+related models with reverse relations through ``OneToOneField``, ``ForeignKey``
  461
+and ``ManyToManyField`` attributes::
  462
+
  463
+	Blog.objects.values('name', 'entry__headline')
  464
+	[{'name': 'My blog', 'entry__headline': 'An entry'},
  465
+         {'name': 'My blog', 'entry__headline': 'Another entry'}, ...]
  466
+
  467
+.. warning::
  468
+
  469
+   Because :class:`~django.db.models.ManyToManyField` attributes and reverse
  470
+   relations can have multiple related rows, including these can have a
  471
+   multiplier effect on the size of your result set. This will be especially
  472
+   pronounced if you include multiple such fields in your ``values()`` query,
  473
+   in which case all possible combinations will be returned.
  474
+
456 475
 ``values_list(*fields)``
457 476
 ~~~~~~~~~~~~~~~~~~~~~~~~
458 477
 
12  tests/modeltests/lookup/models.py
@@ -7,11 +7,23 @@
7 7
 from django.db import models, DEFAULT_DB_ALIAS, connection
8 8
 from django.conf import settings
9 9
 
  10
+class Author(models.Model):
  11
+    name = models.CharField(max_length=100)
  12
+    class Meta:
  13
+        ordering = ('name', )
  14
+
10 15
 class Article(models.Model):
11 16
     headline = models.CharField(max_length=100)
12 17
     pub_date = models.DateTimeField()
  18
+    author = models.ForeignKey(Author, blank=True, null=True)
13 19
     class Meta:
14 20
         ordering = ('-pub_date', 'headline')
15 21
 
16 22
     def __unicode__(self):
17 23
         return self.headline
  24
+
  25
+class Tag(models.Model):
  26
+    articles = models.ManyToManyField(Article)
  27
+    name = models.CharField(max_length=100)
  28
+    class Meta:
  29
+        ordering = ('name', )
83  tests/modeltests/lookup/tests.py
@@ -3,28 +3,43 @@
3 3
 from django.core.exceptions import FieldError
4 4
 from django.db import connection
5 5
 from django.test import TestCase, skipUnlessDBFeature
6  
-from models import Article
  6
+from models import Author, Article, Tag
7 7
 
8 8
 
9 9
 class LookupTests(TestCase):
10 10
 
11 11
     #def setUp(self):
12 12
     def setUp(self):
  13
+        # Create a few Authors.
  14
+        self.au1 = Author(name='Author 1')
  15
+        self.au1.save()
  16
+        self.au2 = Author(name='Author 2')
  17
+        self.au2.save()
13 18
         # Create a couple of Articles.
14  
-        self.a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
  19
+        self.a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26), author=self.au1)
15 20
         self.a1.save()
16  
-        self.a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
  21
+        self.a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27), author=self.au1)
17 22
         self.a2.save()
18  
-        self.a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
  23
+        self.a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27), author=self.au1)
19 24
         self.a3.save()
20  
-        self.a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
  25
+        self.a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28), author=self.au1)
21 26
         self.a4.save()
22  
-        self.a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0))
  27
+        self.a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0), author=self.au2)
23 28
         self.a5.save()
24  
-        self.a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0))
  29
+        self.a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0), author=self.au2)
25 30
         self.a6.save()
26  
-        self.a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
  31
+        self.a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27), author=self.au2)
27 32
         self.a7.save()
  33
+        # Create a few Tags.
  34
+        self.t1 = Tag(name='Tag 1')
  35
+        self.t1.save()
  36
+        self.t1.articles.add(self.a1, self.a2, self.a3)
  37
+        self.t2 = Tag(name='Tag 2')
  38
+        self.t2.save()
  39
+        self.t2.articles.add(self.a3, self.a4, self.a5)
  40
+        self.t3 = Tag(name='Tag 3')
  41
+        self.t3.save()
  42
+        self.t3.articles.add(self.a5, self.a6, self.a7)
28 43
 
29 44
     def test_exists(self):
30 45
         # We can use .exists() to check that there are some
@@ -182,6 +197,42 @@ def test_values(self):
182 197
                 'id_plus_seven': self.a1.id + 7,
183 198
                 'id_plus_eight': self.a1.id + 8,
184 199
             }], transform=identity)
  200
+        # You can specify fields from forward and reverse relations, just like filter().
  201
+        self.assertQuerysetEqual(
  202
+            Article.objects.values('headline', 'author__name'),
  203
+            [
  204
+                {'headline': self.a5.headline, 'author__name': self.au2.name},
  205
+                {'headline': self.a6.headline, 'author__name': self.au2.name},
  206
+                {'headline': self.a4.headline, 'author__name': self.au1.name},
  207
+                {'headline': self.a2.headline, 'author__name': self.au1.name},
  208
+                {'headline': self.a3.headline, 'author__name': self.au1.name},
  209
+                {'headline': self.a7.headline, 'author__name': self.au2.name},
  210
+                {'headline': self.a1.headline, 'author__name': self.au1.name},
  211
+            ], transform=identity)
  212
+        self.assertQuerysetEqual(
  213
+            Author.objects.values('name', 'article__headline').order_by('name', 'article__headline'),
  214
+            [
  215
+                {'name': self.au1.name, 'article__headline': self.a1.headline},
  216
+                {'name': self.au1.name, 'article__headline': self.a2.headline},
  217
+                {'name': self.au1.name, 'article__headline': self.a3.headline},
  218
+                {'name': self.au1.name, 'article__headline': self.a4.headline},
  219
+                {'name': self.au2.name, 'article__headline': self.a5.headline},
  220
+                {'name': self.au2.name, 'article__headline': self.a6.headline},
  221
+                {'name': self.au2.name, 'article__headline': self.a7.headline},
  222
+            ], transform=identity)
  223
+        self.assertQuerysetEqual(
  224
+            Author.objects.values('name', 'article__headline', 'article__tag__name').order_by('name', 'article__headline', 'article__tag__name'),
  225
+            [
  226
+                {'name': self.au1.name, 'article__headline': self.a1.headline, 'article__tag__name': self.t1.name},
  227
+                {'name': self.au1.name, 'article__headline': self.a2.headline, 'article__tag__name': self.t1.name},
  228
+                {'name': self.au1.name, 'article__headline': self.a3.headline, 'article__tag__name': self.t1.name},
  229
+                {'name': self.au1.name, 'article__headline': self.a3.headline, 'article__tag__name': self.t2.name},
  230
+                {'name': self.au1.name, 'article__headline': self.a4.headline, 'article__tag__name': self.t2.name},
  231
+                {'name': self.au2.name, 'article__headline': self.a5.headline, 'article__tag__name': self.t2.name},
  232
+                {'name': self.au2.name, 'article__headline': self.a5.headline, 'article__tag__name': self.t3.name},
  233
+                {'name': self.au2.name, 'article__headline': self.a6.headline, 'article__tag__name': self.t3.name},
  234
+                {'name': self.au2.name, 'article__headline': self.a7.headline, 'article__tag__name': self.t3.name},
  235
+            ], transform=identity)
185 236
         # However, an exception FieldDoesNotExist will be thrown if you specify
186 237
         # a non-existent field name in values() (a field that is neither in the
187 238
         # model nor in extra(select)).
@@ -192,6 +243,7 @@ def test_values(self):
192 243
         self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(),
193 244
             [{
194 245
                 'id': self.a5.id,
  246
+                'author_id': self.au2.id, 
195 247
                 'headline': 'Article 5',
196 248
                 'pub_date': datetime(2005, 8, 1, 9, 0)
197 249
             }], transform=identity)
@@ -250,6 +302,19 @@ def test_values_list(self):
250 302
                 (self.a7.id, self.a7.id+1)
251 303
             ],
252 304
             transform=identity)
  305
+        self.assertQuerysetEqual(
  306
+            Author.objects.values_list('name', 'article__headline', 'article__tag__name').order_by('name', 'article__headline', 'article__tag__name'),
  307
+            [
  308
+                (self.au1.name, self.a1.headline, self.t1.name),
  309
+                (self.au1.name, self.a2.headline, self.t1.name),
  310
+                (self.au1.name, self.a3.headline, self.t1.name),
  311
+                (self.au1.name, self.a3.headline, self.t2.name),
  312
+                (self.au1.name, self.a4.headline, self.t2.name),
  313
+                (self.au2.name, self.a5.headline, self.t2.name),
  314
+                (self.au2.name, self.a5.headline, self.t3.name),
  315
+                (self.au2.name, self.a6.headline, self.t3.name),
  316
+                (self.au2.name, self.a7.headline, self.t3.name),
  317
+            ], transform=identity)
253 318
         self.assertRaises(TypeError, Article.objects.values_list, 'id', 'headline', flat=True)
254 319
 
255 320
     def test_get_next_previous_by(self):
@@ -402,7 +467,7 @@ def test_error_messages(self):
402 467
             self.fail('FieldError not raised')
403 468
         except FieldError, ex:
404 469
             self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' "
405  
-                             "into field. Choices are: headline, id, pub_date")
  470
+                             "into field. Choices are: author, headline, id, pub_date, tag")
406 471
         try:
407 472
             Article.objects.filter(headline__starts='Article')
408 473
             self.fail('FieldError not raised')

0 notes on commit 9b432cb

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