V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
firejoke
V2EX  ›  Django

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

  •  
  •   firejoke · 2021-11-05 15:33:24 +08:00 · 1652 次点击
    这是一个创建于 874 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2440 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 16:04 · PVG 00:04 · LAX 09:04 · JFK 12:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.