I am trying to figure out if its possible to set up a query which would act something like Model.objects.values_list('id', 'related_model__ids')
and return something like [(1L, [2L, 6L]), (2L, [1L,]), (3L, [1L, 3L, 4L, 5L]), ...]
Here are some specifics:
class Player(models.Model):
positions = models.ForeignKey(Position)
...
>>> # players 31, 32, 33 play multiple positions, and I want a result to be
>>> # [(player.id, [a list of position.id for all positions]), ...]
>>> Player.objects.filter(id__in=[31, 32, 33]).values_list('id', 'positions__id')
[(31L, 13L), (31L, 7L), (32L, 13L), (32L, 8L), (33L, 13L), (33L, 7L)]
This values_list
query produces multiple tuples for the same id
value, two entries for id=31
, id=32
and id=33
. When I make a values_list
query on a specific player (id=31
), I get the list of position ids I would like:
>>> p31 = Player.objects.get(id=31)
>>> position_ids = p31.positions.values_list('id', flat=True)
>>> type(position_ids)
<class 'django.db.models.query.ValuesListQuerySet'>
>>> position_ids
[13L, 7L]
>>>
>>> # I want [(31L, [13L, 7L]), (32L, [13L, 8L), (33L, [13L, 7L)]
>>> # Without have to do something like a list comprehension:
>>> # [(p.id, list(p.positions.values_list('id', flat=True))
>>> # for p in Player.objects.filter(id__in=[31, 32, 33])]
No doubt a list comprehension will yield the result I would like. I wanted to avoid multiple individual queries to the database with each list(p.positions.values_list('id', flat=True))
forcing the resolution of a ValuesListQuerySet
into an actual list. Of course, if I don't force the resolution, an operation on the result of the list comprehension would seem to execute a fresh p.positions.valuse_list('id', flat=True)
query.
Yeah, I wish there were some grouping query method as well.
I recently had to do something similar with highly nested models to avoid some crippling database calls. I ended up using values()
to efficiently get all the, umm, values I needed. Then, stitched everything together as a dictionary in pure Python. It ended up miles more efficient, but of course the customization has its own maintenance downside.
The gist of it, using the Author/Book models:
# models.py
class Author(models.Model):
name = models.CharField()
class Book(models.Model):
name = models.CharField()
authors = models.ManyToManyField(Author)
Then in a view (or a shell in my case) construct the dictionary you can pass to your template:
>>> authors = Author.objects.prefetch_related('book_set').values('id', 'book')
>>> print authors
[{'book': 1, 'id': 1}, {'book': 2, 'id': 1}, {'book': 2, 'id': 2}]
>>> author_dict = {}
>>> for a in authors:
if a['id'] not in author_dict:
author_dict[a['id']] = set()
author_dict[a['id']].add(a['book'])
>>> print author_dict
{1: set([1, 2]), 2: set([2])}
Not the answer you probably wanted, but it's the only thing I have come up with. I'd love to hear if anyone else has solved the problem with some native Django method.
Hope this helps!