当有多个 model 通过 ManyToManyField 关联到同一个目标 model 时,如果这个 ManyToManyField 需要额外的字段,则需要单独定义一个中间表。
这时中间表除了指向的源表是不一样的,其他字段包括目标表都是一样的,比如:
# base model
class Created(models.Model):
created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
required_db_vendor = "postgresql"
class CreatedModified(Created):
last = models.DateTimeField(auto_now=True)
class Meta(Created.Meta):
abstract = True
class Argument(CreatedModified):
...
class ArgumentThrough(CreatedModified):
# argument 中间表基类
argument = models.ForeignKey(
to=Argument,
on_delete=models.PROTECT
)
value = models.JSONField(blank=True)
def clean(self):
argument_value_validator(self.argument, self.value)
class Meta(CreatedModified.Meta):
abstract = True
class Inventory(CreatedModified):
...
class Meta(CreatedModified.Meta):
abstract = True
class Host(Inventory):
inventory_variables = models.ManyToManyField(
to=Argument,
through="HostArgument",
blank=True,
help_text=_("Variables for host")
)
class HostArgument(ArgumentThrough):
# Host 到 Argument 的中间表
host = models.ForeignKey(
to=Host,
on_delete=models.CASCADE
)
class Group(Inventory):
inventory_variables = models.ManyToManyField(
to=Argument,
through="GroupArgument",
blank=True,
help_text=_("Variables for group")
)
class GroupArgument(ArgumentThrough):
# Group 到 Argument 的中间表
group = models.ForeignKey(
to=Group,
on_delete=models.CASCADE
)
可以看到 Host 和 Group 的两个关联 Argument 的中间表除了指向的源表以外,其他字段几乎是一模一样的,所以可以改成这样:
class Host(Inventory):
...
class Group(Inventory):
...
def add_related_field__argument(cls):
through_cls_dict = {
cls.__name__.lower(): models.ForeignKey(
to=cls, on_delete=models.CASCADE
),
"__module__": cls.__module__
}
cls.add_to_class(
"inventory_variables", models.ManyToManyField(
to=Argument,
through=type(
f"{cls.__name__}Argument", (ArgumentThrough,), through_cls_dict
),
blank=True,
)
)
for model in (Host, Group):
add_related_field__argument(model)
用这种方法,就可以不用写“重复”的代码了。
不知道你们是怎么解决这类问题的,希望我能抛砖引玉。