1

I have two models — Note and Pinboard — with a many to many relationship in a Django app. Those two models are related trough another model — Pin — so I can store additional information about the relationship.

I want to show the related Note instances in the DetailView for Pinboard. That's not the problem. But I want to prefetch the notes and also order them on the position field from the through table.

Any hints on how to archive this (prefetch + ordering on third table)?

Example

This is what I have so far… it works in the sense, that I don't have to query for each entry, but I found no way to order the Note instances by their position without more queries for each instance.

Models

from django.db import models


class Note(models.Model):

    title = models.CharField(max_lenght=200)

    content = models.TextField()


class Pinboard(models.Model):

    title = models.CharField(max_lenght=200)

    notes = models.ManyToManyField(
        Note, blank=True, related_name='pinboards', through='Pin'
    )


class Pin(models.Model):

    class Meta:
        ordering = ['position', ]
        get_latest_by = 'position'

    pinboard = models.ForeignKey(Pinboard, related_name='note_pins')

    note = models.ForeignKey(Note, related_name='pinboard_pins')

    position = models.PositiveSmallIntegerField(default=0)

View

from django.views.generic import DetailView


class PinboardDetailView(DetailView):

  model = Pinboard

  queryset = Pinboard.objects.prefetch_related('notes')

Template

{% extends 'base.html' %}
{% block content %}
<h1>{{ pinboard.title }}</h1>
{% if pinboard.notes.all.exists %}
    <ol>
    {% for note in pinboard.notes %}
        <li>{{ note.title }}</li>
    {% endfor %}
    </ol>
{% else %}
    <p>Nothing here yet…</p>
{% endif %}
{% endblock content %}
Brutus
  • 7,139
  • 7
  • 36
  • 41

1 Answers1

6

I suggest you use a Prefetch object.

class PinboardDetailView(DetailView):
    model = Pinboard
    queryset = Pinboard.objects.prefetch_related(
        Prefetch(
            'notes',
            Note.objects.order_by('pinboard_pins__position'),
        )
    )

By the way, you don't need to use prefetch_related at all on a DetailView as it will result in the same number of queries.

Plus, since you're already fetching the pinboard.notes I suggest you use {% if pinboard.notes.all %} instead of {% if pinboard.notes.all.exists %}.

Simon Charette
  • 5,009
  • 1
  • 25
  • 33
  • 2
    No magic here :) The `prefetch_related` method help solve the N + 1 query issue but in this case N = 1. See https://stackoverflow.com/questions/97197/what-is-the-n1-selects-issue – Simon Charette Feb 17 '16 at 19:03