Django多外键模型如何实现同一时间仅关联其中一个外键
结论
单个评论模型完全可以满足需求,不需要为问题评论、回答评论分别创建独立模型。Stack Overflow本身就是用单评论模型实现的这个交互,拆表反而会带来大量字段重复、逻辑冗余的问题,后续维护成本很高。
具体实现方案
你现有模型的核心问题是两个外键都设置了非空,根本没法实现“二选一关联”的逻辑,按下面的步骤调整即可:
1. 调整模型字段+加校验规则
把两个关联外键都设置为允许空值,同时加模型层+数据库层的双重校验,保证一条评论只能关联问题、回答二者其一,不能同时关联也不能都不关联:
from django.db import models from django.core.exceptions import ValidationError from django.db.models import Q, CheckConstraint class Comment(models.Model): author = models.ForeignKey( User, related_name='my_comments', on_delete=models.CASCADE ) # 补上评论内容、创建时间这类通用字段 content = models.TextField() created_at = models.DateTimeField(auto_now_add=True) # 两个外键都配置允许为空 question = models.ForeignKey( Questions, on_delete=models.CASCADE, related_name='comments', null=True, blank=True ) answer = models.ForeignKey( Answers, on_delete=models.CASCADE, related_name='comments', null=True, blank=True ) def clean(self): super().clean() # 校验关联对象互斥 linked_count = sum([self.question is not None, self.answer is not None]) if linked_count == 0: raise ValidationError("评论必须关联一个问题或一个回答") if linked_count == 2: raise ValidationError("评论不能同时关联问题和回答") def save(self, *args, **kwargs): # 保存前强制执行校验,避免绕过表单写入脏数据 self.full_clean() super().save(*args, **kwargs) class Meta: # 加数据库层面的检查约束,从底层杜绝脏数据 constraints = [ CheckConstraint( check=(Q(question__isnull=False, answer__isnull=True) | Q(question__isnull=True, answer__isnull=False)), name='comment_single_relation_check' ) ]
注意:你原来写的外键related_name配置有问题,比如author的反向关联名不能叫
author,answer的反向关联名不能叫answers,反向查询的时候会语义混乱,上面代码里已经调整成合理的命名了。
2. 表单/前端层实现选择交互
写评论创建表单的时候,直接把question、answer两个字段都渲染到页面上,前端加个单选控件让用户选“评论问题”/“评论回答”:
- 选“评论问题”时,激活问题选择下拉框,禁用回答选择框,提交时answer字段传空值
- 选“评论回答”时,激活回答选择下拉框,禁用问题选择框,提交时question字段传空值
完全可以实现你要的、和Stack Overflow一致的评论选择效果。
什么时候才需要拆分独立模型?
只有当问题评论和回答评论后续会出现大量完全不共通的字段、业务逻辑时,才考虑拆表。比如问题评论要加专属的“是否为官方补充说明”标记,回答评论要加“关联回答特定段落”的专属定位字段,且两边的权限、审核逻辑完全独立,这时候拆表才划算,你当前的需求完全没必要。
内容的提问来源于stack exchange,提问作者ZAIN DURANI
相关产品推荐
相关产品推荐

