Search code examples
pythondjangodjango-modelsgroup-bydjango-orm

Groupby using Django's ORM to get a dictionary of lists between two models


I have two models, User and Gift:

class User(models.Model):
    name = models.CharField(max_length=150, null=True, blank=True)
    ...


class Gift(models.Model):
    user = models.ForeignKey(
        "User",
        related_name="users",
        on_delete=models.CASCADE,
    )
    ...

Now I want to create a dictionary of lists to have a list of gifts for every user, so that I can lookup pretty fast the ids of gifts that some users have, in a for loop, without having to query the database many times. I came up with this:

from itertools import groupby
gifts_grouped = {
    key: [v.id for v in value] for key, value in groupby(gifts, key=lambda v: v.user_id)
}

Now every time I wish to lookup the gifts of a user, I can simply do:

gifts_grouped[id_of_a_user]

And it'll return a list with ids of gifts for that user. Now this feels like a Python solution to a problem that can easily be solved with Django. But I don't know how. Is it possible to achieve the same results with Django's ORM?


Solution

  • There is no need to do this, you can query with:

    users = User.objects.prefetch_related('users')

    For a user you can then:

    for user in users:
        for gift in user.users.all():
            print(user, ' -> ', gift)
    

    The reason you access these through .users is because of the related_name=.. parameter [Django-doc], which also shows why this is not a good name. You can use:

    from django.conf import settings
    
    
    class User(models.Model):
        name = models.CharField(max_length=150, null=True, blank=True)
        # …
    
    
    class Gift(models.Model):
        user = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            related_name='gifts',
            on_delete=models.CASCADE,
        )
        # …

    Then you access these with:

    users = User.objects.prefetch_related('gifts')
    for user in users:
        for gift in user.gifts.all():
            print(user, ' -> ', gift)
    

    Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.