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.
精彩评论