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
相关产品推荐
相关产品推荐

