Author: carljm
Date: 2010-11-20 20:28:25 -0600 (Sat, 20 Nov 2010)
New Revision: 14655

Modified:
   django/trunk/django/db/models/query.py
   django/trunk/docs/ref/models/querysets.txt
   django/trunk/tests/modeltests/lookup/models.py
   django/trunk/tests/modeltests/lookup/tests.py
Log:
Fixed #5768 -- Added support for ManyToManyFields and reverse relations in 
values() and values_list(). Thanks to mrmachine for the patch.

Modified: django/trunk/django/db/models/query.py
===================================================================
--- django/trunk/django/db/models/query.py      2010-11-20 23:57:01 UTC (rev 
14654)
+++ django/trunk/django/db/models/query.py      2010-11-21 02:28:25 UTC (rev 
14655)
@@ -870,7 +870,7 @@
         self.query.select = []
         if self.extra_names is not None:
             self.query.set_extra_mask(self.extra_names)
-        self.query.add_fields(self.field_names, False)
+        self.query.add_fields(self.field_names, True)
         if self.aggregate_names is not None:
             self.query.set_aggregate_mask(self.aggregate_names)
 

Modified: django/trunk/docs/ref/models/querysets.txt
===================================================================
--- django/trunk/docs/ref/models/querysets.txt  2010-11-20 23:57:01 UTC (rev 
14654)
+++ django/trunk/docs/ref/models/querysets.txt  2010-11-21 02:28:25 UTC (rev 
14655)
@@ -398,11 +398,8 @@
     >>> Blog.objects.values('id', 'name')
     [{'id': 1, 'name': 'Beatles Blog'}]
 
-A couple of subtleties that are worth mentioning:
+A few subtleties that are worth mentioning:
 
-    * The ``values()`` method does not return anything for
-      :class:`~django.db.models.ManyToManyField` attributes and will raise an
-      error if you try to pass in this type of field to it.
     * If you have a field called ``foo`` that is a
       :class:`~django.db.models.ForeignKey`, the default ``values()`` call
       will return a dictionary key called ``foo_id``, since this is the name
@@ -453,6 +450,28 @@
 but it doesn't really matter. This is your chance to really flaunt your
 individualism.
 
+.. versionchanged:: 1.3
+
+The ``values()`` method previously did not return anything for
+:class:`~django.db.models.ManyToManyField` attributes and would raise an error
+if you tried to pass this type of field to it.
+
+This restriction has been lifted, and you can now also refer to fields on
+related models with reverse relations through ``OneToOneField``, ``ForeignKey``
+and ``ManyToManyField`` attributes::
+
+       Blog.objects.values('name', 'entry__headline')
+       [{'name': 'My blog', 'entry__headline': 'An entry'},
+         {'name': 'My blog', 'entry__headline': 'Another entry'}, ...]
+
+.. warning::
+
+   Because :class:`~django.db.models.ManyToManyField` attributes and reverse
+   relations can have multiple related rows, including these can have a
+   multiplier effect on the size of your result set. This will be especially
+   pronounced if you include multiple such fields in your ``values()`` query,
+   in which case all possible combinations will be returned.
+
 ``values_list(*fields)``
 ~~~~~~~~~~~~~~~~~~~~~~~~
 

Modified: django/trunk/tests/modeltests/lookup/models.py
===================================================================
--- django/trunk/tests/modeltests/lookup/models.py      2010-11-20 23:57:01 UTC 
(rev 14654)
+++ django/trunk/tests/modeltests/lookup/models.py      2010-11-21 02:28:25 UTC 
(rev 14655)
@@ -7,11 +7,23 @@
 from django.db import models, DEFAULT_DB_ALIAS, connection
 from django.conf import settings
 
+class Author(models.Model):
+    name = models.CharField(max_length=100)
+    class Meta:
+        ordering = ('name', )
+
 class Article(models.Model):
     headline = models.CharField(max_length=100)
     pub_date = models.DateTimeField()
+    author = models.ForeignKey(Author, blank=True, null=True)
     class Meta:
         ordering = ('-pub_date', 'headline')
 
     def __unicode__(self):
         return self.headline
+
+class Tag(models.Model):
+    articles = models.ManyToManyField(Article)
+    name = models.CharField(max_length=100)
+    class Meta:
+        ordering = ('name', )

Modified: django/trunk/tests/modeltests/lookup/tests.py
===================================================================
--- django/trunk/tests/modeltests/lookup/tests.py       2010-11-20 23:57:01 UTC 
(rev 14654)
+++ django/trunk/tests/modeltests/lookup/tests.py       2010-11-21 02:28:25 UTC 
(rev 14655)
@@ -3,28 +3,43 @@
 from django.core.exceptions import FieldError
 from django.db import connection
 from django.test import TestCase, skipUnlessDBFeature
-from models import Article
+from models import Author, Article, Tag
 
 
 class LookupTests(TestCase):
 
     #def setUp(self):
     def setUp(self):
+        # Create a few Authors.
+        self.au1 = Author(name='Author 1')
+        self.au1.save()
+        self.au2 = Author(name='Author 2')
+        self.au2.save()
         # Create a couple of Articles.
-        self.a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
+        self.a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 
26), author=self.au1)
         self.a1.save()
-        self.a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
+        self.a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 
27), author=self.au1)
         self.a2.save()
-        self.a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
+        self.a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 
27), author=self.au1)
         self.a3.save()
-        self.a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
+        self.a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 
28), author=self.au1)
         self.a4.save()
