diff --git a/django/db/models/query.py b/django/db/models/query.py index bce48e17ef..b39a988ab0 100644 --- a/django/db/models/query.py +++ b/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: diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index e5c0038528..228e2cf736 100644 --- a/docs/ref/models/querysets.txt +++ b/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: , 2: , 3: } >>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug') {'beatles_blog': } + >>> Blog.objects.distinct('name').in_bulk(field_name='name') + {'Beatles Blog': , 'Cheddar Talk': , '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()`` ~~~~~~~~~~~~~~ diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index c7219c759b..d29a582d74 100644 --- a/docs/releases/3.2.txt +++ b/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 ~~~~~~~~~~ diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index 3d8a801933..9094542808 100644 --- a/tests/lookup/tests.py +++ b/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):