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

Django:创建并保存含计算型多对多字段的模型对象

解决Django中根据表单字段随机设置多对多字段的问题

针对你需要在创建Exam实例时,根据选择的categ、sscateg随机选取指定数量Question关联到多对多字段的需求,下面是对现有代码的修正方案:

1. 修正模型(models.py)

原save方法存在调用错误、多对多筛选逻辑不当、仅处理更新场景的问题,修正后代码如下:

import random
from django.db import models

class Exam(models.Model):
    categ = models.ManyToManyField(Category, blank=False)
    sscateg = models.ManyToManyField(SubCategory, blank=True)
    question = models.ManyToManyField(Question, related_name='questionsEx', blank=False)
    nbquestion = models.IntegerField()

    def save(self, *args, **kwargs):
        # 标记是否为新创建的实例
        is_new = not self.pk
        # 先保存实例,多对多字段需要实例有主键才能关联
        super().save(*args, **kwargs)
        
        # 仅在新创建或问题列表为空时执行随机选逻辑(可按需调整触发条件)
        if is_new or self.question.count() == 0:
            # 初始化题目查询集
            qs = Question.objects.all()
            # 筛选选中的分类
            if self.categ.exists():
                qs = qs.filter(category__in=self.categ.all())
            # 筛选选中的子分类(有选择时才执行)
            if self.sscateg.exists():
                qs = qs.filter(sscateg__in=self.sscateg.all())
            
            # 检查可选题目数量是否足够
            total_available = qs.count()
            if total_available >= self.nbquestion:
                # 随机选取指定数量的题目ID
                selected_ids = random.sample(list(qs.values_list('id', flat=True)), self.nbquestion)
                # 批量添加到多对多字段,效率高于循环单个添加
                self.question.add(*selected_ids)
            else:
                raise ValueError(f"可用题目数量({total_available})小于需要的数量({self.nbquestion})")

2. 修正表单(forms.py)

原表单误用ModelChoiceField适配多对多关系,需改为ModelMultipleChoiceField才能正确处理多选数据:

from django import forms
from .models import Exam, Category, SubCategory

class ExamForm(forms.ModelForm):
    nbquestion = forms.IntegerField(required=True)  # 设为必填,确保能获取题目数量
    categ = forms.ModelMultipleChoiceField(
        queryset=Category.objects.all(),
        widget=forms.CheckboxSelectMultiple,
        required=True  # 与模型中blank=False对应
    )
    sscateg = forms.ModelMultipleChoiceField(
        queryset=SubCategory.objects.all(),
        widget=forms.CheckboxSelectMultiple,
        required=False
    )

    class Meta:
        model = Exam
        fields = ['categ', 'sscateg', 'nbquestion']

    def save(self, commit=True):
        instance = super().save(commit=False)
        if commit:
            instance.save()
            self.save_m2m()  # 保存categ、sscateg的多对多关联
        return instance

3. 修正视图(views.py)

原视图form_valid逻辑重复,简化后让表单自行处理保存流程:

from django.views.generic.edit import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from .models import Exam
from .forms import ExamForm

class ExCreateView(LoginRequiredMixin, CreateView):
    login_url = 'login'
    template_name = 'MakeEx.html'
    model = Exam
    form_class = ExamForm
    success_url = reverse_lazy('Exams')

    def form_valid(self, form):
        self.object = form.save()
        return super().form_valid(form)

核心注意点

  • 多对多筛选逻辑:因为categ、sscateg是多对多字段,必须用category__in=self.categ.all()这种方式筛选关联的题目,不能直接用category=self.categ。
  • 实例保存时机:多对多字段的关联操作必须在实例拥有主键后执行,所以要先调用super().save()保存基础实例。
  • 表单字段匹配:多对多关系必须搭配ModelMultipleChoiceField,否则无法正确接收前端多选的分类数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:22:31