Skip to content

Commit b9be11d

Browse files
kc611felixxm
authored andcommitted
Fixed #31918 -- Allowed QuerySet.in_bulk() to fetch on a single distinct field.
1 parent 547a07f commit b9be11d

File tree

4 files changed

+33
-3
lines changed

4 files changed

+33
-3
lines changed

django/db/models/query.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,8 @@ def in_bulk(self, id_list=None, *, field_name='pk'):
699699
if (
700700
field_name != 'pk' and
701701
not opts.get_field(field_name).unique and
702-
field_name not in unique_fields
702+
field_name not in unique_fields and
703+
not self.query.distinct_fields == (field_name,)
703704
):
704705
raise ValueError("in_bulk()'s field_name must be a unique field but %r isn't." % field_name)
705706
if id_list is not None:

docs/ref/models/querysets.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,8 +2250,9 @@ database query like ``count()`` would.
22502250
Takes a list of field values (``id_list``) and the ``field_name`` for those
22512251
values, and returns a dictionary mapping each value to an instance of the
22522252
object with the given field value. If ``id_list`` isn't provided, all objects
2253-
in the queryset are returned. ``field_name`` must be a unique field, and it
2254-
defaults to the primary key.
2253+
in the queryset are returned. ``field_name`` must be a unique field or a
2254+
distinct field (if there's only one field specified in :meth:`distinct`).
2255+
``field_name`` defaults to the primary key.
22552256

22562257
Example::
22572258

@@ -2265,9 +2266,15 @@ Example::
22652266
{1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>}
22662267
>>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug')
22672268
{'beatles_blog': <Blog: Beatles Blog>}
2269+
>>> Blog.objects.distinct('name').in_bulk(field_name='name')
2270+
{'Beatles Blog': <Blog: Beatles Blog>, 'Cheddar Talk': <Blog: Cheddar Talk>, 'Django Weblog': <Blog: Django Weblog>}
22682271

22692272
If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
22702273

2274+
.. versionchanged:: 3.2
2275+
2276+
Using a distinct field was allowed.
2277+
22712278
``iterator()``
22722279
~~~~~~~~~~~~~~
22732280

docs/releases/3.2.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ Models
286286
* The new :class:`~django.db.models.functions.Collate` function allows
287287
filtering and ordering by specified database collations.
288288

289+
* The ``field_name`` argument of :meth:`.QuerySet.in_bulk()` now accepts
290+
distinct fields if there's only one field specified in
291+
:meth:`.QuerySet.distinct`.
292+
289293
Pagination
290294
~~~~~~~~~~
291295

tests/lookup/tests.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,24 @@ def test_in_bulk_non_unique_field(self):
207207
with self.assertRaisesMessage(ValueError, msg):
208208
Article.objects.in_bulk([self.au1], field_name='author')
209209

210+
@skipUnlessDBFeature('can_distinct_on_fields')
211+
def test_in_bulk_distinct_field(self):
212+
self.assertEqual(
213+
Article.objects.order_by('headline').distinct('headline').in_bulk(
214+
[self.a1.headline, self.a5.headline],
215+
field_name='headline',
216+
),
217+
{self.a1.headline: self.a1, self.a5.headline: self.a5},
218+
)
219+
220+
@skipUnlessDBFeature('can_distinct_on_fields')
221+
def test_in_bulk_multiple_distinct_field(self):
222+
msg = "in_bulk()'s field_name must be a unique field but 'pub_date' isn't."
223+
with self.assertRaisesMessage(ValueError, msg):
224+
Article.objects.order_by('headline', 'pub_date').distinct(
225+
'headline', 'pub_date',
226+
).in_bulk(field_name='pub_date')
227+
210228
@isolate_apps('lookup')
211229
def test_in_bulk_non_unique_meta_constaint(self):
212230
class Model(models.Model):

0 commit comments

Comments
 (0)