Преглед изворни кода

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	apps/api/staff/urls.py
jiaweiqi пре 3 година
родитељ
комит
cdc83a243d

+ 14 - 0
apps/api/staff/exam/urls.py

@@ -0,0 +1,14 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+
+]
+
+router = SimpleRouter()
+router.register(r'exam_log', ExamLogViewSet)
+router.register(r'', ExamViewSet)
+urlpatterns += router.urls

+ 316 - 0
apps/api/staff/exam/views.py

@@ -0,0 +1,316 @@
+# coding=utf-8
+import json
+import traceback
+from django.utils import timezone
+from rest_framework.decorators import action
+from rest_framework.serializers import ValidationError
+from rest_framework.views import APIView
+from django.db import transaction
+from django.db.models import Q, Sum, F
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.permission import IsAdministrator, IsStaff
+from apps.examination.exam.serializers import *
+from apps.examination.exam.filters import *
+from apps.system.models import SysLog
+from apps.foundation.serializers import SubjectSerializer, ChapterSerializer, SubjectSimpleSerializer, \
+    ChapterSimpleSerializer, Subject
+from apps.examination.exam.models import ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
+from apps.examination.exampaper.models import ExamPaper, ExamPaperDetail, ExamQuestion
+from apps.examination.exampaper.filters import ExamPaperFilter
+from apps.examination.exampaper.serializers import StaffExamPaperSerializer
+from apps.examination.examquestion.models import ExamQuestionOption, ExamQuestionFill
+from apps.practise.errorbook.models import ErrorBook
+
+
+class ExamViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    end_date = str(timezone.now().date()) + ' 23:59:59'
+    queryset = Exam.objects.filter(delete=False, exam_time__gte=timezone.now().date(), exam_time__lte=end_date)
+    serializer_class = ExamSerializer
+
+    def filter_queryset(self, queryset):
+        f = ExamFilter(self.request.GET, queryset=queryset)
+        return f.qs
+
+class ExamLogViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = ExamLog.objects.filter(delete=False)
+    serializer_class = StaffExamLogSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(user=self.request.user)
+        return queryset
+
+    def create(self, request, *args, **kwargs):
+        exam_id = request.data.get('exam')
+        exampaper_id = request.data.get('exampaper')
+
+        try:
+            with transaction.atomic():
+                data = {
+                    'type': ExamPaper.FORMAL,
+                    'exam': exam_id,
+                    'exampaper': exampaper_id,
+                    'user': request.user.id,
+                    'exam_time': timezone.now()
+                }
+                serializer = StaffExamLogSerializer(data=data)
+                if serializer.is_valid(raise_exception=True):
+                    instance = serializer.save()
+                    result = {
+                        'exam_log': instance.id,  # 模拟考试 id
+                    }
+                    return response_ok(result)
+        except ValidationError as e:
+            traceback.print_exc()
+            return response_error('数据格式有误')
+
+    @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,首次加载第一题,传空
+
+        try:
+            with transaction.atomic():
+                instance = self.get_object()
+                # 点击下一题,保存、判断当前习题答案
+                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,
+                        '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):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=single)
+                    single_questions_list.append(
+                        {
+                            'question_id': single,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 多选题
+                multiple_questions_list = []
+                for multiple in questions.filter(question__type=ExamQuestion.MULTIPLE):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=multiple)
+                    multiple_questions_list.append(
+                        {
+                            'question_id': multiple,
+                            'complete': answer_log and True or False,
+                        }
+                    )
+                # 填空题
+                fill_questions_list = []
+                for fill in questions.filter(question__type=ExamQuestion.FILL):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=fill)
+                    fill_questions_list.append(
+                        {
+                            'question_id': fill,
+                            'complete': answer_log 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)
+                    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()
+
+            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.exam_time - instance.submit_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
+                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()

+ 1 - 1
apps/api/staff/mock/views.py

@@ -56,6 +56,7 @@ class ExamLogViewSet(CustomModelViewSet):
         result = {
             'question': serializer.data,
             'answer_log': answer_log,
+            'ranks': [],
         }
         return response_ok(result)
 
@@ -64,7 +65,6 @@ class ExamLogViewSet(CustomModelViewSet):
 
         try:
             with transaction.atomic():
-                exampaper = ExamPaper.getById(exampaper_id)
                 data = {
                     'type': ExamPaper.MOCK,
                     'exampaper': exampaper_id,

+ 1 - 0
apps/api/staff/urls.py

@@ -13,5 +13,6 @@ urlpatterns = [
     url(r'^practise/', include('apps.api.staff.practise.urls')),
     url(r'^mock/', include('apps.api.staff.mock.urls')),
     url(r'^errorbook/', include('apps.api.staff.errorbook.urls')),
+    url(r'^exam/', include('apps.api.staff.exam.urls')),
     url(r'^knowledge/', include('apps.api.staff.knowledge.urls')),
 ]

+ 1 - 0
apps/examination/exam/models.py

@@ -24,6 +24,7 @@ class Exam(models.Model):
     type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"试卷类型", null=True, editable=False)
     duration = models.IntegerField(verbose_name=u'时长')
     exam_time = models.DateTimeField(verbose_name=u"考试时间")
+    exam_end_time = models.DateTimeField(verbose_name=u"考试结束时间", null=True, editable=False)
     exampaper = models.ForeignKey(ExamPaper, verbose_name=u"试卷", on_delete=models.PROTECT, null=True, editable=True)
     desc = models.TextField(verbose_name=u"备注", null=True, blank=True)
 

+ 46 - 11
apps/examination/exam/serializers.py

@@ -1,5 +1,5 @@
 # coding=utf-8
-
+import datetime
 from rest_framework import serializers
 from .models import Exam, ExamLog, ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
 from apps.examination.examquestion.models import ExamQuestion
@@ -31,12 +31,16 @@ class ExamSerializer(serializers.ModelSerializer):
 
     def create(self, validated_data):
         validated_data['create_user'] = self.context['request'].user
+        exam_end_time = validated_data['exam_time'] +  datetime.timedelta(minutes=validated_data['duration'])
+        validated_data['exam_end_time'] = exam_end_time
         instance = super(ExamSerializer, self).create(validated_data)
         return instance
 
     def update(self, instance, validated_data):
         if instance.delete:
             raise CustomError(u'考试[%s]已经被删除,禁止操作' % instance.name)
+        exam_end_time = validated_data['exam_time'] +  datetime.timedelta(minutes=validated_data['duration'])
+        validated_data['exam_end_time'] = exam_end_time
         instance = super(ExamSerializer, self).update(instance, validated_data)
         instance.change_examtime()
         instance.save()
@@ -85,17 +89,8 @@ class StaffExamLogRetrieveSerializer(serializers.ModelSerializer):
     question_total_count = serializers.CharField(source='exampaper.question_total_count', read_only=True)
     question_total_scores = serializers.CharField(source='exampaper.question_total_scores', read_only=True)
 
-    single_total_count = serializers.CharField(source='exampaper.single_total_count', read_only=True)
-    multiple_total_count = serializers.CharField(source='exampaper.multiple_total_count', read_only=True)
-    fill_total_count = serializers.CharField(source='exampaper.fill_total_count', read_only=True)
-    judgment_total_count = serializers.CharField(source='exampaper.judgment_total_count', read_only=True)
-
-    single_total_scores = serializers.CharField(source='exampaper.single_total_scores', read_only=True)
-    multiple_total_scores = serializers.CharField(source='exampaper.multiple_total_scores', read_only=True)
-    fill_total_scores = serializers.CharField(source='exampaper.fill_total_scores', read_only=True)
-    judgment_total_scores = serializers.CharField(source='exampaper.judgment_total_scores', read_only=True)
-
     total_right_count = serializers.SerializerMethodField()
+    answer_items = serializers.SerializerMethodField()
 
     class Meta:
         model = ExamLog
@@ -104,6 +99,46 @@ class StaffExamLogRetrieveSerializer(serializers.ModelSerializer):
     def get_total_right_count(self, obj):
         return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count
 
+    def get_answer_items(self, obj):
+        data = []
+        if obj.exampaper.single_total_count:
+            item = {
+                'name': '单选题',
+                'total_count': obj.exampaper.single_total_count,
+                'total_right': obj.single_answer_count,
+                'total_scores': obj.exampaper.single_total_scores,
+                'answer_scores': obj.single_answer_scores,
+            }
+            data.append(item)
+        if obj.exampaper.multiple_total_count:
+            item = {
+                'name': '多选题',
+                'total_count': obj.exampaper.multiple_total_count,
+                'total_right': obj.multiple_answer_count,
+                'total_scores': obj.exampaper.multiple_total_scores,
+                'answer_scores': obj.multiple_answer_scores,
+            }
+            data.append(item)
+        if obj.exampaper.fill_total_count:
+            item = {
+                'name': '填空题',
+                'total_count': obj.exampaper.fill_total_count,
+                'total_right': obj.fill_answer_count,
+                'total_scores': obj.exampaper.fill_total_scores,
+                'answer_scores': obj.fill_answer_scores,
+            }
+            data.append(item)
+        if obj.exampaper.judgment_total_count:
+            item = {
+                'name': '判断题',
+                'total_count': obj.exampaper.judgment_total_count,
+                'total_right': obj.judgment_answer_count,
+                'total_scores': obj.exampaper.judgment_total_scores,
+                'answer_scores': obj.judgment_answer_scores,
+            }
+            data.append(item)
+        return data
+
 
 class ExamAnswerLogSimpleSerializer(serializers.ModelSerializer):
     item = serializers.SerializerMethodField()