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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:18:24