【问题标题】:Adding Django M2M Relationship between two 3rd Party Models在两个 3rd 方模型之间添加 Django M2M 关系
【发布时间】:2018-05-07 21:03:59
【问题描述】:

我需要在两个模型之间创建多对多关系。在 Django 中,您通常会将 ManyToManyField 添加到两个模型之一。然而,就我而言:

  • 这两个模型来自第 3 方库,分别是 UserCar
  • 两者都不是抽象的,我想避免,例如,多表继承的复杂性只是添加一个 M2M 字段(这实际上不需要任何一个表中的额外列)
  • 我正在假设其他人会像(如果不是字面上那样)使用它作为 3rd 方库,所以我真的希望提供标准接口,尽管有限制。

显然,我可以创建一个“手动”M2M 表:

class UserCar(models.Model):
    user = models.ForeignKey(User)
    car = models.ForeignKey(Car)

    class Meta:
        unique_together = ('user', 'car')

...但 Django 会将两个 ForeignKey 字段视为多对一,因此标准的“魔术”访问器将不可用:

user.cars.add(car)
car in user.cars
User.objects.filter(cars=car)

有没有一种简单的方法可以在关系双方注入通常的 M2M 魔法? UserCar 基本上是一个 through= 表。我可以创建一个虚拟字段并调用一两个方法(例如contribute_to_classcontribute_to_related_class)来完成这项工作吗?我尝试通过ForeignKeyManyToMany 来解惑,但我对正常的字段级处理不够熟悉,甚至无法猜测关键方法(更不用说排序和延迟处理)。

【问题讨论】:

  • 您可以尝试使用User.add_to_class('cars', models.ManyToManyField(....)),但我对此不确定。它可能会抱怨迁移,如果这是第三方软件包,我不确定接下来会发生什么。也许它会创建对该包的新迁移)。不管怎样,给它一个机会。 P.S. 它也会调用你提到的contribute_to_class。
  • 我一定会试一试的。 “直通”模型应该以任何一种方式创建......要么它被选为直通模型,要么它不是,它只是一个自己创建的一等公民。
  • 是的,我明白。但是,即使使用 through 表添加 ManyToManyField 时,django 仍会创建迁移以跟踪迁移历史记录中的该字段,即使这不会更改数据库中的任何内容。我现在不确定,但机会很大)
  • 我使用了一个简单的if not hasattr 来防止重复调用add_to_class。它添加了该字段,但迁移是在与Car 相同的包中创建的(我将该字段添加到Car),因此它们不会被提交到我的存储库中。
  • 嗯。可以在第三方内部创建迁移吗?也许他们是一种只添加相关管理器的方法?也许你应该看看 ManyToManyField.contribute_to_class...

标签: django django-models many-to-many


【解决方案1】:

在 cmets 中,@AlexandrTatarinov 建议在创建 through 表后立即使用 add_to_classManyToManyField 添加到 Car。我仍然愿意接受更好的答案,但这肯定是一个答案(也是目前最好的答案)。假设我们使用如下代码:

# guard against repeated import
if not hasattr(Car, 'users'):
    field = models.ManyToManyField(User, through=UserCar, related_name='cars')
    field.contribute_to_class(Car, 'users')

这种方法的效果(包括缺点)是:

  • 魔术访问器被添加到关系的双方
  • 迁移(添加 M2M 字段)被放置在应用程序中,用于Car。此迁移不会提交到我的包的存储库,可能会导致一些奇怪的问题。例如,
    • 如果开发人员将我的包用作依赖项,则在开发人员调用makemigrations 时将(重新)创建迁移。如果稍后升级 Car 包(并包含新的迁移),这可能会导致开发机器上出现类似“两个叶节点”的错误。
  • 当我删除 Car 迁移以模拟生产场景案例时,一个简单的测试确实工作(例如,在创建两个相关对象后,user.placesplace.users 工作正常)
  • 因为 M2M 表被定义为 through 表,所以像 place.users.add() 这样的方法不能开箱即用(即使 M2M 表与字段自动创建的表完全相同)。

place.users.add() 的问题是因为手动创建的 through= 模型上的 auto_created=False 而引发的。如果我在UserCar.Meta 中设置auto_created=Car,那么神奇的方法会起作用,但不会创建迁移。为了利用默认迁移通过行为恢复,我可以使用以下内容:

@receiver(request_started)
def set_auto_created(**kwargs):
    UserCar._meta.auto_created = Car

编辑:在较早的迭代中,我使用的是auto_created=True。这导致TransactionTestCase 中的FLUSH 出现问题。在挖掘 M2MField 代码后,看起来auto_created 需要指向持有ManyToManyField 的模型。我认为这与如何在TransactionTestCase 中管理自动创建的表有关。

EDIT2:我还尝试将信号附加到post_migrate。这适用于测试(因为它们总是迁移)但在生产中不起作用。 request_started 似乎对两者都有效。

通过此增强功能,在应用中为Car 创建的额外迁移是此策略的唯一主要问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-01
    • 1970-01-01
    • 2012-09-12
    • 1970-01-01
    相关资源
    最近更新 更多