djangodjango-querysetdjango-generic-relations

Django query caching through a ForeignKey on a GenericRelation


Using a GenericRelation to map Records to Persons, I have the following code, which works correctly, but there is a performance problem I am trying to address:

models.py

class RecordX(Record):  # Child class
    ....

class Record(models.Model):  # Parent class
    people = GenericRelation(PersonRecordMapping)

    def people_all(self):
        # Use select_related here to minimize the # of queries later
        return self.people.select_related('person').all()

    class Meta:
        abstract = True

class PersonRecordMapping(models.Model):
    person = models.ForeignKey(Person)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

class Person(models.Model):
    ...

In summary I have:

RecordX ===GenericRelation===> PersonRecordMapping ===ForeignKey===> Person

The display logic in my application follows these relationships to display all the Persons mapped to each RecordX:

views.py

@rendered_with('template.html')
def show_all_recordXs(request):
    recordXs = RecordX.objects.all()
    return { 'recordXs': recordXs }

The problem comes in the template:

template.html

{% for recordX in recordXs %}
    {# Display RecordX's other fields here #}
    <ul>
    {% for map in record.people_all %}{# <=== This generates a query every time! #}
        <li>{{ map.person.name }}</li>
    {% endfor %}
    </ul>
{% endfor %}

As indicated, every time I request the Persons related to the RecordX, it generates a new query. I cannot seem to figure out how prefetch these initially to avoid the redundant queries.

If I try selected_related, I get an error that no selected_related fields are possible here (error message: Invalid field name(s) given in select_related: 'xxx'. Choices are: (none)). Not surprising, I now see -- there aren't any FKs on this model.

If I try prefetch_related('people'), no error is thrown, but I still get the same repeated queries on every loop in the template as before. Likewise for prefetch_related('people__person'). If I try prefetch_related('personrecordmapping'), I get an error.

Along the lines of this answer, I considered doing trying to simulate a select_related via something like:

PersonRecordMapping.objects.filter(object_id__in=RecordX.objects.values_list('id', flat=True))

but I don't understand the _content_object_cache well enough to adapt this answer to my situation (if even the right approach).

So -- how can I pre-fetch all the Persons to the RecordXs to avoid n queries when the page will display n RecordX objects?

Thanks for your help!


Solution

  • Ack, apparently I needed to call both -- prefetch_related('people', 'people__person'). SIGH.