|
@@ -0,0 +1,383 @@
|
|
|
+# coding=utf-8
|
|
|
+import json
|
|
|
+import traceback
|
|
|
+from django.utils import timezone
|
|
|
+from rest_framework.decorators import action
|
|
|
+from django.db import transaction
|
|
|
+from django.db.models import F
|
|
|
+from utils.custom_modelviewset import CustomModelViewSet
|
|
|
+from utils import response_ok, response_error
|
|
|
+from utils.permission import IsStaff
|
|
|
+from apps.examination.exam.serializers import *
|
|
|
+from apps.examination.exam.filters import *
|
|
|
+from apps.system.models import SysLog
|
|
|
+
|
|
|
+from apps.examination.exam.models import ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
|
|
|
+from apps.examination.exampaper.models import ExamPaperDetail, ExamQuestion
|
|
|
+from apps.examination.examquestion.models import ExamQuestionOption, ExamQuestionFill, ExamQuestionFeedback
|
|
|
+from apps.examination.examquestion.serializers import ExamQuestionFeedbackSerializer
|
|
|
+from apps.practise.errorbook.models import ErrorBook
|
|
|
+from utils.format import strftime
|
|
|
+
|
|
|
+class ExamLogViewSet(CustomModelViewSet):
|
|
|
+ permission_classes = [IsStaff, ]
|
|
|
+ queryset = ExamLog.objects.filter(delete=False, exam__delete=False, type=ExamLog.FORMAL,)
|
|
|
+ serializer_class = StaffExamLogSerializer
|
|
|
+
|
|
|
+ def filter_queryset(self, queryset):
|
|
|
+ queryset = queryset.filter(user=self.request.user)
|
|
|
+ return queryset
|
|
|
+
|
|
|
+ def retrieve(self, request, *args, **kwargs):
|
|
|
+ instance = self.get_object()
|
|
|
+ serializer = StaffExamLogRetrieveSerializer(instance)
|
|
|
+ answer_log = []
|
|
|
+ answer_logs = ExamAnswerLog.objects.filter(main=instance).order_by('detail__order')
|
|
|
+ for al in answer_logs:
|
|
|
+ item = {
|
|
|
+ 'id':al.id,
|
|
|
+ 'status':al.status,
|
|
|
+ }
|
|
|
+ answer_log.append(item)
|
|
|
+ ranks = []
|
|
|
+ rank_data = ExamLog.objects.filter(exam=instance.exam, rank__isnull=False, delete=False).order_by('rank').values('user__name','scores','rank')
|
|
|
+ for rank in rank_data:
|
|
|
+ item = {
|
|
|
+ 'name':rank['user__name'],
|
|
|
+ 'scores':rank['scores'],
|
|
|
+ 'rank':rank['rank'],
|
|
|
+ }
|
|
|
+ ranks.append(item)
|
|
|
+ result = {
|
|
|
+ 'question': serializer.data,
|
|
|
+ 'answer_log': answer_log,
|
|
|
+ 'ranks': ranks,
|
|
|
+ }
|
|
|
+ return response_ok(result)
|
|
|
+
|
|
|
+ @action(methods=['get'], detail=False)
|
|
|
+ def get_home_exam(self, request):
|
|
|
+ # 首页,考试提示
|
|
|
+ start_time = timezone.now() + datetime.timedelta(minutes=30)
|
|
|
+ queryset = ExamLog.objects.filter(delete=False,
|
|
|
+ type=ExamLog.FORMAL,
|
|
|
+ exam__exam_time__lte=start_time,
|
|
|
+ user=request.user,
|
|
|
+ exam__exam_end_time__gte=timezone.now()
|
|
|
+ )
|
|
|
+ data = []
|
|
|
+ for exam_log in queryset:
|
|
|
+ if exam_log.exam.exam_time < timezone.now() < exam_log.exam.exam_end_time:
|
|
|
+ name = '您有一个考试,正在进行中,点击这里立即参加!'
|
|
|
+ id = exam_log.id
|
|
|
+ else:
|
|
|
+ time = (exam_log.exam.exam_time - timezone.now()).seconds
|
|
|
+ name = '您有一个考试,将在{}分钟后开始,请做好准备!'.format(int(time/60) + 1)
|
|
|
+ id = ''
|
|
|
+ item = {
|
|
|
+ 'id':id,
|
|
|
+ 'name':name,
|
|
|
+ 'exam_end_time':exam_log.exam.exam_end_time,
|
|
|
+ }
|
|
|
+ data.append(item)
|
|
|
+ return response_ok(data)
|
|
|
+
|
|
|
+ @action(methods=['get'], detail=False)
|
|
|
+ def get_exam(self, request):
|
|
|
+ # 正式考试,获取考试列表,提前半小时
|
|
|
+ start_time = timezone.now() + datetime.timedelta(minutes=30)
|
|
|
+ queryset = ExamLog.objects.filter(delete=False,
|
|
|
+ type=ExamLog.FORMAL,
|
|
|
+ exam__exam_time__lte=start_time,
|
|
|
+ user=request.user,
|
|
|
+ exam__exam_end_time__gte=timezone.now()
|
|
|
+ )
|
|
|
+ data = []
|
|
|
+ for exam_log in queryset:
|
|
|
+ item = {
|
|
|
+ 'id':exam_log.id,
|
|
|
+ 'name':exam_log.exam.name,
|
|
|
+ 'subject':exam_log.exam.subject.name,
|
|
|
+ 'exam_time':strftime(exam_log.exam.exam_time),
|
|
|
+ 'exam_end_time':strftime(exam_log.exam.exam_end_time),
|
|
|
+ 'duration':exam_log.exam.duration,
|
|
|
+ 'submit_time':exam_log.submit_time and True or False,
|
|
|
+ }
|
|
|
+ data.append(item)
|
|
|
+ return response_ok(data)
|
|
|
+
|
|
|
+ @action(methods=['post'], detail=True)
|
|
|
+ def get_next_practise(self, request, pk):
|
|
|
+ now_practise = request.data.get('now_practise') # 当前提交的模拟考试明细id。第一题或继续答题时,该参数为空
|
|
|
+ answers = json.loads(request.data.get('answers')) # 答案, 第一题或继续答题时,该参数为空
|
|
|
+ next_practise = request.data.get('next_practise') # 下一题id,首次加载第一题,传空
|
|
|
+ submit = request.data.get('submit') # 交卷1,下一题为空
|
|
|
+
|
|
|
+ try:
|
|
|
+ with transaction.atomic():
|
|
|
+ instance = self.get_object()
|
|
|
+ if instance.submit_time:
|
|
|
+ raise CustomError('您已交卷,禁止重复答题!')
|
|
|
+ if timezone.now() < instance.exam.exam_time:
|
|
|
+ raise CustomError('还未到考试时间,请稍等!')
|
|
|
+ if not submit and timezone.now() > instance.exam.exam_end_time:
|
|
|
+ raise CustomError('考试已结束,禁止答题!')
|
|
|
+ # 点击下一题,保存
|
|
|
+ if now_practise:
|
|
|
+ detail = ExamPaperDetail.objects.filter(id=now_practise).first()
|
|
|
+ if not detail:
|
|
|
+ raise CustomError('提交的考试习题有误,请刷新重试!')
|
|
|
+ now_question = detail.question
|
|
|
+ if len(answers) > 0:
|
|
|
+ answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance,
|
|
|
+ detail=detail, )
|
|
|
+ if now_question.type <= ExamQuestion.MULTIPLE:
|
|
|
+ # 单选、多选
|
|
|
+ answers.sort()
|
|
|
+ ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
|
|
|
+ for a in answers:
|
|
|
+ ExamAnswerOptionLog.objects.create(main=answer_log, option_id=a)
|
|
|
+ elif now_question.type == ExamQuestion.FILL:
|
|
|
+ # 填空
|
|
|
+ answers_len = len(answers)
|
|
|
+ ExamAnswerFillLog.objects.filter(main=answer_log).delete()
|
|
|
+ for a in range(0, answers_len):
|
|
|
+ ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a], order=a + 1)
|
|
|
+ else:
|
|
|
+ # 判断
|
|
|
+ if answers[0] == 1:
|
|
|
+ answer_log.status = ExamAnswerLog.RIGHT
|
|
|
+ else:
|
|
|
+ answer_log.status = ExamAnswerLog.WRONG
|
|
|
+ answer_log.save()
|
|
|
+ else:
|
|
|
+ try:
|
|
|
+ answer_log = ExamAnswerLog.objects.get(main=instance, detail=detail)
|
|
|
+ ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
|
|
|
+ ExamAnswerFillLog.objects.filter(main=answer_log).delete()
|
|
|
+ answer_log.status = ExamAnswerLog.NOTDONE
|
|
|
+ answer_log.save()
|
|
|
+ except ExamAnswerLog.DoesNotExist:
|
|
|
+ # traceback.print_exc()
|
|
|
+ pass
|
|
|
+
|
|
|
+ question_data = {}
|
|
|
+ # 返回下一题
|
|
|
+ if next_practise:
|
|
|
+ detail = ExamPaperDetail.objects.filter(id=next_practise, main=instance.exampaper,
|
|
|
+ delete=False).first()
|
|
|
+ else:
|
|
|
+ detail = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).first()
|
|
|
+
|
|
|
+ if detail:
|
|
|
+ question = detail.question
|
|
|
+ question_data = {
|
|
|
+ 'id': detail.id,
|
|
|
+ 'question': question.id,
|
|
|
+ 'title': question.title,
|
|
|
+ 'next_type': question.type, # 下一题习题类别
|
|
|
+ 'next_number': detail.order + 1, # 下下一题序号,
|
|
|
+ 'option': [],
|
|
|
+ }
|
|
|
+
|
|
|
+ answer_log = ExamAnswerLog.objects.filter(main=instance, detail=detail).first()
|
|
|
+ if question.type == ExamQuestion.JUDGMENT:
|
|
|
+ item1 = {
|
|
|
+ 'id': 1,
|
|
|
+ 'content': '正确',
|
|
|
+ 'answer': True if answer_log and answer_log.status == ExamAnswerLog.RIGHT else False
|
|
|
+ }
|
|
|
+ item0 = {
|
|
|
+ 'id': 0,
|
|
|
+ 'content': '错误',
|
|
|
+ 'answer': True if answer_log and answer_log.status == ExamAnswerLog.WRONG else False
|
|
|
+ }
|
|
|
+ question_data['option'].append(item1)
|
|
|
+ question_data['option'].append(item0)
|
|
|
+ elif question.type <= ExamQuestion.MULTIPLE:
|
|
|
+ rows = ExamQuestionOption.objects.filter(main=question, delete=False)
|
|
|
+ for row in rows:
|
|
|
+ option_log = ExamAnswerOptionLog.objects.filter(main=answer_log, option=row).first()
|
|
|
+ item = {
|
|
|
+ 'id': row.id,
|
|
|
+ 'content': row.content,
|
|
|
+ 'answer': option_log and True or False
|
|
|
+ }
|
|
|
+ question_data['option'].append(item)
|
|
|
+ elif question.type == ExamQuestion.FILL:
|
|
|
+ rows = ExamQuestionFill.objects.filter(main=question, delete=False)
|
|
|
+ for row in rows:
|
|
|
+ option_log = ExamAnswerFillLog.objects.filter(main=answer_log, order=row.order).first()
|
|
|
+ item = {
|
|
|
+ 'id': row.order, # 填空题序号
|
|
|
+ 'content': option_log and option_log.content or '',
|
|
|
+ }
|
|
|
+ question_data['option'].append(item)
|
|
|
+
|
|
|
+ # 右侧习题类别列表
|
|
|
+ # 单选、多选、填空。选择答案后,可能会把答案清空,得加上NOTDONE过滤
|
|
|
+ questions = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).values_list('id',
|
|
|
+ flat=True)
|
|
|
+ single_questions_list = []
|
|
|
+ for single in questions.filter(question__type=ExamQuestion.SINGLE):
|
|
|
+ answers = ExamAnswerOptionLog.objects.filter(main__main=instance, main__detail=single).first()
|
|
|
+ single_questions_list.append(
|
|
|
+ {
|
|
|
+ 'question_id': single,
|
|
|
+ 'complete': answers and True or False,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ # 多选题
|
|
|
+ multiple_questions_list = []
|
|
|
+ for multiple in questions.filter(question__type=ExamQuestion.MULTIPLE):
|
|
|
+ answers = ExamAnswerOptionLog.objects.filter(main__main=instance, main__detail=multiple).first()
|
|
|
+ multiple_questions_list.append(
|
|
|
+ {
|
|
|
+ 'question_id': multiple,
|
|
|
+ 'complete': answers and True or False,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ # 填空题
|
|
|
+ fill_questions_list = []
|
|
|
+ for fill in questions.filter(question__type=ExamQuestion.FILL):
|
|
|
+ answers = ExamAnswerFillLog.objects.filter(main__main=instance, main__detail=multiple).first()
|
|
|
+ fill_questions_list.append(
|
|
|
+ {
|
|
|
+ 'question_id': fill,
|
|
|
+ 'complete': answers and True or False,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ # 判断题
|
|
|
+ judgment_questions_list = []
|
|
|
+ for judgment in questions.filter(question__type=ExamQuestion.JUDGMENT):
|
|
|
+ answer_log = ExamAnswerLog.objects.filter(main=instance, detail=judgment).exclude(status=ExamAnswerLog.NOTDONE)
|
|
|
+ judgment_questions_list.append(
|
|
|
+ {
|
|
|
+ 'question_id': judgment,
|
|
|
+ 'complete': answer_log and True or False,
|
|
|
+ }
|
|
|
+ )
|
|
|
+ result = {
|
|
|
+ 'question_data': question_data, # 下一题练习题
|
|
|
+ 'single_questions_list': single_questions_list, # 单选
|
|
|
+ 'multiple_questions_list': multiple_questions_list, # 多选
|
|
|
+ 'fill_questions_list': fill_questions_list, # 填空
|
|
|
+ 'judgment_questions_list': judgment_questions_list, # 判断
|
|
|
+ }
|
|
|
+ return response_ok(result)
|
|
|
+ except CustomError as e:
|
|
|
+ return response_error(e.get_error_msg())
|
|
|
+ except Exception as e:
|
|
|
+ traceback.print_exc()
|
|
|
+ return response_error(str(e))
|
|
|
+
|
|
|
+ @action(methods=['post'], detail=True)
|
|
|
+ def submit_practise(self, request, pk):
|
|
|
+ # 习题交卷,把上个接口的判断答案,放到此处
|
|
|
+ try:
|
|
|
+ instance = self.get_object()
|
|
|
+ if timezone.now() > instance.exam.exam_end_time:
|
|
|
+ raise CustomError('考试已结束,禁止答题!')
|
|
|
+ if instance.submit_time:
|
|
|
+ raise CustomError('您已交卷,禁止重复交卷!')
|
|
|
+ with transaction.atomic():
|
|
|
+ paper_details = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False)
|
|
|
+ for detail in paper_details:
|
|
|
+ # 创建模拟考试未答题记录,如果没有找到答案记录,则该题保留未答状态
|
|
|
+ answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance, detail=detail)
|
|
|
+ # answer_logs = ExamAnswerLog.objects.filter(main=instance)
|
|
|
+ # for answer_log in answer_logs:
|
|
|
+ question = detail.question
|
|
|
+ if question.type == ExamQuestion.SINGLE:
|
|
|
+ # 单选
|
|
|
+ answers = ExamAnswerOptionLog.objects.filter(main=answer_log).first()
|
|
|
+ if answers:
|
|
|
+ if answers.option.right:
|
|
|
+ answer_log.status = ExamAnswerLog.RIGHT
|
|
|
+ instance.single_answer_count += 1
|
|
|
+ else:
|
|
|
+ answer_log.status = ExamAnswerLog.WRONG
|
|
|
+ ErrorBook.add_error(question, request.user, answer_log)
|
|
|
+ elif question.type == ExamQuestion.MULTIPLE:
|
|
|
+ # 多选
|
|
|
+ answers = ExamAnswerOptionLog.objects.filter(main=answer_log).order_by('option_id').values_list(
|
|
|
+ 'option_id', flat=True)
|
|
|
+ if answers:
|
|
|
+ right = ExamQuestionOption.objects.filter(main=question, right=True,
|
|
|
+ delete=False).values_list('id', flat=True)
|
|
|
+ if list(answers) == list(right):
|
|
|
+ answer_log.status = ExamAnswerLog.RIGHT
|
|
|
+ instance.multiple_answer_count += 1
|
|
|
+ else:
|
|
|
+ answer_log.status = ExamAnswerLog.WRONG
|
|
|
+ ErrorBook.add_error(question, request.user, answer_log)
|
|
|
+ elif question.type == ExamQuestion.FILL:
|
|
|
+ # 填空
|
|
|
+ fill_logs = ExamAnswerFillLog.objects.filter(main=answer_log)
|
|
|
+ right = True
|
|
|
+ if fill_logs:
|
|
|
+ for fill_log in fill_logs:
|
|
|
+ right_answer = ExamQuestionFill.objects.filter(main=question, content=fill_log.content,
|
|
|
+ order=fill_log.order, delete=False)
|
|
|
+ if not right_answer:
|
|
|
+ right = False
|
|
|
+ break
|
|
|
+ if right:
|
|
|
+ answer_log.status = ExamAnswerLog.RIGHT
|
|
|
+ instance.fill_answer_count += 1
|
|
|
+ else:
|
|
|
+ answer_log.status = ExamAnswerLog.WRONG
|
|
|
+ ErrorBook.add_error(question, request.user, answer_log)
|
|
|
+ else:
|
|
|
+ # 判断
|
|
|
+ if answer_log.status != ExamAnswerLog.NOTDONE:
|
|
|
+ if question.judgment == (answer_log.status == ExamAnswerLog.RIGHT):
|
|
|
+ answer_log.status = ExamAnswerLog.RIGHT
|
|
|
+ instance.judgment_answer_count += 1
|
|
|
+ else:
|
|
|
+ answer_log.status = ExamAnswerLog.WRONG
|
|
|
+ ErrorBook.add_error(question, request.user, answer_log)
|
|
|
+ answer_log.save()
|
|
|
+
|
|
|
+ instance.submit_time = timezone.now()
|
|
|
+ use_time = instance.submit_time - instance.exam_time
|
|
|
+ instance.use_time = use_time.seconds
|
|
|
+
|
|
|
+ single_answer_scores = instance.single_answer_count * instance.exampaper.single_scores
|
|
|
+ multiple_answer_scores = instance.multiple_answer_count * instance.exampaper.multiple_scores
|
|
|
+ fill_answer_scores = instance.fill_answer_count * instance.exampaper.fill_scores
|
|
|
+ judgment_answer_scores = instance.judgment_answer_count * instance.exampaper.judgment_scores
|
|
|
+
|
|
|
+ instance.single_answer_scores = single_answer_scores
|
|
|
+ instance.multiple_answer_scores = multiple_answer_scores
|
|
|
+ instance.fill_answer_scores = fill_answer_scores
|
|
|
+ instance.judgment_answer_scores = judgment_answer_scores
|
|
|
+ instance.scores = single_answer_scores + judgment_answer_scores + multiple_answer_scores + fill_answer_scores
|
|
|
+ # 计算排名
|
|
|
+ up_rank = ExamLog.objects.filter(exam=instance.exam, scores__gte=instance.scores).count()
|
|
|
+ instance.rank = up_rank + 1
|
|
|
+ ExamLog.objects.filter(exam=instance.exam, scores__lte=instance.scores).update(rank=F('rank') + 1)
|
|
|
+ instance.save()
|
|
|
+
|
|
|
+ instance.exampaper.did_count += 1
|
|
|
+ instance.exampaper.save()
|
|
|
+
|
|
|
+ SysLog.objects.addnew(request.user, SysLog.INSERT, u"提交模拟考试题,id=%d" % (instance.id))
|
|
|
+ except CustomError as e:
|
|
|
+ return response_error(e.get_error_msg())
|
|
|
+ except Exception as e:
|
|
|
+ traceback.print_exc()
|
|
|
+ return response_error(str(e))
|
|
|
+ return response_ok()
|
|
|
+
|
|
|
+
|
|
|
+class ExamQuestionFeedbackViewSet(CustomModelViewSet):
|
|
|
+ permission_classes = [IsStaff, ]
|
|
|
+ queryset = ExamQuestionFeedback.objects.filter()
|
|
|
+ serializer_class = ExamQuestionFeedbackSerializer
|
|
|
+
|
|
|
+ def perform_create(self, serializer):
|
|
|
+ super(ExamQuestionFeedbackViewSet, self).perform_create(serializer)
|
|
|
+ instance = serializer.instance
|
|
|
+ validated_data = serializer.validated_data
|
|
|
+ SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加试题错误反馈,id=%d' % instance.id, validated_data)
|