Cácio Lucas
Cácio Lucas

Reputation: 23

How to annotate a field with information from a specific manytomany entity?

I have 2 models and a manytomany relationship between then, Property and Owners, they are pretty much the following:

class Property(models.Model):
    name = models.CharField(max_length=50)
    owners = models.ManyToManyField('Owner', through='PropertyOwner')
    ...

class Owner(models.Model):
    name = models.CharField("Owner's name", max_length=50)
    ...

class PropertyOwner(models.Model):
    property = models.ForeignKey('Property', on_delete=models.CASCADE)
    owner = models.ForeignKey('Owner', on_delete=models.CASCADE)
    current = models.BooleanField()

How can I annotate a new field in my Property.objects.all() queryset with the current owner's id. (maybe using .first())

Upvotes: 1

Views: 41

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476544

A simple way to do this is by making a property that will make a query (per Property) to obtain the current owner. This thus looks like:

class Property(models.Model):
    name = models.CharField(max_length=50)
    owners = models.ManyToManyField('Owner', through='PropertyOwner')

    @property
    def current_owner(self):
        return self.owners.filter(propertyowner__current=True).first()

If we however load a lot of Propertys, and we need to find the current owner for each Property, this will result in a lot of queries.

An alternative might be to make a query to obtain the Propertys with the primary key of the current owner, then load all these owners, and finally assigning these owners to the Property objects. This thus looks like:

We can annotate the QuerySet with the primary key of the current owner, and then fetch these in bulk and implement the JOIN logic in Django:

from operator import attrgetter

properties = list(Property.objects.filter(
    propertyowner__current=True
).annotate(
    current_owner_id=F('owners__pk')
))

owners = set(map(attrgetter('current_owner_id'), properties))
owners = {
    owner.pk: owner
    for owner in Owner.objects.filter(pk__in=owners)
}

for property in properties:
    property.current_owner = owners.get(property.current_owner_id)

After this code fragment, properties is a list of Property objects that have an extra attribute: .current_owner which is an Owner object for the person currently owning that Property.

Upvotes: 1

Related Questions