Django中如何实现带可编辑指定顺序的一对多模型关联?
实现有序关联模型的几种方案
方案一:优化顺序字段方案
你之前考虑的index字段其实可以通过优化避免效率问题,具体这么做:
- 在
Question模型里加order字段,用PositiveIntegerField类型,默认设为0,并且给这个字段加数据库索引:
class Question(models.Model): text = models.CharField(max_length=300, null=False, blank=False) ... questionary = models.ForeignKey(Questionary, on_delete=models.CASCADE) order = models.PositiveIntegerField(default=0, db_index=True) class Meta: # 设置默认排序,查询时直接按问卷+顺序返回 ordering = ['questionary', 'order']
- 重排时不用逐个更新后续所有记录,用批量更新操作:
# 示例:把id为123的Question调整到新位置new_order question = Question.objects.get(id=123) old_order = question.order new_order = 2 if new_order < old_order: # 把目标区间内的记录顺序+1,腾出位置 Question.objects.filter( questionary=question.questionary, order__gte=new_order, order__lt=old_order ).update(order=F('order') + 1) else: # 把目标区间内的记录顺序-1,填补空位 Question.objects.filter( questionary=question.questionary, order__gt=old_order, order__lte=new_order ).update(order=F('order') - 1) question.order = new_order question.save()
这种批量更新是数据库层面的操作,效率并不低,比循环更新每条记录快很多。
方案二:用第三方有序模型库简化操作
用django-ordered-model包可以省去自己写重排逻辑的麻烦:
- 先安装包:
pip install django-ordered-model - 修改模型代码:
from ordered_model.models import OrderedModel, OrderedModelManager class QuestionManager(OrderedModelManager): def get_queryset(self): return super().get_queryset().order_by('order') class Question(OrderedModel): text = models.CharField(max_length=300, null=False, blank=False) ... questionary = models.ForeignKey(Questionary, on_delete=models.CASCADE) # 指定按questionary分组排序 order_with_respect_to = 'questionary' objects = QuestionManager()
- 之后直接调用库提供的方法就能完成重排:
question = Question.objects.get(id=123) question.up() # 上移一位 question.down() # 下移一位 question.to(2) # 直接移到目标位置 question.top() # 移到开头 question.bottom()# 移到末尾
这个库内部已经封装了高效的批量更新逻辑,不用自己操心细节。
方案三:用JSONField存储顺序(小体量场景可选)
如果不想给Question加字段,可以在Questionary里加一个question_order字段存储Question的id列表:
class Questionary(models.Model): name = models.CharField(max_length=300, null=False, blank=False) question_order = models.JSONField(default=list) ...
查询时按这个列表的顺序返回Question:
questionary = Questionary.objects.get(id=456) questions = Question.objects.filter(id__in=questionary.question_order).order_by( models.Case( *[models.When(id=id, then=pos) for pos, id in enumerate(questionary.question_order)] ) )
但这个方案有个明显缺点:如果Question被删除,需要手动维护这个id列表,否则会出现无效id;而且大规模数据下,排序逻辑的效率不如前两种,只适合问卷数量少的场景。
内容的提问来源于stack exchange,提问作者Vicente Espinosa
相关产品推荐
相关产品推荐