-        self.a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 
9, 0))
+        self.a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 
9, 0), author=self.au2)
         self.a5.save()
-        self.a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 
8, 0))
+        self.a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 
8, 0), author=self.au2)
         self.a6.save()
-        self.a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
+        self.a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 
27), author=self.au2)
         self.a7.save()
+        # Create a few Tags.
+        self.t1 = Tag(name='Tag 1')
+        self.t1.save()
+        self.t1.articles.add(self.a1, self.a2, self.a3)
+        self.t2 = Tag(name='Tag 2')
+        self.t2.save()
+        self.t2.articles.add(self.a3, self.a4, self.a5)
+        self.t3 = Tag(name='Tag 3')
+        self.t3.save()
+        self.t3.articles.add(self.a5, self.a6, self.a7)
 
     def test_exists(self):
         # We can use .exists() to check that there are some
@@ -182,6 +197,42 @@
                 'id_plus_seven': self.a1.id + 7,
                 'id_plus_eight': self.a1.id + 8,
             }], transform=identity)
+        # You can specify fields from forward and reverse relations, just like 
filter().
+        self.assertQuerysetEqual(
+            Article.objects.values('headline', 'author__name'),
+            [
+                {'headline': self.a5.headline, 'author__name': self.au2.name},
+                {'headline': self.a6.headline, 'author__name': self.au2.name},
+                {'headline': self.a4.headline, 'author__name': self.au1.name},
+                {'headline': self.a2.headline, 'author__name': self.au1.name},
+                {'headline': self.a3.headline, 'author__name': self.au1.name},
+                {'headline': self.a7.headline, 'author__name': self.au2.name},
+                {'headline': self.a1.headline, 'author__name': self.au1.name},
+            ], transform=identity)
+        self.assertQuerysetEqual(
+            Author.objects.values('name', 
'article__headline').order_by('name', 'article__headline'),
+            [
+                {'name': self.au1.name, 'article__headline': self.a1.headline},
+                {'name': self.au1.name, 'article__headline': self.a2.headline},
+                {'name': self.au1.name, 'article__headline': self.a3.headline},
+                {'name': self.au1.name, 'article__headline': self.a4.headline},
+                {'name': self.au2.name, 'article__headline': self.a5.headline},
+                {'name': self.au2.name, 'article__headline': self.a6.headline},
+                {'name': self.au2.name, 'article__headline': self.a7.headline},
+            ], transform=identity)
+        self.assertQuerysetEqual(
+            Author.objects.values('name', 'article__headline', 
'article__tag__name').order_by('name', 'article__headline', 
'article__tag__name'),
+            [
+                {'name': self.au1.name, 'article__headline': self.a1.headline, 
'article__tag__name': self.t1.name},
+                {'name': self.au1.name, 'article__headline': self.a2.headline, 
'article__tag__name': self.t1.name},
+                {'name': self.au1.name, 'article__headline': self.a3.headline, 
'article__tag__name': self.t1.name},
+                {'name': self.au1.name, 'article__headline': self.a3.headline, 
'article__tag__name': self.t2.name},
+                {'name': self.au1.name, 'article__headline': self.a4.headline, 
'article__tag__name': self.t2.name},
+                {'name': self.au2.name, 'article__headline': self.a5.headline, 
'article__tag__name': self.t2.name},
+                {'name': self.au2.name, 'article__headline': self.a5.headline, 
'article__tag__name': self.t3.name},
+                {'name': self.au2.name, 'article__headline': self.a6.headline, 
'article__tag__name': self.t3.name},
+                {'name': self.au2.name, 'article__headline': self.a7.headline, 
'article__tag__name': self.t3.name},
+            ], transform=identity)
         # However, an exception FieldDoesNotExist will be thrown if you specify
         # a non-existent field name in values() (a field that is neither in the
         # model nor in extra(select)).
@@ -192,6 +243,7 @@
         
self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(),
             [{
                 'id': self.a5.id,
+                'author_id': self.au2.id, 
                 'headline': 'Article 5',
                 'pub_date': datetime(2005, 8, 1, 9, 0)
             }], transform=identity)
@@ -250,6 +302,19 @@
                 (self.a7.id, self.a7.id+1)
             ],
             transform=identity)
+        self.assertQuerysetEqual(
+            Author.objects.values_list('name', 'article__headline', 
'article__tag__name').order_by('name', 'article__headline', 
'article__tag__name'),
+            [
+                (self.au1.name, self.a1.headline, self.t1.name),
+                (self.au1.name, self.a2.headline, self.t1.name),
+                (self.au1.name, self.a3.headline, self.t1.name),
+                (self.au1.name, self.a3.headline, self.t2.name),
+                (self.au1.name, self.a4.headline, self.t2.name),
+                (self.au2.name, self.a5.headline, self.t2.name),
+                (self.au2.name, self.a5.headline, self.t3.name),
+                (self.au2.name, self.a6.headline, self.t3.name),
+                (self.au2.name, self.a7.headline, self.t3.name),
+            ], transform=identity)
         self.assertRaises(TypeError, Article.objects.values_list, 'id', 
'headline', flat=True)
 
     def test_get_next_previous_by(self):
@@ -402,7 +467,7 @@
             self.fail('FieldError not raised')
         except FieldError, ex:
             self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' "
-                             "into field. Choices are: headline, id, pub_date")
+                             "into field. Choices are: author, headline, id, 
pub_date, tag")
         try:
             Article.objects.filter(headline__starts='Article')
             self.fail('FieldError not raised')

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-upda...@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to