You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Django模型继承高效编码:多类型Answer类设计问询

嘿,针对你这个基于Django模型继承实现多类型Answer的需求,我整理了一套实用的方案,咱们一步步来捋清楚:

第一步:定义抽象基类Answer

首先咱们把所有Answer共有的字段抽出来做成抽象基类,让每个具体的Answer子类都能复用通用逻辑:

from django.db import models
from django.conf import settings

class Answer(models.Model):
    # 和Question关联,related_name方便从Question反向获取所有答案
    question = models.ForeignKey('Question', on_delete=models.CASCADE, related_name='answers')
    # 关联提交答案的用户
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    # 通用的时间字段
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        # 标记为抽象基类,不会生成单独的数据库表
        abstract = True

第二步:创建对应不同类型的Answer子类

接下来针对Question里的每一种answer_type,创建对应的Answer子类,每个子类只保留该类型特有的字段:

# 选择题答案
class ChoiceAnswer(Answer):
    # 假设你有一个Choice模型关联Question,存储选项内容
    selected_choice = models.ForeignKey('Choice', on_delete=models.CASCADE)

    class Meta:
        verbose_name = "选择题答案"

# 短文本答案
class ShortAnswer(Answer):
    text = models.CharField(max_length=255)

    class Meta:
        verbose_name = "短文本答案"

# 长文本答案
class LongAnswer(Answer):
    text = models.TextField()

    class Meta:
        verbose_name = "长文本答案"

# 专家概率判断答案
class ExpertProbabilityAnswer(Answer):
    probability = models.FloatField()
    notes = models.TextField(blank=True, null=True)

    class Meta:
        verbose_name = "专家概率判断答案"

# 其他类型(比如专家价值判断、头脑风暴、反馈)同理,按需添加字段即可

第三步:关联Question与Answer类的映射

在Question类里添加映射关系,把answer_type和对应的Answer子类绑定,方便后续动态获取:

class Question(models.Model):
    ANSWER_TYPE_CHOICES = (
        ('CH', 'Choice'),
        ('SA', 'Short Answer'),
        ('LA', 'Long Answer'),
        ('E3', 'Expert Judgement of Probabilities'),
        ('E4', 'Expert Judgment of Values'),
        ('BS', 'Brainstorms'),
        ('FB', 'Feedback'),
    )
    answer_type = models.CharField(max_length=2, choices=ANSWER_TYPE_CHOICES)
    text = models.TextField()  # 问题内容字段

    # 核心映射:把answer_type对应到具体的Answer子类
    ANSWER_CLASS_MAP = {
        'CH': ChoiceAnswer,
        'SA': ShortAnswer,
        'LA': LongAnswer,
        'E3': ExpertProbabilityAnswer,
        # 记得补充E4、BS、FB对应的Answer子类
    }

    def get_answer_class(self):
        # 方便通过Question实例直接获取对应的Answer类
        return self.ANSWER_CLASS_MAP.get(self.answer_type)

第四步:创建与查询Answer实例

现在你可以根据Question的类型,动态创建对应的Answer实例了:

# 假设你拿到了一个Question实例和当前用户
question = Question.objects.get(id=1)
current_user = request.user

# 获取对应的Answer类
answer_class = question.get_answer_class()
if not answer_class:
    raise ValueError(f"不支持的答案类型:{question.answer_type}")

# 根据不同类型传入对应的参数
if question.answer_type == 'CH':
    # 假设已经获取到用户选择的choice实例
    selected_choice = Choice.objects.get(id=10)
    answer = answer_class.objects.create(
        question=question,
        user=current_user,
        selected_choice=selected_choice
    )
elif question.answer_type == 'SA':
    answer = answer_class.objects.create(
        question=question,
        user=current_user,
        text="这是我的短文本答案"
    )
# 其他类型同理,按需传入字段参数

优化查询:使用django-polymorphic处理多态模型

如果需要跨类型查询所有Answer(比如获取一个Question的所有答案,不管类型),推荐使用django-polymorphic库,它能完美处理Django的多态模型查询:

  1. 先安装库:pip install django-polymorphic
  2. 修改抽象基类,继承PolymorphicModel:
from polymorphic.models import PolymorphicModel

class Answer(PolymorphicModel):
    # 字段和之前一样,只是父类换成PolymorphicModel
    question = models.ForeignKey('Question', on_delete=models.CASCADE, related_name='answers')
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

现在你就可以轻松查询所有类型的Answer了:

# 获取某个Question的所有答案,自动返回对应子类的实例
all_answers = question.answers.all()
for answer in all_answers:
    if isinstance(answer, ChoiceAnswer):
        print(f"选择题答案:{answer.selected_choice.text}")
    elif isinstance(answer, ShortAnswer):
        print(f"短文本答案:{answer.text}")

类型安全验证

为了避免创建和Question类型不匹配的Answer,咱们可以在Answer的save方法里加验证逻辑:

class Answer(PolymorphicModel):
    # ... 字段 ...

    def save(self, *args, **kwargs):
        # 反向映射:从Answer类找到对应的answer_type
        reverse_type_map = {v: k for k, v in Question.ANSWER_CLASS_MAP.items()}
        expected_type = reverse_type_map.get(self.__class__)
        
        if expected_type and self.question.answer_type != expected_type:
            raise ValueError(
                f"该答案类型只能关联到【{dict(Question.ANSWER_TYPE_CHOICES)[expected_type]}】类型的问题"
            )
        super().save(*args, **kwargs)

这样如果有人尝试给选择题类型的Question创建短文本Answer,就会抛出错误,保证数据的一致性。

方案总结

这个设计的优点很明显:

  • 字段清晰:每个Answer类型只保留必要字段,没有冗余
  • 扩展性强:新增Answer类型只需要添加子类和更新映射关系
  • 类型安全:通过验证避免数据不匹配的问题
  • 查询便捷:配合django-polymorphic可以轻松跨类型查询

内容的提问来源于stack exchange,提问作者Atcrank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:03:20