【问题标题】:Defining an Abstract model with a ForeignKey to another Abstract model使用外键定义抽象模型到另一个抽象模型
【发布时间】:2017-07-07 07:38:50
【问题描述】:

我正在尝试构建两个名为 SurveyQuestionBaseSurveyResponseBase 的抽象类,它们将作为模板快速定义新的具体模型,以在我们的网站上实施特定调查。我遇到的问题是强制SurveyResponseBase 模型在具体化时应将ForeignKey 定义为SurveyQuestionBase 的具体模型。

Django 不允许我们将ForeignKeys 定义为抽象类,所以我不能,例如,这样做: question = models.ForeignKey(SurveyQuestionBase) 出于类似的原因,我也不能将其命名为 Noneapp_label.ModelName

一个 hacky 解决方法是创建一个新的具体模型 SurveyQuestionConcrete 并让 ForeignKey 指向此:question = models.ForeignKey(concrete_model),并结合验证以确保替换此模型。

有没有更简洁的方法来实现同样的目标? 我需要做的就是确保当有人从SurveyResponseBase 定义具体模型时,他们将ForeignKey 包含到从SurveyQuestionBase 定义的具体模型中

这是完整的代码:

from __future__ import unicode_literals


from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

# Implementation borrows from: https://github.com/jessykate/django-survey/


class SurveyQuestionBase(models.Model):
    TEXT = 'text'
    INTEGER = 'integer'
    RADIO = 'radio'
    SELECT = 'select'
    MULTI_SELECT = 'multi-select'

    ANSWER_TYPE_CHOICES = (
        (INTEGER, 'Integer',),
        (TEXT, 'Text',),
        (RADIO, 'Radio',),
        (SELECT, 'Select',),
        (MULTI_SELECT, 'Multi-Select',),
    )

    question = models.TextField()
    required = models.BooleanField()
    question_type = models.CharField(choices=ANSWER_TYPE_CHOICES, max_length=20)

    class Meta:
        abstract = True


class SurveyResponseBase(models.Model):
    """
    concrete_question_model: 'app_label.Model' - Define the concrete model this question belongs to
    """
    concrete_model = 'SurveyQuestionBase'

    question = models.ForeignKey(concrete_model)
    response = models.TextField()

    class Meta:
        abstract = True

【问题讨论】:

    标签: python django django-models metaprogramming django-orm


    【解决方案1】:

    解决此问题的两种解决方案(均有效):

    第一个解决方案涉及使用GenericForeignKey。第二个更有趣,涉及动态生成SurveyResponseBase

    解决方案 1:使用 GenericForeignKey

    class SurveyQuestionBase(models.Model):
        TEXT = 'text'
        INTEGER = 'integer'
        RADIO = 'radio'
        SELECT = 'select'
        MULTI_SELECT = 'multi-select'
    
        ANSWER_TYPE_CHOICES = (
            (INTEGER, 'Integer',),
            (TEXT, 'Text',),
            (RADIO, 'Radio',),
            (SELECT, 'Select',),
            (MULTI_SELECT, 'Multi-Select',),
        )
    
        question = models.TextField()
        required = models.BooleanField()
        question_type = models.CharField(choices=ANSWER_TYPE_CHOICES, max_length=20)
    
        class Meta:
            abstract = True
    
        @classmethod
        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
    
    
    class SurveyResponseBase(models.Model):
        content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to=get_content_choices)
        object_id = models.PositiveIntegerField()
        content_object = GenericForeignKey('content_type', 'object_id')
        response = models.TextField()
    
        class Meta:
            abstract = True
    
    def get_content_choices():
        query_filter = None
    
        for cls in SurveyQuestionBase.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
    

    解决方案 2:动态基类生成

    class SurveyQuestionBase(models.Model):
        TEXT = 'text'
        INTEGER = 'integer'
        RADIO = 'radio'
        RATING = 'rating'
        SELECT = 'select'
        MULTI_SELECT = 'multi-select'
    
        QUESTION_TYPES = (
            (INTEGER, 'Integer'),
            (TEXT, 'Text'),
            (RADIO, 'Radio'),
            (RATING, 'Rating'),
            (SELECT, 'Select'),
            (MULTI_SELECT, 'Multi-Select'),
        )
    
        CHOICE_TYPES = (RADIO, RATING, SELECT, MULTI_SELECT)
    
        question = models.TextField()
        required = models.BooleanField()
        question_type = models.CharField(choices=QUESTION_TYPES, max_length=20)
        choices = models.TextField(blank=True, null=True)
    
        choices.help_text = """
        If the question type is "Radio," "Select," or "Multi-Select", 
        provide a comma-separated list of options for this question
        """
    
        class Meta:
            abstract = True
    
    
    Meta = type('Meta', (object,), {'abstract': True})
    
    
    def get_response_base_class(concrete_question_model):
        """
        Builder method that returns the SurveyResponseBase base class
        Args:
            concrete_question_model: Concrete Model for SurveyQuestionBase
    
        Returns: SurveyResponseBase Class
        """
        try:
            assert SurveyQuestionBase in concrete_question_model.__bases__
        except AssertionError:
            raise ValidationError('{} is not a subclass of SurveyQuestionBase'.format(concrete_question_model))
    
        attrs = {
            'question': models.ForeignKey(concrete_question_model, related_name='responses'),
            'response': models.TextField(),
            '__module__': 'survey_builder.models',
            'Meta': Meta(),
        }
        return type('SurveyResponseBase', (models.Model,), attrs)
    

    我们决定继续使用解决方案 2,因为 GenericForeignKeys 方法需要额外的 ContentType 选择。

    【讨论】:

      【解决方案2】:

      我相信您不能这样做,因为 ForeignKey 不知道要指向哪个实际模型。

      您可能正在寻找 GenericForeignKey (https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#generic-relations)。它允许您正确定义这种关系。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-23
        • 2016-04-19
        • 2016-06-24
        • 1970-01-01
        • 2018-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多