django:在多对多关系的额外领域有黑魔法吗?

时间:2011-08-19 15:55:17

标签: python django many-to-many

来自Django的官方文档:https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships

模型如下:

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)

最后,它给出了一个例子:

# 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]

我的问题是Person模型如何识别组和成员资格属性,因为它们没有在其上定义。是不是通过它或许它在Django中的某种普遍的魔法?

如果我要实现相同的查询,我会认为遵循代码更自然(从Django的角度来看,而不是从业务角度来看):

Membership.objects.filter(group__name='The Beatles', date_joined__gt=date(1961,1,1))).select_related('person')

修改 我再次阅读该文件并确实发现这种背后是普遍的。在第https://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships段中,它提到:

  

它也是倒退的。引用&#34;反向&#34;关系,只是   使用模型的小写名称。

我之前从未使用过此向后查询。所以我第一次看到它时,就撞了我的大脑。对不起,这个帖子结果证明是愚蠢的。但我希望它可以帮助那些跳过该段中非常(细)线的人。

2 个答案:

答案 0 :(得分:2)

它不是ManyToMany字段的神奇之处;它是具有外键(包括ManyToMany)关系的django ORM的通用之一。

,例如,如果你有

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)

您可以执行Musician.objects.filter(band__name='The Beatles')之类的操作。可以使用django-debug-toolbar或django shell查看查询:

from django.db import connection
connection.queries

将对复杂的JOIN语句进行汇编。 ORM构造的SQL查询看起来像:

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";

编辑: 如果您Band.objects.filter(musician__name = 'Ringo Starr'),ORM会将其转换为:

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";

ORM知道关系并可以将您的ORM查询转换为适当的SQL。 Band没有音乐家对象并不重要;你能给django一个明确的要求: 我想要所有的乐队对象,那里有音乐家与乐队联系,名字叫“Ringo Starr”。

我认为你可能会想到对象封装(例如,乐队没有音乐家;你怎么能根据它进行过滤)?它不是黑魔法,因为过滤器请求是明确且明确的 - 并且作为参数而不是Band对象的成员;但要过滤的命令。例如,你可以做Band.objects(name__icontains = "The"),即使Band中没有name__icontains个对象。 ORM将您的请求转换为SQL,这很容易执行。


EDIT2: 你的最后一个例子:

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

然后像Band.objects.filter(musician__name='Paul McCartney')这样的查询不起作用(FieldError),但查询类似 Band.objects.filter(initial_band_members__name='Paul McCartney')将使用以下SQL返回披头士乐队(使用sqlite作为数据库;该命令取决于数据库后端):

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 '

答案 1 :(得分:1)

或许可以查看https://docs.djangoproject.com/en/dev/topics/db/queries/的答案。通常使用连接。