Search code examples
djangoforeign-keysdjango-queryset

Fetching results from a method in Django


My Django app ('crisismode') has two models : Events and Actions

I would like to display events and actions related to these events. For each event, I only want to display active actions, so I used a method (activactions).

How to prefetch these actions ?

class Event(models.Model):
    description = models.CharField(max_length=1000, blank=True)
    active = models.BooleanField(default=True)

    def activeactions(self):
        actions = Action.objects.filter(event=self, active=True)
        if actions:
            return Action.objects.filter(event=self, active=True)
        else:
            return None

class Action(models.Model):
    description = models.CharField(max_length=1000)
    active = models.BooleanField(default=True)
    event = models.ForeignKey(Event, on_delete=models.SET_NULL, related_name="actionsforevent", null=True, blank=True)

I perform the following query:

events = Event.objects.filter(crisis=crisis, active=True).prefetch_related('actionsforevent')

But django-debug-toolbar shows me that there are multiple queries for it :

FROM "crisismode_action"
 WHERE ("crisismode_action"."active" = true AND "crisismode_action"."event_id" = 323)
 ORDER BY "crisismode_action"."order" ASC, "crisismode_action"."id"
 ASC 10 similar queries.  Duplicated 2 times.

Solution

  • Firstly I would state a part from the documentation about prefetch_related:

    prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python.

    Meaning that prefetch related would make multiple queries. Moving on looking at your question you have a method activeactions in which you try to fetch the active actions for an event. Also you try using prefetch related as .prefetch_related('actionsforevent'). This does not work as you expect because the prefetching you do is separate from your method call. Also you don't filter in this prefetching.

    For your use I believe you want to prefetch only the actions whose field active is True. You can do this using the Prefetch object which can take a queryset:

    from django.db.models import Prefetch
    
    events = Event.objects.filter(
        crisis=crisis,
        active=True
    ).prefetch_related(
        Prefetch(
            'actionsforevent',
            queryset=Action.objects.filter(active=True),
            to_attr='active_actions'
        )
    )
    

    We used the to_attr argument to specify that we wanted the results on a custom attribute active_actions.

    Now we can simply access the prefetched active actions by using event.active_actions. e.g.:

    for event in events:
        for action in event.active_actions:
            print(action.description)