完善教育
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from . import exam_controller
|
||||
@@ -0,0 +1,185 @@
|
||||
import json
|
||||
import logging
|
||||
|
||||
from odoo import http, fields
|
||||
from odoo.http import request
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExamController(http.Controller):
|
||||
"""学生答题卡式考试页面控制器。"""
|
||||
|
||||
def _check_paper_access(self, paper_id, states=None):
|
||||
"""校验当前登录用户是否为该答卷的学员本人。"""
|
||||
paper = request.env['yuthon.school.exam.paper'].sudo().browse(paper_id)
|
||||
if not paper.exists():
|
||||
raise http.NotFound()
|
||||
if paper.user_id.id != request.env.user.id:
|
||||
raise AccessError('您无权访问该答卷。')
|
||||
if states and paper.state not in states:
|
||||
raise UserError('当前答卷状态不允许该操作。')
|
||||
return paper
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 1. 开始考试:初始化答题记录并跳转到答题页面
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/exam/start/<int:paper_id>', type='http', auth='user')
|
||||
def exam_start(self, paper_id, **kw):
|
||||
paper = self._check_paper_access(paper_id, states=('pending', 'in_progress'))
|
||||
try:
|
||||
paper.with_user(request.env.user).action_start()
|
||||
except UserError as e:
|
||||
return request.render('college.exam_error_page', {'message': str(e)})
|
||||
return request.redirect('/exam/page/%s' % paper_id)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 2. 答题主页面
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/exam/page/<int:paper_id>', type='http', auth='user', website=False)
|
||||
def exam_page(self, paper_id, **kw):
|
||||
paper = self._check_paper_access(paper_id)
|
||||
if paper.state == 'pending':
|
||||
# 未开始则自动开始
|
||||
try:
|
||||
paper.with_user(request.env.user).action_start()
|
||||
except UserError as e:
|
||||
return request.render('college.exam_error_page', {'message': str(e)})
|
||||
|
||||
if paper.state not in ('in_progress', 'submitted', 'graded'):
|
||||
return request.render('college.exam_error_page', {
|
||||
'message': '该答卷当前状态为 %s,无法进入答题页面。' % dict(paper._fields['state'].selection).get(paper.state)
|
||||
})
|
||||
|
||||
answers = paper.answer_ids.sorted(lambda a: (a.sequence, a.id))
|
||||
exam = paper.exam_id
|
||||
|
||||
# 计算剩余时间(秒)
|
||||
remaining_seconds = 0
|
||||
if paper.start_time and exam.duration_minutes:
|
||||
end_dt = paper.start_time + fields.Datetime.timedelta(minutes=exam.duration_minutes)
|
||||
remaining_seconds = max(0, int((end_dt - fields.Datetime.now()).total_seconds()))
|
||||
|
||||
values = {
|
||||
'paper': paper,
|
||||
'exam': exam,
|
||||
'course': paper.course_id,
|
||||
'chapters': paper.chapter_ids,
|
||||
'student': paper.student_id,
|
||||
'answers': answers,
|
||||
'answer_json': json.dumps([self._answer_to_dict(a) for a in answers]),
|
||||
'remaining_seconds': remaining_seconds,
|
||||
'duration_minutes': exam.duration_minutes or 0,
|
||||
'question_types': dict(request.env['yuthon.school.exam.question']._fields['question_type'].selection),
|
||||
}
|
||||
return request.render('college.exam_take_page', values)
|
||||
|
||||
def _answer_to_dict(self, answer):
|
||||
"""将答题记录转为前端 JSON 可用的字典。"""
|
||||
q = answer.question_id
|
||||
return {
|
||||
'id': answer.id,
|
||||
'sequence': answer.sequence or 0,
|
||||
'question_type': answer.question_type,
|
||||
'question_text': answer.question_text or '',
|
||||
'question_text_short': answer.question_text_short or '',
|
||||
'max_score': answer.max_score,
|
||||
'options': [{
|
||||
'id': opt.id,
|
||||
'label': opt.label,
|
||||
'content': opt.content or '',
|
||||
'text': opt.name or '%s. %s' % (opt.label, opt._strip_html(opt.content or '')),
|
||||
} for opt in q.option_ids.sorted(lambda o: (o.sequence, o.id))],
|
||||
# 当前作答
|
||||
'selected_option_id': answer.selected_option_id.id or False,
|
||||
'selected_option_ids': answer.selected_option_ids.ids,
|
||||
'answer_choice': answer.answer_choice or False,
|
||||
'answer_text': answer.answer_text or '',
|
||||
'is_answered': bool(
|
||||
answer.selected_option_id or
|
||||
answer.selected_option_ids or
|
||||
answer.answer_choice or
|
||||
(answer.answer_text and answer._strip_html(answer.answer_text))
|
||||
),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 3. AJAX 保存单题答案
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/exam/save', type='json', auth='user', methods=['POST'])
|
||||
def exam_save_answer(self, **kw):
|
||||
# 注意:type='json' 路由的参数在 request.params(JSON-RPC 内层 params),
|
||||
# 而非 request.jsonrequest(那是 envelope {jsonrpc, method, params, id})。
|
||||
params = request.params or {}
|
||||
paper_id = params.get('paper_id')
|
||||
answer_id = params.get('answer_id')
|
||||
data = params.get('data', {})
|
||||
|
||||
paper = self._check_paper_access(paper_id, states=('in_progress',))
|
||||
answer = request.env['yuthon.school.exam.answer'].sudo().browse(int(answer_id))
|
||||
if not answer.exists() or answer.paper_id.id != paper.id:
|
||||
return {'status': 'error', 'message': '题目不属于当前答卷。'}
|
||||
|
||||
qtype = answer.question_type
|
||||
vals = {}
|
||||
if qtype == 'single_choice':
|
||||
vals['selected_option_id'] = int(data.get('selected_option_id') or 0) or False
|
||||
if vals['selected_option_id']:
|
||||
vals['selected_option_ids'] = [(6, 0, [vals['selected_option_id']])]
|
||||
else:
|
||||
vals['selected_option_ids'] = [(5,)]
|
||||
elif qtype == 'multiple_choice':
|
||||
opt_ids = [int(x) for x in data.get('selected_option_ids', []) if x]
|
||||
vals['selected_option_ids'] = [(6, 0, opt_ids)]
|
||||
vals['selected_option_id'] = opt_ids[0] if opt_ids else False
|
||||
elif qtype == 'true_false':
|
||||
vals['answer_choice'] = data.get('answer_choice') or False
|
||||
elif qtype in ('fill_blank', 'essay'):
|
||||
vals['answer_text'] = data.get('answer_text') or False
|
||||
|
||||
answer.write(vals)
|
||||
return {
|
||||
'status': 'ok',
|
||||
'is_answered': bool(
|
||||
answer.selected_option_id or
|
||||
answer.selected_option_ids or
|
||||
answer.answer_choice or
|
||||
(answer.answer_text and answer._strip_html(answer.answer_text))
|
||||
),
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 4. AJAX 提交整卷
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/exam/submit', type='json', auth='user', methods=['POST'])
|
||||
def exam_submit(self, **kw):
|
||||
params = request.params or {}
|
||||
paper_id = params.get('paper_id')
|
||||
paper = self._check_paper_access(paper_id, states=('in_progress',))
|
||||
try:
|
||||
paper.with_user(request.env.user).action_submit()
|
||||
except UserError as e:
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
return {
|
||||
'status': 'ok',
|
||||
'redirect': '/exam/result/%s' % paper.id,
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# 5. 结果页面
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/exam/result/<int:paper_id>', type='http', auth='user', website=False)
|
||||
def exam_result(self, paper_id, **kw):
|
||||
paper = self._check_paper_access(paper_id)
|
||||
if paper.state not in ('submitted', 'graded'):
|
||||
return request.redirect('/exam/page/%s' % paper_id)
|
||||
|
||||
answers = paper.answer_ids.sorted(lambda a: (a.sequence, a.id))
|
||||
values = {
|
||||
'paper': paper,
|
||||
'exam': paper.exam_id,
|
||||
'answers': answers,
|
||||
'question_types': dict(request.env['yuthon.school.exam.question']._fields['question_type'].selection),
|
||||
}
|
||||
return request.render('college.exam_result_page', values)
|
||||
Reference in New Issue
Block a user