Changes between Initial Version and Version 1 of ManyToManyManager


Ignore:
Timestamp:
May 31, 2007, 3:31:35 PM (17 years ago)
Author:
Marty Alchin <gulopine@…>
Comment:

Initial document

Legend:

Unmodified
Added
Removed
Modified
  • ManyToManyManager

    v1 v1  
     1While the existing `ManyToManyField` is suitable for basic relationships, some projects find need to tie objects together along with some information about their relationship. The common example is the role an actor played in a movie, but it could be used for many other things, including what "base" a dating couple has gotten to, for instance. For these cases, the common recommendation is to simply create an intermediary model with `ForeignKey`s to each of the connected models, along with any extra fields that are appropriate for the relationship.
     2
     3In the Hollywood example, something like this:
     4
     5{{{
     6#!python
     7class Actor(models.Model):
     8    name = models.CharField(maxlength=255)
     9
     10class Movie(models.Model):
     11    title = models.CharField(maxlength=255)
     12    actors = models.ManyToManyField(Actor)
     13}}}
     14
     15would become something like this instead, with the `role` field added:
     16
     17{{{
     18#!python
     19class Actor(models.Model):
     20    name = models.CharField(maxlength=255)
     21
     22class Film(models.Model):
     23    title = models.CharField(maxlength=255)
     24
     25class Role(models.Model):
     26    actor = models.ForeignKey(Actor, related_name='roles')
     27    film = models.ForeignKey(Film, related_name='roles')
     28    role = models.CharField(maxlength=255)
     29}}}
     30
     31Unfortunately, the database API then goes from this:
     32
     33{{{
     34#!python
     35>>> for actor in film.actors.all():
     36...     print actor.name
     37Graham Chapman
     38Terry Gilliam
     39John Cleese
     40Eric Idle
     41}}}
     42
     43to this:
     44
     45{{{
     46#!python
     47>>> for role in film.roles.all():
     48...     print '%s played %s' % (role.actor.name, role.role)
     49Graham Chapman played King Arthur
     50Terry Gilliam played Sir Bors
     51John Cleese played Sir Lancelot the Brave
     52Eric Idle played Sir Robin the Not-Quite-So-Brave-as-Sir Launcelot
     53}}}
     54
     55While programmers may not have a problem with this change, it is very counter-intuitive for template authors who may not have (nor should they need) an intimate understanding of how relational databases work. I'd like to propose an alternative, using a custom manager for these intermediary models, that will enable a more natural API.
     56
     57By simply adding one line and renaming the `related_name`s:
     58
     59{{{
     60#!python
     61class Actor(models.Model):
     62    name = models.CharField(maxlength=255)
     63
     64class Film(models.Model):
     65    title = models.CharField(maxlength=255)
     66
     67class Role(models.Model):
     68    actor = models.ForeignKey(Actor, related_name='films')
     69    film = models.ForeignKey(Film, related_name='actors')
     70    role = models.CharField(maxlength=255)
     71
     72    objects = models.ManyToManyManager()
     73}}}
     74
     75we could get a much more intuitive API:
     76
     77{{{
     78#!python
     79>>> for actor in film.actors.all():
     80...     print '%s played %s' % (actor.name, actor.role)
     81Graham Chapman played King Arthur
     82Terry Gilliam played Sir Bors
     83John Cleese played Sir Lancelot the Brave
     84Eric Idle played Sir Robin the Not-Quite-So-Brave-as-Sir Launcelot
     85}}}
     86
     87This would work just as well in both directions:
     88
     89{{{
     90#!python
     91>>> for film in john.films.all():
     92...     print '%s in %s' % (film.role, film.title)
     93Sir Lancelot the Brave in Monty Python and the Holy Grail
     94Sir Nicholas de Mimsy-Porpington in Harry Potter and the Sorceror's Stone
     95}}}
     96
     97Ideally, this API would also extend the `add` method of the manager, allowing it to take keyword attributes for the relationship meta-data:
     98
     99{{{
     100#!python
     101>>> film = Film.objects.create(title='And Now for Something Completely Different')
     102>>> john.films.add(film, role='Sir George Head')
     103>>> for film in john.films.all():
     104...     print '%s in %s' % (film.role, film.title)
     105Sir Lancelot the Brave in Monty Python and the Holy Grail
     106Sir Nicholas de Mimsy-Porpington in Harry Potter and the Sorceror's Stone
     107Sir George Head in And Now for Something Completely Different
     108}}}
     109
     110And without the intermediary model, there's need for an `update` function on the manager, which would handle modifying the meta-data:
     111
     112{{{
     113#!python
     114>>> film.actors.update(john, role='Mungo the Cook')
     115}}}
Back to Top