首页 > 其他分享 >每天40分玩转Django:Django在线考试系统开发指南

每天40分玩转Django:Django在线考试系统开发指南

时间:2025-01-07 09:00:37浏览次数:3  
标签:attempt exam models self 40 Django 玩转 time id

Django在线考试系统开发指南

1. 数据模型设计

试卷和题目模型

# exam/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class Exam(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    duration = models.IntegerField(help_text='考试时长(分钟)')
    start_time = models.DateTimeField()
    end_time = models.DateTimeField()
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.title

class Question(models.Model):
    TYPES = (
        ('single', '单选题'),
        ('multiple', '多选题'),
        ('essay', '问答题'),
    )
    
    exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
    question_type = models.CharField(max_length=10, choices=TYPES)
    content = models.TextField()
    points = models.IntegerField(default=1)
    
    def __str__(self):
        return f"{self.exam.title} - {self.content[:50]}"

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    content = models.CharField(max_length=200)
    is_correct = models.BooleanField(default=False)
    
    def __str__(self):
        return self.content

class ExamAttempt(models.Model):
    exam = models.ForeignKey(Exam, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    start_time = models.DateTimeField(auto_now_add=True)
    end_time = models.DateTimeField(null=True)
    score = models.DecimalField(max_digits=5, decimal_places=2, null=True)
    
    @property
    def time_remaining(self):
        if not self.end_time:
            elapsed = timezone.now() - self.start_time
            remaining = self.exam.duration * 60 - elapsed.total_seconds()
            return max(0, remaining)
        return 0

2. 视图实现

考试视图

# exam/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from .models import Exam, ExamAttempt, Question
from .forms import AnswerForm

@login_required
def exam_list(request):
    exams = Exam.objects.filter(
        start_time__lte=timezone.now(),
        end_time__gte=timezone.now()
    )
    return render(request, 'exam/exam_list.html', {'exams': exams})

@login_required
def start_exam(request, exam_id):
    exam = get_object_or_404(Exam, pk=exam_id)
    
    # 检查是否已经开始考试
    attempt = ExamAttempt.objects.filter(
        exam=exam,
        user=request.user,
        end_time__isnull=True
    ).first()
    
    if not attempt:
        attempt = ExamAttempt.objects.create(
            exam=exam,
            user=request.user
        )
    
    return redirect('take_exam', attempt_id=attempt.id)

@login_required
def take_exam(request, attempt_id):
    attempt = get_object_or_404(ExamAttempt, pk=attempt_id, user=request.user)
    
    if attempt.time_remaining <= 0:
        return finish_exam(request, attempt_id)
    
    questions = attempt.exam.question_set.all()
    
    if request.method == 'POST':
        form = AnswerForm(request.POST, questions=questions)
        if form.is_valid():
            form.save(attempt)
            return redirect('exam_result', attempt_id=attempt.id)
    else:
        form = AnswerForm(questions=questions)
    
    return render(request, 'exam/take_exam.html', {
        'attempt': attempt,
        'form': form,
        'questions': questions
    })

计时器实现

// exam/static/js/timer.js
class ExamTimer {
    constructor(remainingTime, updateCallback, expireCallback) {
        this.remainingTime = remainingTime;
        this.updateCallback = updateCallback;
        this.expireCallback = expireCallback;
        this.timerId = null;
    }
    
    start() {
        this.timerId = setInterval(() => {
            this.remainingTime -= 1;
            
            if (this.remainingTime <= 0) {
                this.stop();
                this.expireCallback();
                return;
            }
            
            this.updateCallback(this.formatTime(this.remainingTime));
        }, 1000);
    }
    
    stop() {
        if (this.timerId) {
            clearInterval(this.timerId);
        }
    }
    
    formatTime(seconds) {
        const hours = Math.floor(seconds / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);
        const secs = seconds % 60;
        
        return `${hours.toString().padStart(2, '0')}:${
            minutes.toString().padStart(2, '0')}:${
            secs.toString().padStart(2, '0')}`;
    }
}

3. 模板设计

考试页面模板

<!-- exam/templates/exam/take_exam.html -->
{% extends 'base.html' %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-md-8">
            <h2>{{ attempt.exam.title }}</h2>
            <div id="timer" class="alert alert-info">
                剩余时间: <span id="time-remaining"></span>
            </div>
            
            <form method="post" id="exam-form">
                {% csrf_token %}
                {% for question in questions %}
                <div class="card mb-3">
                    <div class="card-header">
                        问题 {{ forloop.counter }}: {{ question.points }}分
                    </div>
                    <div class="card-body">
                        <p>{{ question.content }}</p>
                        {% if question.question_type == 'single' %}
                            {% for choice in question.choice_set.all %}
                            <div class="form-check">
                                <input type="radio" 
                                       name="question_{{ question.id }}" 
                                       value="{{ choice.id }}" 
                                       class="form-check-input">
                                <label class="form-check-label">
                                    {{ choice.content }}
                                </label>
                            </div>
                            {% endfor %}
                        {% endif %}
                    </div>
                </div>
                {% endfor %}
                <button type="submit" class="btn btn-primary">提交答案</button>
            </form>
        </div>
    </div>
</div>

<script>
const timer = new ExamTimer(
    {{ attempt.time_remaining }},
    (time) => {
        document.getElementById('time-remaining').textContent = time;
    },
    () => {
        document.getElementById('exam-form').submit();
    }
);
timer.start();
</script>
{% endblock %}

4. 成绩统计

统计视图

# exam/views.py
from django.db.models import Avg, Count
from django.db.models.functions import TruncMonth

def exam_statistics(request, exam_id):
    exam = get_object_or_404(Exam, pk=exam_id)
    
    # 总体统计
    stats = {
        'total_attempts': ExamAttempt.objects.filter(exam=exam).count(),
        'average_score': ExamAttempt.objects.filter(exam=exam)
            .aggregate(avg=Avg('score'))['avg'],
        'passing_rate': ExamAttempt.objects.filter(
            exam=exam, 
            score__gte=60
        ).count() / ExamAttempt.objects.filter(exam=exam).count() * 100
    }
    
    # 月度统计
    monthly_stats = ExamAttempt.objects.filter(exam=exam)\
        .annotate(month=TruncMonth('start_time'))\
        .values('month')\
        .annotate(
            count=Count('id'),
            avg_score=Avg('score')
        )\
        .order_by('month')
    
    return render(request, 'exam/statistics.html', {
        'exam': exam,
        'stats': stats,
        'monthly_stats': monthly_stats
    })

统计图表

// exam/static/js/statistics.js
function renderCharts(monthlyStats) {
    const ctx = document.getElementById('scoreChart').getContext('2d');
    new Chart(ctx, {
        type: 'line',
        data: {
            labels: monthlyStats.map(stat => stat.month),
            datasets: [{
                label: '平均分',
                data: monthlyStats.map(stat => stat.avg_score),
                borderColor: 'rgb(75, 192, 192)',
                tension: 0.1
            }]
        },
        options: {
            responsive: true,
            scales: {
                y: {
                    beginAtZero: true,
                    max: 100
                }
            }
        }
    });
}

5. 系统流程图

在这里插入图片描述

6. 测评标准

评分项目分值占比评分标准
选择题60%自动评分,正确得分
问答题40%人工评分,根据答案质量评分
总分100%所有题目得分之和

7. 优化建议

  1. 性能优化
# 使用缓存减少数据库查询
from django.core.cache import cache

def get_exam_questions(exam_id):
    cache_key = f'exam_questions_{exam_id}'
    questions = cache.get(cache_key)
    
    if questions is None:
        questions = Question.objects.filter(exam_id=exam_id)\
            .prefetch_related('choice_set')\
            .all()
        cache.set(cache_key, questions, 3600)
    
    return questions
  1. 防作弊措施
// 禁止复制粘贴
document.addEventListener('copy', (e) => {
    e.preventDefault();
    return false;
});

// 禁止右键菜单
document.addEventListener('contextmenu', (e) => {
    e.preventDefault();
    return false;
});

// 检测页面切换
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        // 记录切换次数
        const switches = parseInt(localStorage.getItem('page_switches') || '0');
        localStorage.setItem('page_switches', switches + 1);
    }
});
  1. 异步提交答案
