django: is there a black magic on extra fields on many-to-many relationships?
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开发者_如何学Python. 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
class Musician(models.Model):
    name = models.CharField(max_length=128)
    band = models.ForeignKey("Band")
class Band(models.Model):
    name = models.CharField(max_length=128)
    genre = models.CharField(max_length=50)
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:
from django.db import connection
connection.queries
and will be comrpised of a complicated JOIN statement. The SQL query constructed by the ORM will look something like:
SELECT "appname_musician"."id", "appname_musician"."name", "appname_musician"."band_id" 
    FROM "appname_musician" 
    INNER JOIN "appname_band" ON ("appname_musician"."band_id" = "appname_band"."id") 
    WHERE "appname_band"."name" = "The Beatles";
EDIT:
If you did  Band.objects.filter(musician__name = 'Ringo Starr') the ORM would translate it into: 
SELECT "appname_band"."id", "appname_band"."name", "appname_band"."genre"
    FROM "appname_band" 
    INNER JOIN "appname_musician" ON ("appname_musician"."band_id" = "appname_band"."id") 
    WHERE "appname_musician"."name" = "Ringo Starr";
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 no name__icontains object in Band.  The ORM converts your request into SQL, which is simple to execute quickly.
EDIT2: Your final example:
class Musician(models.Model):
    name = models.CharField(max_length=128)
    initial_band = models.ForeignKey("Band", related_name = 'initial_band_members')
    final_band = models.ForeignKey("Band", related_name = 'final_band_members')
class Band(models.Model):
    name = models.CharField(max_length=128)
    genre = models.CharField(max_length=50)
try: ## create some objects
    cream,_ = Band.objects.get_or_create(name="Cream", genre="Classic Rock")
    derek, _ = Band.objects.get_or_create(name="Derek and the Dominos", genre="Classic Rock")
    beatles, _ = Band.objects.get_or_create(name="The Beatles", genre="Classic Rock")
    wings, _ = Band.objects.get_or_create(name="Wings", genre="Classic Rock")
    Musician.objects.get_or_create(name="Eric Clapton", initial_band=cream, final_band=derek)
    Musician.objects.get_or_create(name="Paul McCartney", initial_band=beatles, final_band=wings)
    Musician.objects.get_or_create(name="John Lennon", initial_band=beatles, final_band=beatles)
except:
    pass
Then queries like Band.objects.filter(musician__name='Paul McCartney') don't work (FieldError), but queries like 
Band.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):
SELECT "testing_band"."id", "testing_band"."name", "testing_band"."genre" 
  FROM "testing_band" 
  INNER JOIN "testing_musician" ON ("testing_band"."id" = "testing_musician"."final_band_id") 
  WHERE "testing_musician"."name" = Paul McCartney '
Perhaps look into https://docs.djangoproject.com/en/dev/topics/db/queries/ for your answer. In general joins are used.
 
         加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论