It’s from Django’s official doc: https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
The models are like:
class Person(models.Model):
name = models.CharField(max_length=128)
def __unicode__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __unicode__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
In the end, it gives such an example:
# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
... group__name='The Beatles',
... membership__date_joined__gt=date(1961,1,1))
[<Person: Ringo Starr]
My question is how Person model is aware of a group and membership attributes since they are not defined on it. Is it just a magic of manytomany with through or it’s some kind of universal one in Django?
If I were to achieve the same query, I would think following code more natural (from Django’s orm perspective, not from business one):
Membership.objects.filter(group__name='The Beatles', date_joined__gt=date(1961,1,1))).select_related('person')
Edit
I read the document again and do find such backwards is universal. In paragraph https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships, it mentions:
It works backwards, too. To refer to a “reverse” relationship, just
use the lowercase name of the model.
I’ve never used this backwards query before. So the first time I saw it, it banged my brain. Sorry that this thread turns out to be stupid. But I hope it helps people who skip the very (thin) line in that paragraph.
Its not a magic of ManyToMany fields; its a universal one of the django ORM with foreign key (including ManyToMany) relations.
E.g., if you have
You can do something like
Musician.objects.filter(band__name='The Beatles')The query can be seen with django-debug-toolbar or from the django shell with:and will be comrpised of a complicated JOIN statement. The SQL query constructed by the ORM will look something like:
EDIT:
If you did
Band.objects.filter(musician__name = 'Ringo Starr')the ORM would translate it into:The ORM knows the relationships and can convert your ORM query into the appropriate SQL. It doesn’t matter that Band doesn’t have a musician object; you were able to give django a clear request:
I want all band objects, where there’s musician linked to the band with the name ‘Ringo Starr’.
I think you may be hung up on thinking about object encapsulation (e.g., band doesn’t have a musician; how can you filter based upon it)? Its not black magic, as the filter request is clear and unambiguous — and takes as arguments not members of the Band object; but commands to filter by. E.g., you could do
Band.objects(name__icontains = "The"), even though there’s noname__icontainsobject in Band. The ORM converts your request into SQL, which is simple to execute quickly.EDIT2:
Your final example:
Then queries like
Band.objects.filter(musician__name='Paul McCartney')don’t work (FieldError), but queries likeBand.objects.filter(initial_band_members__name='Paul McCartney')will give back The Beatles found with the following SQL (using sqlite as a db; the command is dependent on the DB backend):