如何在 Django 中表达一对多关系?

Posted

技术标签:

【中文标题】如何在 Django 中表达一对多关系?【英文标题】:How to express a One-To-Many relationship in Django? 【发布时间】:2011-10-19 05:05:37 【问题描述】:

我现在正在定义我的 Django 模型,我意识到模型字段类型中没有 OneToManyField。我确定有办法做到这一点,所以我不确定我错过了什么。我基本上有这样的东西:

class Dude(models.Model):
    numbers = models.OneToManyField('PhoneNumber')

class PhoneNumber(models.Model):
    number = models.CharField()

在这种情况下,每个Dude 可以有多个PhoneNumbers,但关系应该是单向的,因为我不需要从PhoneNumber 中知道Dude 拥有它本身,因为我可能有许多不同的对象拥有 PhoneNumber 实例,例如 Business

class Business(models.Model):
    numbers = models.OneToManyField('PhoneNumber')

我会在模型中用什么替换OneToManyField(不存在)来表示这种关系?我来自 Hibernate/JPA,在这里声明一对多关系很简单:

@OneToMany
private List<PhoneNumber> phoneNumbers;

如何在 Django 中表达这一点?

【问题讨论】:

【参考方案1】:

要在 Django 中处理一对多关系,您需要使用 ForeignKey

ForeignKey 的文档非常全面,应该可以回答您的所有问题:

https://docs.djangoproject.com/en/3.2/ref/models/fields/#foreignkey

您示例中的当前结构允许每个 Dude 拥有一个号码,并且每个号码属于多个 Dudes(与 Business 相同)。

如果您想要反向关系,则需要将两个 ForeignKey 字段添加到您的 PhoneNumber 模型,一个给 Dude,一个给 Business。这将允许每个号码属于一个 Dude 或一个 Business,并且 Dudes 和 Businesses 能够拥有多个号码。我想这可能就是你所追求的。

class Business(models.Model):
    ...
class Dude(models.Model):
    ...
class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)
    business = models.ForeignKey(Business)

【讨论】:

你能给我举个例子吗?我可能只是完全想念它,但我已经阅读 Django 文档有一段时间了,仍然不清楚如何创建这种关系。 可能想让两个 ForeignKeys 都不需要(空白=True,null=True),或者添加某种自定义验证以确保至少有一个或另一个。拥有通用编号的企业情况如何?还是失业的家伙? @j_syk 关于自定义验证的好点。但是同时包含一个外键给花花公子和一个外键给业务,然后进行自定义(模型定义外部)验证似乎有点hacky。似乎必须有一种更清洁的方法,但我也想不通。 一个相关的事情要提到的是 ForeignKey 的“related_name”参数。因此,在 PhoneNumber 类中,您将拥有 dude = models.ForeignKey(Dude, related_name='numbers'),然后您可以使用 some_dude_object.numbers.all() 获取所有相关号码(如果您未指定“related_name”,它将默认为“number_set”)。跨度> 是否可以修改此号码以使该号码可以属于 Business 或 Dude,但不能同时属于两者?我还想知道是否可以轻松地将数字从 Business 切换到 Dude,反之亦然。【参考方案2】:

在 Django 中,一对多的关系称为 ForeignKey。但是,它仅在一个方向上起作用,因此您需要的不是 numberDude 的属性

class Dude(models.Model):
    ...

class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)

许多模型可以有一个 ForeignKey 到另一个模型,所以第二个属性 PhoneNumber 是有效的,这样

class Business(models.Model):
    ...
class Dude(models.Model):
    ...
class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)
    business = models.ForeignKey(Business)

您可以使用d.phonenumber_set.objects.all() 访问PhoneNumbers 以获取Dude 对象d,然后对Business 对象执行类似操作。

【讨论】:

我假设ForeignKey 的意思是“一对一”。使用上面的示例,我应该有一个 Dude 有很多 PhoneNumbers 对吗? 我编辑了我的答案以反映这一点。是的。如果您指定ForeignKey(Dude, unique=True)ForeignKey 仅是一对一的,因此使用上面的代码您将得到一个带有多个PhoneNumbers 的Dude @rolling stone- 谢谢,我在意识到我的错误后补充说,正如你评论的那样。 Unique=True 与 OneToOneField 不完全一样,我的意思是,如果您指定 Unique=True,ForeignKey 仅使用一对一的关系。 +1 来自PhoneNumber。现在它开始变得有意义了。 ForeignKey 本质上是多对一的,因此您需要向后执行才能获得一对多:) 有人能解释一下字段名称phonenumber_set的意义吗?我没有看到它在任何地方定义。它是模型的名称,全小写,附加“_set”吗?【参考方案3】:

更清楚一点 - Django 中没有 OneToMany,只有 ManyToOne - 这是上面描述的外键。您可以使用 Foreignkey 来描述 OneToMany 关系,但这非常不具表现力。

一篇关于它的好文章: https://amir.rachum.com/blog/2013/06/15/a-case-for-a-onetomany-relationship-in-django/

【讨论】:

你可以使用ManyToManyField。【参考方案4】:

