Django GenericForeignKey对从特定抽象模型继承的ContentTypes的限制

时间:2016-01-05 05:28:03

标签: django django-models

在我的应用模型中,我需要一种链接ProblemsSolutions的方法 - 每个Problem可以有多个Solutions,而给定的Solution可以映射回到多个Problems

Solution是一个抽象基类,因为Solutions可以有很多种。所以,我想我需要一个映射表ProblemSolutionMapping,它使用GenericForeignKey来容纳所有这些子类。但我正在试图弄清楚如何将类限制为Solutions的子项,而不是整个应用程序中可用的所有类,这是当前正在发生的事情。

# Thanks to http://stackoverflow.com/a/23555691/1149759
class Solution(models.Model):
    ...
    @classmethod
    def get_subclasses(cls):
        content_types = ContentType.objects.filter(app_label=cls._meta.app_label)
        models = [ct.model_class() for ct in content_types]
        return [model for model in models
                if (model is not None and
                    issubclass(model, cls) and
                    model is not cls)]

    class Meta:
        abstract = True


class ProblemSolutionMapping(models.Model):
    problem = models.ForeignKey(Problem)
    content_type = models.ForeignKey(ContentType,
        limit_choices_to=Solution.get_subclasses()) # <==== This is the issue
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

问题是,当我启动我的Django应用程序时,对ContentType.objects.filter(app_label=cls._meta.app_label)的调用会引发错误:

django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.

不知道该怎么做 - 我尝试将映射表作为相关模型文件中的最后一个(所有子类都在同一个文件中定义在它上面),但它没有区别。 这是我必须进入管理表单的东西吗?或者在模型级别还有其他方法可以做到这一点吗?

(Django 1.9,如果重要的话。)

提前感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

django 1.7不再支持在导入期间重新生成模型。在加载所有应用程序后,您应该使用模型。所以你应该将列表静态传递给你的limit_choices_to 使用Q对象,如下所示:

limit_choices_to=models.Q(app_label = 'app', model = 'a') | models.Q(app_label = 'app', model = 'b')

Also you can limit what shows to user in form level

答案 1 :(得分:1)

所以我到了这里寻找答案。根据Mehran的帖子,我开发了类似于你的方法。相反,limit_choice_to调用一个返回运行时创建的Q对象的方法。

下面是与你的get_subclasses类似的部分。

def get_subclasses(cls, *args, **kwargs):
    for app_config in apps.get_app_configs():
        for app_model in app_config.get_models():
            model_classes = [c.__name__ for c in inspect.getmro(app_model)]
            if cls.__name__ in model_classes:
                yield app_model

这为我们创建了Q过滤器(在我的实现中,这只是一个没有附加到任何类的普通旧方法,但我认为它可能是):

def get_content_choices():
    query_filter = None

    for cls in Solution.get_subclasses():

        app_label, model = cls._meta.label_lower.split('.')
        current_filter = models.Q(app_label=app_label, model=model)

        if query_filter is None:
            query_filter = current_filter
        else:
            query_filter |= current_filter

    return query_filter

最后,在我们的模型中:

class ProblemSolutionMapping(models.Model):
    ...
    content_type = models.ForeignKey(ContentType, limit_choices_to=get_content_choices())
    ...