分享一个在 Django 的 model 里,使用元类动态添加相似的带自定义中间表的多对多关系字段的方法

2021-11-05 15:33:24 +08:00
 firejoke

当有多个 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)

用这种方法,就可以不用写“重复”的代码了。
不知道你们是怎么解决这类问题的,希望我能抛砖引玉。

1884 次点击
所在节点    Django
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/813300

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX