Django不使用表单如何修改模型字段并存储用户测验答题数据
完全可以不使用Django表单实现这个需求,Django表单本身只是帮你省了参数校验、表单渲染的重复工作,你直接读取request.POST里的提交值自行处理入库完全可行,具体落地改法如下:
1. 先调整数据模型结构
你现有的userQuizInfo表字段设计满足不了需求:既没有记录测验尝试序号,也存不了单题的作答结果,questOrder设成最大长度10的字符串根本存不下10道题的ID序列。建议拆成两张表,一张存每次测验尝试的汇总信息,一张存每道题的作答明细:
from django.db import models from django.contrib.auth.models import User class Quizzes(models.Model): CHOICES = ( ('Easy', 'Easy'), ('Medium', 'Medium'), ('Hard', 'Hard'), ) name = models.CharField(max_length=200) quizDesc = models.TextField() difficulty = models.CharField(max_length=200, null=True, choices=CHOICES) class Meta: verbose_name_plural = "Quizzes" def __str__(self): return self.name class QuestionList(models.Model): quiz = models.ForeignKey(Quizzes, on_delete=models.CASCADE) questNum = models.IntegerField(null=True) question = models.CharField(max_length=200,null=True) op1 = models.CharField(max_length=200,null=True) op2 = models.CharField(max_length=200,null=True) op3 = models.CharField(max_length=200,null=True) op4 = models.CharField(max_length=200,null=True) ans = models.CharField(max_length=200,null=True) # 测验尝试汇总表 class UserQuizAttempt(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) quiz = models.ForeignKey(Quizzes, on_delete=models.CASCADE) attempt_number = models.IntegerField() # 标记是第几次作答 total_correct = models.IntegerField(default=0) total_wrong = models.IntegerField(default=0) question_order = models.JSONField() # 直接存出题顺序的题目ID列表,不用手动拼字符串 submit_time = models.DateTimeField(auto_now_add=True) # 单题作答明细表 class UserAnswerDetail(models.Model): attempt = models.ForeignKey(UserQuizAttempt, on_delete=models.CASCADE, related_name='answer_details') question = models.ForeignKey(QuestionList, on_delete=models.CASCADE) user_answer = models.CharField(max_length=200, null=True) is_correct = models.BooleanField(default=False)
改完模型记得执行python manage.py makemigrations和python manage.py migrate同步数据库结构。
2. 调整视图逻辑
你现在的视图有两个核心问题:一是每次请求都重新抽题,根本没法保证用户答的是固定的10道题、出题顺序也一致;二是没有累计答题进度,也没统计当前是第几次尝试。用session存当前作答的临时状态即可,完全不需要依赖Django表单:
import random from django.shortcuts import render, get_object_or_404 from .models import Quizzes, QuestionList, UserQuizAttempt, UserAnswerDetail def quiz_view(request, pk): quiz = get_object_or_404(Quizzes, pk=pk) session_key = f'quiz_{pk}_attempt' # 首次进入答题页初始化会话状态 if session_key not in request.session: # 统计用户当前已经完成的作答次数 attempt_count = UserQuizAttempt.objects.filter(user=request.user, quiz=quiz).count() # 抽10道题,存ID序列保证题目和顺序全程固定 all_questions = list(QuestionList.objects.filter(quiz=quiz)) selected_questions = random.sample(all_questions, min(10, len(all_questions))) question_order = [q.id for q in selected_questions] # 写入会话缓存 request.session[session_key] = { 'attempt_number': attempt_count + 1, 'question_order': question_order, 'answered_count': 0, 'answers': {} } attempt_data = request.session[session_key] current_step = attempt_data['answered_count'] if request.method == 'POST': # 取当前题的用户答案,注意不要用题干当参数名,题干重复会出bug question_id = attempt_data['question_order'][current_step] current_question = QuestionList.objects.get(pk=question_id) user_ans = request.POST.get(f'question_{question_id}') # 暂存答案到会话 attempt_data['answers'][str(question_id)] = user_ans attempt_data['answered_count'] += 1 request.session.modified = True # 答满10道题就批量存库 if attempt_data['answered_count'] == 10: total_correct = 0 total_wrong = 0 # 先创建作答汇总记录 new_attempt = UserQuizAttempt.objects.create( user=request.user, quiz=quiz, attempt_number=attempt_data['attempt_number'], question_order=attempt_data['question_order'] ) # 逐题存作答明细 for qid, ans in attempt_data['answers'].items(): q = QuestionList.objects.get(pk=int(qid)) is_correct = (ans == q.ans) if is_correct: total_correct +=1 else: total_wrong +=1 UserAnswerDetail.objects.create( attempt=new_attempt, question=q, user_answer=ans, is_correct=is_correct ) # 更新汇总的对错计数 new_attempt.total_correct = total_correct new_attempt.total_wrong = total_wrong new_attempt.save() # 清除会话里的本次作答临时数据 del request.session[session_key] # 跳转到结果页 return render(request, 'results.html', { 'correct': total_correct, 'wrong': total_wrong, 'attempt_number': new_attempt.attempt_number }) # 没答完就更新进度,展示下一题 current_step = attempt_data['answered_count'] # 取当前要展示的单道题目 current_qid = attempt_data['question_order'][current_step] current_question = QuestionList.objects.get(pk=current_qid) return render(request, 'questions.html', { 'question': current_question, 'current_num': current_step + 1, 'total_num': 10 })
3. 调整题目页模板
你现在的模板一次渲染所有题,和「逐题单独展示」的需求不匹配,而且单选框的name用题干当键很容易出冲突,改成用题目ID当参数名:
{% block page_content %} <h1>第{{ current_num }}题 / 共{{ total_num }}题</h1> <form method='post'> {% csrf_token %} <h3>{{ question.question }}</h3> <br> <input type="radio" name="question_{{ question.id }}" value="{{ question.op1 }}" required>{{ question.op1 }} <br> <input type="radio" name="question_{{ question.id }}" value="{{ question.op2 }}">{{ question.op2 }} <br> <input type="radio" name="question_{{ question.id }}" value="{{ question.op3 }}">{{ question.op3 }} <br> <input type="radio" name="question_{{ question.id }}" value="{{ question.op4 }}">{{ question.op4 }} <br><br> <button type="submit" class="btn btn-secondary">下一题</button> </form> {% endblock %}
补充说明
- 以上逻辑默认你给答题路径加了登录校验,未登录用户无法访问,否则
request.user拿不到有效用户数据 - 用
JSONField存出题顺序需要Django 3.1及以上版本,搭配MySQL 5.7+、PostgreSQL或SQLite即可,如果是老版本环境,把题目ID转成逗号分隔的字符串存储也可以,取用时再split成列表就行 - 要查询某一次尝试的单题作答记录,直接过滤
UserAnswerDetail表对应attempt的记录即可,能精确拿到每道题用户的选项、是否正确 - 整套逻辑完全没有依赖Django表单,所有提交参数都是直接从
request.POST读取后自行处理,完全满足你的需求
内容的提问来源于stack exchange,提问作者nothanzo
相关产品推荐
相关产品推荐

