Browse Source

Fixed #31918 -- Allowed QuerySet.in_bulk() to fetch on a single distinct field.

Kaustubh 4 years ago
parent
commit
b9be11d442
4 changed files with 33 additions and 3 deletions
  1. 2 1
      django/db/models/query.py
  2. 9 2
      docs/ref/models/querysets.txt
  3. 4 0
      docs/releases/3.2.txt
  4. 18 0
      tests/lookup/tests.py

+ 2 - 1
django/db/models/query.py

@@ -699,7 +699,8 @@ class QuerySet:
         if (
             field_name != 'pk' and
             not opts.get_field(field_name).unique and
-            field_name not in unique_fields
+            field_name not in unique_fields and
+            not self.query.distinct_fields == (field_name,)
         ):
             raise ValueError("in_bulk()'s field_name must be a unique field but %r isn't." % field_name)
         if id_list is not None:

+ 9 - 2
docs/ref/models/querysets.txt

@@ -2250,8 +2250,9 @@ database query like ``count()`` would.
 Takes a list of field values (``id_list``) and the ``field_name`` for those
 values, and returns a dictionary mapping each value to an instance of the
 object with the given field value. If ``id_list`` isn't provided, all objects
-in the queryset are returned. ``field_name`` must be a unique field, and it
-defaults to the primary key.
+in the queryset are returned. ``field_name`` must be a unique field or a
+distinct field (if there's only one field specified in :meth:`distinct`).
+``field_name`` defaults to the primary key.
 
 Example::
 
@@ -2265,9 +2266,15 @@ Example::
     {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}
     >>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug')
     {'beatles_blog': <Blog: Beatles Blog>}
+    >>> Blog.objects.distinct('name').in_bulk(field_name='name')
+    {'Beatles Blog': <Blog: Beatles Blog>, 'Cheddar Talk': <Blog: Cheddar Talk>, 'Django Weblog': <Blog: Django Weblog>}
 
 If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
 
+.. versionchanged:: 3.2
+
+    Using a distinct field was allowed.
+
 ``iterator()``
 ~~~~~~~~~~~~~~
 

+ 4 - 0
docs/releases/3.2.txt

@@ -286,6 +286,10 @@ Models
 * The new :class:`~django.db.models.functions.Collate` function allows
   filtering and ordering by specified database collations.
 
+* The ``field_name`` argument of :meth:`.QuerySet.in_bulk()` now accepts
+  distinct fields if there's only one field specified in
+  :meth:`.QuerySet.distinct`.
+
 Pagination
 ~~~~~~~~~~
 

+ 18 - 0
tests/lookup/tests.py

@@ -207,6 +207,24 @@ class LookupTests(TestCase):
         with self.assertRaisesMessage(ValueError, msg):
             Article.objects.in_bulk([self.au1], field_name='author')
 
+    @skipUnlessDBFeature('can_distinct_on_fields')
+    def test_in_bulk_distinct_field(self):
+        self.assertEqual(
+            Article.objects.order_by('headline').distinct('headline').in_bulk(
+                [self.a1.headline, self.a5.headline],
+                field_name='headline',
+            ),
+            {self.a1.headline: self.a1, self.a5.headline: self.a5},
+        )
+
+    @skipUnlessDBFeature('can_distinct_on_fields')
+    def test_in_bulk_multiple_distinct_field(self):
+        msg = "in_bulk()'s field_name must be a unique field but 'pub_date' isn't."
+        with self.assertRaisesMessage(ValueError, msg):
+            Article.objects.order_by('headline', 'pub_date').distinct(
+                'headline', 'pub_date',
+            ).in_bulk(field_name='pub_date')
+
     @isolate_apps('lookup')
     def test_in_bulk_non_unique_meta_constaint(self):
         class Model(models.Model):