确定 M2M 字段是否具有自定义直通模型

Determine if an M2M field has a custom through model

对于 M2M 关系的自定义直通模型,.add().create().remove() 被禁用。

目前,我尝试使用 .add()(或其他)并捕获并处理那些自定义 M2M 关系的 AttributeError

是否有 'official' 方法通过模型识别自定义,使用 Meta API 或其他方式?在我处理的这个阶段,我宁愿通过关系尽可能通用地处理所有自定义(而不是大量 if m2m_field.related.through == FooBar 语句)

(为 Django 1.8 找到了一个解决方案,但我开始为 2.2 悬赏)

看起来好像

m2m_field.related.through._meta.auto_created is False

完成任务。

从 Django 2.2 开始,.add().create() 等方法可以使用自定义 through 模型,只要您为所需字段提供相应的值使用 through_defaults:

的中间模型

来自documentation:

You can also use add(), create(), or set() to create relationships, as long as you specify through_defaults for any required fields:

>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})

You may prefer to create instances of the intermediate model directly.

.remove() 方法的行为需要一点注意:

If the custom through table defined by the intermediate model does not enforce uniqueness on the (model1, model2) pair, allowing multiple values, the remove() call will remove all intermediate model instances:

>>> Membership.objects.create(person=ringo, group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>

关于 _meta 字段我无法从 m2m 字段访问它,但文档的上述部分似乎允许避免访问 _meta 的“体操”。
如果我发现任何有趣的事情,我会相应地更新我的答案。

对于django 2.2你可以直接检查through模型是否自动创建

这可以通过像下面这样的直接检查来检查

# check for the auto_created value
m2m_field.through._meta.auto_created == False

为了对此进行测试,我创建了一些具有多个 M2M 字段的示例类,一个具有自定义直通字段,一个具有默认 class。

from django.db import models
import uuid


class Leaf(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(null=True, max_length=25, blank=True)


# Create your models here.
class MainTree(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    new_leaves = models.ManyToManyField(Leaf, through='LeafTree')
    leaves = models.ManyToManyField(Leaf, related_name='main_branch')


class LeafTree(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    tree = models.ForeignKey(MainTree, on_delete=models.CASCADE)
    leaf = models.ForeignKey(Leaf, on_delete=models.CASCADE)

测试我们的方法

In [2]: from trees.models import MainTree                                                                                                                                                                

In [3]: m = MainTree()                                                                                                                                                                                   

In [4]: m.leaves.through._meta                                                                                                                                                                           
Out[4]: <Options for MainTree_leaves>

In [5]: m.leaves.through._meta.auto_created                                                                                                                                                              
Out[5]: trees.models.MainTree

In [6]: m.new_leaves.through._meta.auto_created                                                                                                                                                          
Out[6]: False

In [7]: m.new_leaves.through._meta.auto_created == False                                                                                                                                                 
Out[7]: True

In [8]: m.leaves.through._meta.auto_created == False                                                                                                                                                     
Out[8]: False