【问题标题】:django: is there a black magic on extra fields on many-to-many relationships?django:多对多关系的额外字段是否有黑魔法?
【发布时间】:2011-08-19 15:55:17
【问题描述】:

来自 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 的 orm 角度来看,而不是从业务角度来看):

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 段中,它提到:

它也可以向后工作。要引用“反向”关系,只需 使用模型的小写名称。

我以前从未使用过这种向后查询。所以我第一次看到它时,它撞到了我的大脑。对不起,这个线程原来是愚蠢的。但我希望它可以帮助那些跳过该段中非常(细)行的人。

【问题讨论】:

    标签: python django many-to-many


    【解决方案1】:

    这不是多对多领域的魔法;它是具有外键(包括多对多)关系的 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,易于快速执行。


    编辑2: 你的最后一个例子:

    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 '
    

    【讨论】:

    • 我完全可以理解你的例子,因为 band 是在 Musician 模型上定义的外键。但在我的问题中, Person 模型没有定义 person 属性。要回答我的问题,请告诉我如何做相反的事情,例如 Band.objects.filter(musician__name = 'Ringo Starr')。
    • 这是相同的基本机制。 django ORM 将您的 django 查询转换为 SQL。我会用一个例子来编辑。
    • 感谢您的解释。我又读了一遍文档,意识到反向查询是可能的。但这是一条线的东西。所以我想我的眼睛以前只是跳过它。
    【解决方案2】:

    也许请查看https://docs.djangoproject.com/en/dev/topics/db/queries/ 以获得您的答案。通常使用连接。

    【讨论】:

    • 这篇文章无法回答我的问题。在我的问题中,Person 模型不知道组或成员资格属性。我知道 django 会给它添加 group_set 和 members_set 管理器。但是组和成员属性?没有 django 文档提到过这种可能性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多