我需要在两个模型之间创建一个多对多关系。 在 Django 中,你通常会为两个模型之一添加一个ManyToManyField
。 但是,就我而言:
- 这两个模型来自第三方库,
User
和Car
- 两者都是非抽象的,例如,我想避免多表继承的复杂性,只是为了添加一个 M2M 字段(实际上不需要在两个表中增加列(
- 我的工作假设其他人会像(如果不是字面意思的话(一样使用它作为第三方库,所以我真的很想提供标准接口,尽管有限制。
显然,我可以创建一个"手动"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_class
和contribute_to_related_class
(来完成这项工作? 我尝试通过ForeignKey
和ManyToMany
来费解,但我对正常的字段级处理不够熟悉,甚至无法猜测关键方法(更不用说排序和延迟处理了(。
在注释中,@AlexandrTatarinov建议在创建through
表后立即使用add_to_class
向Car
添加ManyToManyField
。 我仍然愿意接受更好的答案,但这肯定是一个答案(目前是最好的答案(。 假设我们使用如下代码:
# 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.places
和place.users
正常工作( - 由于 M2M 表被定义为
through
表,因此像place.users.add()
这样的方法不能开箱即用(即使 M2M 表与字段自动创建的表完全相同(。
由于auto_created=False
手动创建的through=
模型,因此引发了place.users.add()
问题。 如果我在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
中管理自动创建的表有关。
编辑2:我还尝试将信号附加到post_migrate
。 这适用于测试(因为它们总是迁移(,但在生产中不起作用。request_started
似乎对两者都有效。
借助此增强功能,在应用中为Car
创建的额外迁移是此策略的唯一主要问题。