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

Django生产环境Ajax请求报错:Question匹配查询不存在问题求助

解决Django生产环境Ajax提交问卷时Question.DoesNotExist异常的问题

首先,咱们先定位核心问题:你用Question模型的text字段(富文本类型)作为查询条件,这在生产环境很容易踩坑——因为富文本内容在开发和生产环境可能存在细微差异(比如HTML标签格式、换行符、空格),导致数据库里的记录和你提交的text匹配不上。就算你换成pk,也得确保前端和视图的逻辑完全对应,不然还是会出错。

下面是一步步的解决方案:

1. 彻底抛弃用text字段作为查询标识

富文本字段(RichTextUploadingField)设计用来存储富内容,不是用来做唯一查询键的——哪怕你加了unique=True,也会因为内容的格式化差异(比如CKEditor自动补全标签、换行转义)导致匹配失败。改用Question的主键(pk)作为查询依据是最可靠的。

修改前端Ajax请求

把原来提交的text字段换成Question的pk,比如原来的请求数据是{"问题文本": "选中的答案"},改成{"1": "答案内容", "2": "另一个答案"}(这里的数字是Question的pk),或者更规范的数组结构:

// 示例:前端收集答案的逻辑
const answers = [];
document.querySelectorAll('.question-item').forEach(item => {
  const questionPk = item.dataset.questionPk;
  const selectedAnswer = item.querySelector('input:checked')?.value;
  if (selectedAnswer) {
    answers.push({ question_pk: questionPk, selected_answer: selectedAnswer });
  }
});

// 提交请求
$.ajax({
  url: `/save-quiz/${quizPk}/${bookPk}/`,
  method: 'POST',
  data: {
    csrfmiddlewaretoken: '{{ csrf_token }}',
    answers: JSON.stringify(answers)
  },
  success: res => {
    // 处理结果
  }
});

2. 调整视图代码,用pk查询Question

方案一:适配前端传递的pk作为key的情况

如果前端还是用pk作为请求数据的key,修改视图里的循环逻辑:

from django.shortcuts import get_object_or_404

def save_quiz_view(request, pk, book_pk, question_pk):
    quiz = get_object_or_404(Quiz, pk=pk)
    if request.is_ajax() and request.user.is_authenticated:
        user_profile = request.user.profile
        # 提前判断用户是否已完成问卷,减少无效逻辑
        if user_profile in quiz.completed_users.all():
            return JsonResponse({'error': '你已经完成过这个问卷了'}, status=400)
        
        data = request.POST
        data_ = dict(data.lists())
        data_.pop('csrfmiddlewaretoken')
        
        questions = []
        for q_pk in data_.keys():
            try:
                # 用pk查询Question,避免text字段的匹配问题
                question = Question.objects.get(pk=q_pk)
                # 额外校验:确保这个问题属于当前问卷,防止恶意提交
                if question.quiz != quiz:
                    continue
                questions.append(question)
            except Question.DoesNotExist:
                # 可以记录日志,忽略无效的pk
                continue
        
        score = 0
        results = []
        for q in questions:
            a_selected = request.POST.get(str(q.pk))
            correct_answer = None
            if a_selected:
                question_answers = Answer.objects.filter(question=q)
                for a in question_answers:
                    if a.correct:
                        correct_answer = a.text
                    if a_selected == a.text and a.correct:
                        score += 1
                results.append({
                    str(q): {
                        'correct_answer': correct_answer,
                        'answered': a_selected
                    }
                })
            else:
                results.append({str(q): 'not answered'})
        
        # 计算百分比分数,和问卷的及格线匹配
        score_percent = (score / quiz.number_of_questions) * 100
        # 创建Result实例
        Result.objects.create(quiz=quiz, user=user_profile, score=score_percent)
        # 标记用户已完成问卷
        quiz.completed_users.add(user_profile)
        
        if score_percent >= quiz.required_score_to_pass:
            return JsonResponse({'passed': True, 'score': score_percent, 'results': results})
        else:
            return JsonResponse({'passed': False, 'score': score_percent, 'results': results})
    return JsonResponse({'error': '无效请求'}, status=400)

方案二:适配前端传递的规范数组结构

如果前端用JSON数组传递答案,视图里可以这样解析:

import json
from django.shortcuts import get_object_or_404

def save_quiz_view(request, pk, book_pk, question_pk):
    quiz = get_object_or_404(Quiz, pk=pk)
    if request.is_ajax() and request.user.is_authenticated:
        user_profile = request.user.profile
        if user_profile in quiz.completed_users.all():
            return JsonResponse({'error': '你已经完成过这个问卷了'}, status=400)
        
        try:
            answers = json.loads(request.POST.get('answers', '[]'))
        except json.JSONDecodeError:
            return JsonResponse({'error': '无效的答案格式'}, status=400)
        
        score = 0
        results = []
        for item in answers:
            q_pk = item.get('question_pk')
            a_selected = item.get('selected_answer')
            if not q_pk or not a_selected:
                continue
            try:
                # 同时校验问题属于当前问卷,防止恶意提交
                q = Question.objects.get(pk=q_pk, quiz=quiz)
            except Question.DoesNotExist:
                continue
            
            correct_answer = None
            question_answers = Answer.objects.filter(question=q)
            for a in question_answers:
                if a.correct:
                    correct_answer = a.text
                if a_selected == a.text and a.correct:
                    score += 1
            results.append({
                str(q): {
                    'correct_answer': correct_answer,
                    'answered': a_selected
                }
            })
        
        score_percent = (score / quiz.number_of_questions) * 100
        Result.objects.create(quiz=quiz, user=user_profile, score=score_percent)
        quiz.completed_users.add(user_profile)
        
        return JsonResponse({
            'passed': score_percent >= quiz.required_score_to_pass,
            'score': score_percent,
            'results': results
        })
    return JsonResponse({'error': '无效请求'}, status=400)

3. 额外的健壮性优化

  • 用get_object_or_404代替直接get,避免未捕获的DoesNotExist异常;
  • 校验Question是否属于当前Quiz,防止恶意提交其他问卷的问题;
  • 增加用户登录状态的提前判断,减少无效逻辑;
  • 日志记录无效的请求或查询失败的情况,方便生产环境排查问题。

为什么生产环境用text字段会出错?

主要是富文本字段的存储特性:

  • CKEditor在开发和生产环境的配置可能不同,自动格式化HTML(比如换行转<br>或<p>,空格、缩进的处理);
  • 数据库字符集或存储引擎的差异,导致存储的内容存在不可见的细微差别;
  • 前端提交时的编码问题,导致传递的text和数据库存储的不完全一致。

这些差异在开发环境可能因为数据量小、配置一致不容易暴露,但到生产环境就会触发查询失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 21:19:06