async function submitAnswer(questionId, answer) {
    try {
        const response = await fetch('/api/submit-answer/', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCsrfToken()
            },
            body: JSON.stringify({
                question_id: questionId,
                answer: answer
            })
        });
        
        if (!response.ok) {
            throw new Error('提交失败');
        }
        
        return await response.json();
    } catch (error) {
        console.error('提交答案出错:', error);
        throw error;
    }
}

8. 测试用例

# exam/tests.py
from django.test import TestCase
from django.utils import timezone
from django.contrib.auth.models import User
from .models import Exam, Question, Choice, ExamAttempt

class ExamTestCase(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass'
        )
        
        self.exam = Exam.objects.create(
            title='测试考试',
            duration=60,
            start_time=timezone.now(),
            end_time=timezone.now() + timezone.timedelta(days=1)
        )
        
        self.question = Question.objects.create(
            exam=self.exam,
            question_type='single',
            content='测试题目',
            points=5
        )
        
        self.choice = Choice.objects.create(
            question=self.question,
            content='正确答案',
            is_correct=True
        )
    
    def test_exam_attempt(self):
        self.client.login(username='testuser', password='testpass')
        
        # 开始考试
        response = self.client.post(f'/exam/{self.exam.id}/start/')
        self.assertEqual(response.status_code, 302)
        
        # 验证考试尝试记录
        attempt = ExamAttempt.objects.get(
            user=self.user,
            exam=self.exam
        )
        self.assertIsNotNone(attempt)
        
        # 提交答案
        response = self.client.post(
            f'/exam/attempt/{attempt.id}/',
            {'question_{}'.format(self.question.id): self.choice.id}
        )
        self.assertEqual(response.status_code, 302)
        
        # 验证得分
        attempt.refresh_from_db()
        self.assertEqual(attempt.score, 5)