您可以在OneToMany 关系的多方使用外键(即ManyToOne 关系)或使用具有唯一约束的ManyToMany(在任何一方)。

【讨论】:

【参考方案5】:

Django 足够聪明。实际上我们不需要定义oneToMany 字段。它将由 Django 自动为您生成。我们只需要在相关表中定义一个foreignKey。也就是说,我们只需要使用foreignKey定义ManyToOne关系即可。

class Car(models.Model):
    # wheels = models.oneToMany() to get wheels of this car [**it is not required to define**].


class Wheel(models.Model):
    car = models.ForeignKey(Car, on_delete=models.CASCADE)  

如果我们想获取特定汽车的车轮列表,我们将使用 Python 的自动生成对象wheel_set。对于汽车c,您将使用c.wheel_set.all()

【讨论】:

也可以用ForeignKey(related_name=...)修改自动生成的名字【参考方案6】:

虽然rolling stone 的回答很好、直接且实用,但我认为它没有解决两件事。

    如果 OP 想要强制一个电话号码不能同时属于 Dude 和 Business 由于在 PhoneNumber 模型上而不是在 Dude/Business 模型上定义关系而产生的不可避免的悲伤感。当外星人来到地球时,我们想要添加外星人模型,我们需要修改电话号码(假设外星人有电话号码),而不是简单地在外星人模型中添加“phone_numbers”字段。

引入content types framework,它公开了一些允许我们在PhoneNumber 模型上创建“通用外键”的对象。然后,我们可以定义 Dude 和 Business 的反向关系

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models

class PhoneNumber(models.Model):
    number = models.CharField()

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    owner = GenericForeignKey()

class Dude(models.Model):
    numbers = GenericRelation(PhoneNumber)

class Business(models.Model):
    numbers = GenericRelation(PhoneNumber)

查看docs 了解详细信息,或者查看this article 以获得快速教程。

另外,an articlean article反对使用通用 FK。

【讨论】:

【参考方案7】:

首先我们参观一下:

01) 一对多关系:

ASSUME:

class Business(models.Model):
    name = models.CharField(max_length=200)
    .........
    .........
    phone_number = models.OneToMany(PhoneNumber) (NB: Django do not support OneToMany relationship)

class Dude(models.Model):
    name = models.CharField(max_length=200)
    .........
    .........
    phone_number = models.OneToMany(PhoneNumber) (NB: Django do not support OneToMany relationship)

class PhoneNumber(models.Model):
    number = models.CharField(max_length=20)
    ........
    ........

注意:Django 不提供任何 OneToMany 关系。所以我们不能在Django中使用upper方法。但是我们需要在关系模型中进行转换。所以,我们能做些什么?在这种情况下,我们需要将关系模型转换为反向关系模型。

这里:

关系模型 = OneToMany

所以,反向关系模型 = ManyToOne

注意:Django 支持 ManyToOne 关系 & 在 Django ManyToOne 中由 ForeignKey 表示。

02) 多对一关系:

SOLVE:

class Business(models.Model):
    .........
    .........

class Dude(models.Model):
    .........
    .........

class PhoneNumber(models.Model):
    ........
    ........
    business = models.ForeignKey(Business)
    dude = models.ForeignKey(Dude)

注意:简单思考!!

【讨论】:

您好!如果我希望 Business admin.py 页面包含电话号码,在这种情况下应该怎么做?【参考方案8】:

如果“多”模型不能证明创建模型本身是合理的(这里不是这种情况,但它可能会使其他人受益),另一种选择是依赖特定的 PostgreSQL 数据类型,通过 @987654321 @

Postgres 可以处理 Array 或 JSON 数据类型,当 many-ies 只能绑定到单个时,这可能是处理一对多的好方法one 的实体。

Postgres 允许您访问数组的单个元素,这意味着查询可以非常快,并避免应用程序级别的开销。当然,Django implements a cool API 也可以利用此功能。

它显然有不能移植到其他数据库后端的缺点,但我认为它仍然值得一提。

希望它可以帮助一些正在寻找想法的人。

【讨论】:

【参考方案9】:

实际上,一对多的关系非常有用。我做这样的事情:

class Dude(models.Model):
    number = models.ManyToManyField(PhoneNumber)

    def save(self, *args, **kwargs):
        if Dude.objects.get(number=self.number):
            raise Exception("Dude, this number has been used.")
        return super(Dude, self).save(*args, **kwargs)

class PhoneNumber(models.Model):
    number = models.CharField(...)

这将确保该号码只使用一次。

【讨论】:

【参考方案10】:

一对多关系意味着一个模型记录可以有许多其他模型记录与其关联。

from django.db import models

class Menu(models.Model):
    name = models.CharField(max_length=30)

class Item(models.Model):
    menu = models.ForeignKey(Menu)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)

【讨论】:

以上是关于如何在 Django 中表达一对多关系?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 1.9 中表达 sqlite LEFT OUTER JOIN?

如何在 Eclipse PDE 中表达项目间依赖关系

如何在 django 中从一对多关系中检索数据?

如何使用 Django 查询从一对多关系中获取数据

如何借助一对多关系在 Django 中使用外键从课程中获取主题

如何在 App Engine 中表示一对一的关系