覆盖抽象Django模型中定义的ForeignKey引用模型

时间:2017-02-16 21:22:26

标签: django inheritance foreign-keys

我有一个这样的抽象模型:

class Like(models.Model):
    TARGET_MODEL = 'TargetModel'
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    target = models.ForeignKey(TARGET_MODEL)

    class Meta:
        abstract = True

我希望TARGET_MODEL在每个子类中都不同。例如,模型LikeForPost引用Post应用程序中的blog模型:

class LikeForPost(Like):
    TARGET_MODEL = 'blog.Post'

它似乎不起作用,因为TARGET_MODEL没有从子类实例化。实现这一目标的正确方法是什么?

我知道我可以重新定义target类中的整个LikeForPost字段,但我希望有一个更优雅的解决方案,它只允许覆盖模型名称。

2 个答案:

答案 0 :(得分:1)

看起来我找到了一个基于抽象类的类方法的解决方案,它通过contribute_to_class方法创建了适当的字段,并由class_prepared信号启动。

受到以下文章的启发:Django Model Field Injection,在某些情况下,Django application import and missed class_prepared signals文章可能很有用,但在我的特定情况下,它不相关。

因此,解决方案是在抽象类中定义一个方法,该方法将创建适当的字段:

class Like(models.Model):
    TARGET_MODEL = None  # will be overridden in subclass
    user = models.ForeignKey(settings.AUTH_USER_MODEL)

    @classmethod
    def on_class_prepared(cls):
        target_field = models.ForeignKey(cls.TARGET_MODEL)
        target_field.contribute_to_class(cls, 'target')

    class Meta:
        abstract = True

子类可以不受影响:

class LikeForPost(Like):
    TARGET_MODEL = 'blog.Post'

要使其工作,必须在创建on_class_prepared()类后调用LikeForPost函数,这可以通过将其挂钩到Django的class_prepared信号来实现。根据{{​​3}},放置它的最佳位置是AppConfig.__init__()方法。因此,我们选择应负责此设置的应用程序,在我的情况下为blog,并将以下代码添加到blog/apps.py

from django.apps import AppConfig
from django.db.models import signals

def call_on_class_prepared(sender, **kwargs):
    """Calls the function only if it is defined in the class being prepared"""
    try:
        sender.on_class_prepared()
    except AttributeError:
        pass


class BlogConfig(AppConfig):
    name = 'blog'

    def __init__(self, app_name, app_module):
        super(BlogConfig, self).__init__(app_name, app_module)
        # Connect programmatic class adjustment function to the signal
        signals.class_prepared.connect(call_on_class_prepared)

要使此配置默认处于活动状态,请在blog/__init__.py中配置:

default_app_config = 'blog.apps.BlogConfig'

此解决方案在Django 1.10中进行了测试,在运行服务器,模型测试以及使用manage.py makemigrations准备迁移时,其行为与正常定义的硬编码字段相同。

答案 1 :(得分:0)

articleStephen McDonald描述了与@BartoNaz的答案方法完全相同的方法,称为 Django Model Field Injection ,并在 other弥补缺点

  

一种方法是实现尽可能多的模型类和抽象基类,以便用户可以使用自己的模型来子类化。这种方法对某些类型的自定义有意义,例如,这就是我对django-forms-builder所做的。但是,这种方法存在一些警告。首先,不能在抽象模型上定义关系字段,因此需要在同一应用程序内或由用户实现自己的子类的具体模型中实现这些关系字段。其次,引用模型的任何功能(例如视图或中间件)都需要具有可配置的设置以选择要使用的模型,或者完全由用户重新实现以利用其自定义字段。

     

另一种方法是简单地建议用户使用多表继承对应用程序提供的模型进行子类化。不幸的是,这将带来不必要的开销,以及访问子类实例时所需的额外数据库查询。最好的情况是,在处理单个实例的视图中,这等于一个额外的查询或两个查询。最糟糕的情况是,当将此方法与模板中的查询集一起使用时,会对返回的每个实例执行额外的查询-经典的N + 1查询问题。

并精确定义此 Django模型字段注入的确切目标:

  

该方法可归结为三个概念:

     
      
  • 动态地向模型类添加字段
  •   
  • 确保Django的模型系统尊重新字段
  •   
  • 正确执行装载顺序以使上述工作正常
  •