Is there a way to enforce ordering in a many-to-many queryset?

Hey folks. I’m an intermediate Pythonista who’s learning Django and I’m trying to create a project for my existing Blu-Ray collection. The collection is currently stored as a Nornir project, and I’m running into some complexities as part of the transition.

Here’s an example of the data as it’s currently stored in my Nornir inventory:

2001-a_space_odyssey:
  groups:
    - hdr10_dv
  data:
    title: '2001: A Space Odyssey'
    year: 1968
    runtime: 149
    director: Stanley Kubrick
    crew:
      writer:
        - Stanley Kubrick
        - Arthur C. Clarke
      cinematographer: Geoffrey Unsworth
      prod_designer:
        - Tony Masters
        - Harry Lange
        - Ernest Archer
      composer: null
      editor: Ray Lovejoy
    release:
      publisher: Warner Bros. Entertainment Inc
      upc: 883929671526
      discs: 2
      aspect_ratio: 2.2
    mpaa:
      certificate: 22197
      rating: G
      reason: null
      distributor: MGM Film Company
      alt_title: null
    genres:
      - drama
      - science fiction
  sort_key: 2001-a_space_odyssey

And here’s a look at the Movie model class I’ve built in Django:

class Movie(models.Model):
    """Aggregate model for movies."""
    title = models.CharField(max_length=150)
    slug = models.CharField(verbose_name="Sort Key", max_length=150)
    publisher = models.ForeignKey(Publisher, on_delete=models.PROTECT, null=True, blank=True)
    discs = models.IntegerField(verbose_name="Number of Discs", null=True, blank=True)
    year = models.ForeignKey(Year, on_delete=models.PROTECT, null=True, blank=True)
    run_time = models.IntegerField()
    aspect_ratio = models.ForeignKey(AspectRatio, on_delete=models.PROTECT, null=True, blank=True)
    director = models.ManyToManyField(Director)
    writer = models.ManyToManyField(Writer, blank=True)
    dp = models.ManyToManyField(Cinematographer, verbose_name="Director of Photography", blank=True)
    editor = models.ManyToManyField(Editor, blank=True)
    prod_designer = models.ManyToManyField(ProdDesigner, verbose_name="Production Designer", blank=True)
    composer = models.ManyToManyField(Composer, blank=True)

As you can see, the crew models attrs are all many-to-many fields (a film can have many writers, and a writer can have written many films). Here’s where I’m falling down:

I’d like to preserve the existing elements’ positions in their respective lists. The ordering of shared credits in a movie carries meaning: for writers, it can be the order ascending in which they worked on the film (position 0 wrote the first draft, position 1 wrote the second)…for editors, it’s the order descending of their contribution to the film (position 0 has the most input, position 1 has the next most, etc).

Since I have a large dataset, I’ll be writing some sort of conversion tool to load the data into Django. But since Django’s going to access the values in PK ascending order, any external context for the hierarchy based on the elements’ position is lost.

TL;DR - is there a way to preserve the elements’ positions as they’re added to the movie model’s attrs?

Welcome @architekture !

Quick answer - Yes, but it’s done by adding a sequencing attribute to the through model that joins the Writer to a Movie. You would also be responsible to assigning the values to that attribute. See Extra fields on many-to-many relationships for more information about this.

Thank you for the welcome and the quick reply. Unfortunately, the example doesn’t exactly clear up the logic for me. Perhaps you can help me understand it a little better?

Let’s say I have a Python list in the desired order which I’m loading into my models. The attrs in the intermediary model in the example is listing join dates of the band in a tuple, but what I want I think is simpler than that. Would it be possible to for loop over the list and then do a list comprehension or similar to assign it a value? for instance, can slice 0 of the incoming writers list be given seq = 0 and so on? If so, is there any good documentation you can recommend as an example for this kind of behavior?

What I’m doing may end up being more complicated than it’s worth in the short term, but ideally I’d like to preserve the ordering for my own OCD and better understanding of the framework.

I find that a lot of examples and tutorials I’ve read skip over the logic of the underlying Python framework, and I’ve seen some rather un-Pythonic examples thrown around, especially where YouTube is concerned.

Logically, there is no difference. Mentally convert the usage of dates to integers, and the principles are the same.

Mechanically, some of the details will depend upon specifically how you are entering this data.

But speaking in general terms, assume you have a model defined in this pattern:

class MovieWriter(models.Model):
    movie = models.ForeignKey(Movie, ...)
    writer = models.ForeignKey(Writer, ...)
    sequence = models.IntegerField(...)

Your definition in Movie might then look like this:

    writer = models.ManyToManyField(Writer, through='MovieWriter')

Given this, when you want to add a relationship between an instance of Movie named a_movie and an instance of Writer named a_writer, with a sequence number of n, you’ve got two choices.

First, you can use the regular add method:

a_movie.writer.add(a_writer, through_defaults={'sequence': n})

Or, you can directly create the instance of the through model:
MovieWriter.objects.create(movie=a_movie, writer=a_writer, sequence=n)

Both of these are the functional equivalent of what’s in the docs.

1 Like