通过以上代码和说明,你应该能够构建一个基本的在线考试系统。记住要根据实际需求进行调整和扩展,特别是在安全性和性能方面。同时,建议添加更多的测试用例来确保系统的稳定性。


怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

标签:attempt,exam,models,self,40,Django,玩转,time,id
From: https://blog.csdn.net/weixin_40780178/article/details/144975659

相关文章

  • 40
    实验 20:备忘录模式本次实验属于模仿型实验,通过本次实验学生将掌握以下内容: 1、理解备忘录模式的动机,掌握该模式的结构;2、能够利用备忘录模式解决实际问题。 [实验任务一]:多次撤销改进课堂上的“用户信息操作撤销”实例,使得系统可以实现多次撤销(可以使用HashMap、ArrayLis......
  • msvcp140.dll跑丢啦!快来看看msvcp140.dll丢失的解决方法将其找回
    在使用电脑时,我们可能会遇到提示缺少msvcp140.dll的错误信息。这个提示意味着我们的电脑中缺少MSVCP140.dll这个文件,它是某些程序运行所必需的。如果我们遇到这个问题,应该如何解决呢?本文将详细解析如何解决msvcp140.dll丢失的问题,帮助大家快速解决这个问题。一,了解msvcp140.......
  • (2024最新毕设合集)基于Django的电影资讯共享平台-10223|可做计算机毕业设计JAVA、PHP、
    目录摘要Abstract1绪论1.1研究背景1.2研究意义1.3论文结构与章节安排2电影资讯共享平台系统分析2.1可行性分析2.1.1技术可行性分析2.1.2经济可行性分析2.1.3 社会可行性2.1.4法律可行性分析2.2系统功能分析2.2.1功能性分析2.2.2非功能性分析2.......
  • 定时任务与异步任务:django-apscheduler 与 django-Q的区别
    django-apscheduler和django-Q是两个用于调度任务和异步任务处理的Django扩展库,但它们的功能和设计目标有所不同。以下是两者的主要区别:1.django-apschedulerdjango-apscheduler是Django框架下的一个定时任务调度工具,它基于Python的APScheduler实现。它主要......
  • GA/T1400视图库平台EasyCVR小知识:如何评估现有监控系统的技术状况?
    在当今社会,随着技术的不断发展和安全需求的日益提高,监控系统在各个领域的应用越来越广泛。为了确保监控系统的有效性和可靠性,定期对其技术状况进行全面评估是非常必要的。通过对监控系统的系统功能、性能、安全性、硬件设备、软件系统以及维护管理等方面的细致检查与分析,可以及时......
  • 【S32DS项目实战系列】项目工程外设 之 C40_IP组件
    前言:前面新建了一个工程但是没有任何驱动配置,接下来就教大家配置一下外设驱动的C40_IP1,文件自定义管理在S32DSIDE安装的目录下面的找到一下头文件,并复制到自建的文件夹里面用于自己建立的RTD库当中,当然这个是为了方面进行文件管理2,C40_IP组件简单介绍一下C40_IP组件......
  • 电脑盲如何选购笔记本避免被坑?很简单,不用别人教你也能搞定.240108
     经常有人问伟哥,笔记本电脑该如何选购。其实很简单,一是看预算,二是到某东看预算范围内的热销电脑,闭眼选就是。笔记本现在也是消费品了,没那么多讲究。 当然,如果要显得你自己更加专业或更加有逼格一点,那,在笔记本选购中,要注意两点:CPU和硬盘。 硬盘很好说,现在这时代,一定是固态......
  • docker部署最新6.2版Zabbix Server端.240103
    一、安装docker,参见本博客docker安装文档。二、启动空的mysql-eMYSQL_DATABASE="zabbix"\-eMYSQL_USER="zabbix"\-eMYSQL_PASSWORD="zabbix_pwd1234"\-eMYSQL_ROOT_PASSWORD="root_pwd12345"\-p3306:3......
  • 云计算真的很便宜吗?.240103
    ​37Signals的首席技术官DavidHeinemeierHansson表示,2022年一共在AWS(亚马逊云)花了3,201,564美元,即每月266,797美元。其中759,983美元都花费在AWS的EC2和EKS服务服务计算上面。Heinemeier指出,即使是这个账单,也是团队共同努力保持costdown的结果。“这已经是一个高度优化的预算!......
  • Just keep scaling!思维链作者Jason Wei 40分钟讲座剖析LLM扩展范式
    关注AI领域的人对JasonWei这个名字一定不陌生。他是OpenAI的一位资深研究科学家,也常在OpenAI的发布活动中露脸。更重要的是,他是思维链概念开山之作《Chain-of-ThoughtPromptingElicitsReasoninginLargeLanguageModels》的第一作者。他本科毕业就加入了谷歌......