Ticket #5768: 5768-m2m-values-r14618.diff

File 5768-m2m-values-r14618.diff, 9.6 KB (added by Tai Lee, 13 years ago)
  • django/db/models/query.py

     
    490490    # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
    491491    ##################################################
    492492
    493     def values(self, *fields):
    494         return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields)
     493    def values(self, *fields, **kwargs):
     494        allow_m2m = kwargs.pop('allow_m2m', False)
     495        if kwargs:
     496            raise TypeError('Unexpected keyword arguments to values_list: %s'
     497                    % (kwargs.keys(),))
     498        return self._clone(klass=ValuesQuerySet, setup=True, _fields=fields,
     499                allow_m2m=allow_m2m)
    495500
    496501    def values_list(self, *fields, **kwargs):
    497502        flat = kwargs.pop('flat', False)
     503        allow_m2m = kwargs.pop('allow_m2m', False)
    498504        if kwargs:
    499505            raise TypeError('Unexpected keyword arguments to values_list: %s'
    500506                    % (kwargs.keys(),))
    501507        if flat and len(fields) > 1:
    502508            raise TypeError("'flat' is not valid when values_list is called with more than one field.")
    503509        return self._clone(klass=ValuesListQuerySet, setup=True, flat=flat,
    504                 _fields=fields)
     510                _fields=fields, allow_m2m=allow_m2m)
    505511
    506512    def dates(self, field_name, kind, order='ASC'):
    507513        """
     
    870876        self.query.select = []
    871877        if self.extra_names is not None:
    872878            self.query.set_extra_mask(self.extra_names)
    873         self.query.add_fields(self.field_names, False)
     879        self.query.add_fields(self.field_names, self.allow_m2m)
    874880        if self.aggregate_names is not None:
    875881            self.query.set_aggregate_mask(self.aggregate_names)
    876882
     
    886892        c.field_names = self.field_names
    887893        c.extra_names = self.extra_names
    888894        c.aggregate_names = self.aggregate_names
     895        c.allow_m2m = self.allow_m2m
    889896        if setup and hasattr(c, '_setup_query'):
    890897            c._setup_query()
    891898        return c
     
    972979    def _clone(self, *args, **kwargs):
    973980        clone = super(ValuesListQuerySet, self)._clone(*args, **kwargs)
    974981        clone.flat = self.flat
     982        clone.allow_m2m = self.allow_m2m
    975983        return clone
    976984
    977985
  • tests/modeltests/lookup/tests.py

     
    33from django.core.exceptions import FieldError
    44from django.db import connection
    55from django.test import TestCase, skipUnlessDBFeature
    6 from models import Article
     6from models import Author, Article, Tag
    77
    88
    99class LookupTests(TestCase):
    1010
    1111    #def setUp(self):
    1212    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()
    1318        # 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)
    1520        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)
    1722        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)
    1924        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)
    2126        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)
    2328        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)
    2530        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)
    2732        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)
    2843
    2944    def test_exists(self):
    3045        # We can use .exists() to check that there are some
     
    188203        self.assertRaises(FieldError,
    189204            Article.objects.extra(select={'id_plus_one': 'id + 1'}).values,
    190205            'id', 'id_plus_two')
     206        # You can specify fields from related models that cross a single-valued
     207        # relation (one-to-one, many-to-one).
     208        self.assertQuerysetEqual(
     209            Article.objects.values('headline', 'author__name'),
     210            [
     211                {'headline': self.a5.headline, 'author__name': self.au2.name},
     212                {'headline': self.a6.headline, 'author__name': self.au2.name},
     213                {'headline': self.a4.headline, 'author__name': self.au1.name},
     214                {'headline': self.a2.headline, 'author__name': self.au1.name},
     215                {'headline': self.a3.headline, 'author__name': self.au1.name},
     216                {'headline': self.a7.headline, 'author__name': self.au2.name},
     217                {'headline': self.a1.headline, 'author__name': self.au1.name},
     218            ], transform=identity)
     219        # If you want to specify fields from related models that cross a
     220        # multi-valued relation, you need to use the allow_m2m argument.
     221        self.assertQuerysetEqual(
     222            Author.objects.values('name', 'article__headline', allow_m2m=True),
     223            [
     224                {'name': self.au1.name, 'article__headline': self.a1.headline},
     225                {'name': self.au1.name, 'article__headline': self.a2.headline},
     226                {'name': self.au1.name, 'article__headline': self.a3.headline},
     227                {'name': self.au1.name, 'article__headline': self.a4.headline},
     228                {'name': self.au2.name, 'article__headline': self.a5.headline},
     229                {'name': self.au2.name, 'article__headline': self.a6.headline},
     230                {'name': self.au2.name, 'article__headline': self.a7.headline},
     231            ], transform=identity)
     232        self.assertQuerysetEqual(
     233            Author.objects.values('name', 'article__headline', 'article__tag__name', allow_m2m=True),
     234            [
     235                {'name': self.au1.name, 'article__headline': self.a1.headline, 'article__tag__name': self.t1.name},
     236                {'name': self.au1.name, 'article__headline': self.a2.headline, 'article__tag__name': self.t1.name},
     237                {'name': self.au1.name, 'article__headline': self.a3.headline, 'article__tag__name': self.t1.name},
     238                {'name': self.au1.name, 'article__headline': self.a3.headline, 'article__tag__name': self.t2.name},
     239                {'name': self.au1.name, 'article__headline': self.a4.headline, 'article__tag__name': self.t2.name},
     240                {'name': self.au2.name, 'article__headline': self.a5.headline, 'article__tag__name': self.t2.name},
     241                {'name': self.au2.name, 'article__headline': self.a5.headline, 'article__tag__name': self.t3.name},
     242                {'name': self.au2.name, 'article__headline': self.a6.headline, 'article__tag__name': self.t3.name},
     243                {'name': self.au2.name, 'article__headline': self.a7.headline, 'article__tag__name': self.t3.name},
     244            ], transform=identity)
    191245        # If you don't specify field names to values(), all are returned.
    192246        self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(),
    193247            [{
    194248                'id': self.a5.id,
     249                'author_id': self.a2.id,
    195250                'headline': 'Article 5',
    196251                'pub_date': datetime(2005, 8, 1, 9, 0)
    197252            }], transform=identity)
     
    402457            self.fail('FieldError not raised')
    403458        except FieldError, ex:
    404459            self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' "
    405                              "into field. Choices are: headline, id, pub_date")
     460                             "into field. Choices are: author, headline, id, pub_date, tag")
    406461        try:
    407462            Article.objects.filter(headline__starts='Article')
    408463            self.fail('FieldError not raised')
  • tests/modeltests/lookup/models.py

     
    77from django.db import models, DEFAULT_DB_ALIAS, connection
    88from django.conf import settings
    99
     10class Author(models.Model):
     11    name = models.CharField(max_length=100)
     12
    1013class Article(models.Model):
    1114    headline = models.CharField(max_length=100)
    1215    pub_date = models.DateTimeField()
     16    author = models.ForeignKey(Author, blank=True, null=True)
    1317    class Meta:
    1418        ordering = ('-pub_date', 'headline')
    1519
    1620    def __unicode__(self):
    1721        return self.headline
     22
     23class Tag(models.Model):
     24        articles = models.ManyToManyField(Article)
     25        name = models.CharField(max_length=100)
Back to Top