Question

I'm trying to figure out the Django ORM and I'm getting a little confused on the equivalent of a table join.

Assume I have the three models below, abbreviated for readability. "User" is the out-of-the-box Django user model, "AppUser" is a corresponding model for storing additional profile info like zip code, and "Group" is a collection of users (note that this group has nothing to do with authentication groups). Each AppUser has a one-to-one relationship with each User. Similarly, each AppUser also points to the Group of which that user is member (which could be None).

My task: given a group_id, generate a list of all the member emails.

I know that I can "traverse backward one-level" from Group to AppUser using .appuser_set.all() but I don't know how to go further to fetch the related User and its email without iterating through and doing it in a very DB heavy way like this:

appUser = AppUser.objects.get(user_id=request.user.id)
group = appUser.group
appUsers = group.appuser_set.all()
emails = []
for x in appUsers:
    emails.append(x.user.email) 

Alternatively I could write a raw SQL join to do it but suspect the Django ORM is capable. What's the proper and most efficient way to do this?

Models:

class User(Model):
    id = IntegerField()
    username = CharField()
    email = CharField()

    class Meta:
        db_table = "auth_user"


class AppUser(Model):
    user = OneToOneField(User, primary_key=True)
    group = ForeignKey('Group', null=True, on_delete=SET_NULL)
    zip = CharField()

    class Meta:
        db_table = "app_users"


class Group(Model):                
    creator = ForeignKey(AppUser, related_name='+')
    created = DateTimeField()
    name = CharField()

    class Meta:
        db_table = "groups"
Was it helpful?

Solution

The trick is to always start from the model you actually want to fetch. In this case, you want to fetch emails, which is a field on the User model. So, start with that model, and use the double-underscore syntax to follow the relationships to Group:

users = User.objects.filter(appuser__group_id=group_id)

In this case, you actually only need a single JOIN, because group_id is a field on AppUser itself (it's the underlying db field for the group ForeignKey). If you had the group name, you would need two joins:

users = User.objects.filter(appuser__group__name=group_name)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top