覆盖抽象Django模型中定义的ForeignKey参考模型

覆盖抽象Django模型中定义的ForeignKey参考模型,django,inheritance,foreign-keys,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\u模型在每个子类中都有所不同。例如,引用blog应用

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

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

    class Meta:
        abstract = True
我想让
TARGET\u模型在每个子类中都有所不同。例如,引用
blog
应用程序中的
Post
模型的模型
LikeForPost

class LikeForPost(Like):
    TARGET_MODEL = 'blog.Post'
它似乎不起作用,因为
TARGET\u模型
不是从子类实例化的。实现这一目标的正确方法是什么


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

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

它受到以下文章的启发:,在某些情况下,这篇文章是有用的,但在我的特殊情况下,这篇文章并不相关

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

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'
要使其工作,必须在创建
LikeForPost
类后调用
on_class_prepared()
函数,这可以通过将其挂接到Django的
class_prepared
信号来实现。根据,放置它的最佳位置是
AppConfig.\uuuu init\uuuu()
方法。因此,我们选择一个应该负责此设置的应用程序,在我的例子中是
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)
default_app_config = 'blog.apps.BlogConfig'
要使此配置在默认情况下处于活动状态,请在
blog/\uuuuu init\uuuuuu.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)
default_app_config = 'blog.apps.BlogConfig'

此解决方案在Django 1.10中进行了测试,在运行服务器、测试模型以及使用
manage.py makemigrations

准备迁移时,其行为与通常定义的硬编码字段相同。该解决方案描述了与@BartoNaz完全相同的应答方法,称为Django model field Injection,并就其他方法的缺点提供了更多的上下文:

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

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

并精确确定了Django模型现场注入的确切目标:

该方法归结为三个概念:

  • 向模型类动态添加字段
  • 确保Django的模型系统尊重新领域
  • 使负载顺序正确,以使上述各项正常工作