wushaodong %!s(int64=2) %!d(string=hai) anos
pai
achega
e533d5ffb3

+ 40 - 0
apps/api/admin/exam/views.py

@@ -1,6 +1,7 @@
 # coding=utf-8
 
 import json
+import traceback
 from django.utils import timezone
 from django.db import transaction
 from rest_framework.viewsets import ReadOnlyModelViewSet
@@ -131,3 +132,42 @@ class ExamLogViewSet(ReadOnlyModelViewSet):
         queryset = self.filter_queryset(self.queryset)
         serializer = self.get_serializer(queryset, many=True)
         return response_ok(serializer.data)
+
+    @action(methods=['get'], detail=True)
+    def get_discuss(self, request, pk):
+        result = []
+        rows = ExamAnswerLog.objects.filter(main_id=pk, status=ExamAnswerLog.WAIT_CHECK).values('id',
+                            'detail__question__title','detail__question__discuss_answer','detail__main__discuss_scores', 'discuss_answer')
+        for row in rows:
+            item = {
+                'id':row['id'],
+                'question': row['detail__question__title'],# 问题
+                'answer': row['detail__question__discuss_answer'], # 正确答案
+                'discuss_answer': row['discuss_answer'], # 学员答案
+                'discuss_scores': row['detail__main__discuss_scores'], # 该题分数
+            }
+            result.append(item)
+        return response_ok(result)
+
+    @action(methods=['post'], detail=True)
+    def discuss_check(self, request, pk):
+        items = json.loads(request.POST.get('items'))
+        try:
+            instance = self.get_object()
+            with transaction.atomic():
+                discuss_answer_scores = 0
+                for item in items:
+                    discuss_scores = int(item['user_scores'])
+                    discuss_answer_scores += discuss_scores
+                    ExamAnswerLog.objects.filter(id=item['id'], status=ExamAnswerLog.WAIT_CHECK).\
+                        update(status=ExamAnswerLog.NOTDONE, discuss_scores=discuss_scores)
+                instance.discuss_answer_scores = discuss_answer_scores
+                instance.scores += discuss_answer_scores
+                instance.save()
+
+        except CustomError as e:
+            return response_error(e.get_error_msg())
+        except Exception as e:
+            traceback.print_exc()
+            return response_error(u'保存失败!')
+        return response_ok()

+ 79 - 1
apps/api/admin/examquestion/views.py

@@ -401,6 +401,84 @@ class ExamQuestionViewSet(CustomModelViewSet):
             return response_error(str(e))
         return response_ok()
 
+
+    @action(methods=['post'], detail=False)
+    def import_discuss(self, request):
+        file = request.FILES.get('excel_file')
+        status = ExcelImporter.validity(file)
+        if not status['success']:
+            return response_error(status['errors'])
+
+        data = status['data']
+        line = 2
+        try:
+            with transaction.atomic():
+                for row in data:
+                    try:
+                        title = row['试题内容']
+                        subject = row['科目']
+                        chapter = row['章节']
+                        difficulty_text = row['难度']
+                        scores = row['分数']
+                        analysis = row['解析']
+                        discuss_text = row['正确答案']
+                    except:
+                        raise CustomError(u'模版文件不正确,请检查模版文件或重新下载')
+
+                    if not title:
+                        raise CustomError(u'第%d行:试题内容不能为空' % line)
+                    if not subject:
+                        raise CustomError(u'第%d行:科目不能为空' % line)
+                    if not chapter:
+                        raise CustomError(u'第%d行:章节不能为空' % line)
+                    if not difficulty_text:
+                        raise CustomError(u'第%d行:难度不能为空' % line)
+                    if not scores:
+                        raise CustomError(u'第%d行:分数不能为空' % line)
+                    if not discuss_text:
+                        raise CustomError(u'第%d行:答案不能为空' % line)
+
+                    if difficulty_text == u'简单':
+                        difficulty = ExamQuestion.SIMPLE
+                    elif difficulty_text == u'中等':
+                        difficulty = ExamQuestion.MID
+                    elif difficulty_text == u'困难':
+                        difficulty = ExamQuestion.HARD
+                    else:
+                        raise CustomError(u'第%d行:难度为无效数据' % line)
+
+                    subject = Subject.objects.filter(name=subject, delete=False).first()
+                    if not subject:
+                        raise CustomError(u'第%d行:科目为无效数据' % line)
+                    chapter = Chapter.objects.filter(subject=subject, name=chapter, delete=False).first()
+                    if not chapter:
+                        raise CustomError(u'第%d行:章节为无效数据' % line)
+                    try:
+                        scores = int(scores)
+                    except:
+                        raise CustomError(u'第%d行:分数为无效数据' % line)
+
+                    ExamQuestion.objects.create(
+                        chapter=chapter,
+                        type=ExamQuestion.DISCUSS,
+                        difficulty=difficulty,
+                        scores=scores,
+                        title=title,
+                        discuss_answer=discuss_text,
+                        analysis=analysis,
+                        create_user=request.user,
+                        create_time=timezone.now()
+                    )
+
+                    line += 1
+                SysLog.objects.addnew(request.user, SysLog.IMPORT, u"导入[%d]道论述题" % (line - 2))
+        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(ReadOnlyModelViewSet):
     permission_classes = [IsAdministrator, ]
     queryset = ExamQuestionFeedback.objects.filter().order_by('status', '-id')
@@ -408,4 +486,4 @@ class ExamQuestionFeedbackViewSet(ReadOnlyModelViewSet):
 
     def filter_queryset(self, queryset):
         f = ExamQuestionFeedbackFilter(self.request.GET, queryset=queryset)
-        return f.qs
+        return f.qs

+ 2 - 2
apps/api/admin/views.py

@@ -6,7 +6,7 @@ from rest_framework.serializers import ValidationError
 from rest_framework.views import APIView
 from utils import response_error, response_ok
 from utils.permission import IsAdministrator
-from utils.empower import checkLicence
+#from utils.empower import checkLicence
 from apps.staff.serializers import AdminUserJWTSerializer
 from utils.exceptions import CustomError
 
@@ -53,4 +53,4 @@ class LogoutView(APIView):
 
     def get(self, request):
         User.objects.filter(pk=request.user.pk).update(status=User.OFFLINE)
-        return response_ok()
+        return response_ok()

+ 29 - 3
apps/api/staff/exam/views.py

@@ -130,6 +130,7 @@ class ExamLogViewSet(CustomModelViewSet):
                     now_question = detail.question
 
                     has_answers = len(answers) > 0
+                    # 填空题,判断至少填了一个空
                     if now_question.type == ExamQuestion.FILL:
                         has_answers = False
                         for a in range(0, len(answers)):
@@ -152,6 +153,11 @@ class ExamLogViewSet(CustomModelViewSet):
                             ExamAnswerFillLog.objects.filter(main=answer_log).delete()
                             for a in range(0, answers_len):
                                 ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a] or '', order=a + 1)
+                        elif now_question.type == ExamQuestion.DISCUSS:
+                            # 论述题
+                            answer_log.status = ExamAnswerLog.WAIT_CHECK
+                            answer_log.discuss_answer = answers[0]
+                            answer_log.save()
                         else:
                             # 判断
                             if answers[0] == 1:
@@ -223,6 +229,12 @@ class ExamLogViewSet(CustomModelViewSet):
                                 'content': option_log and option_log.content or '',
                             }
                             question_data['option'].append(item)
+                    elif question.type == ExamQuestion.DISCUSS: # 论述题
+                        item = {
+                            'id': question.id,  # 论述题序号
+                            'content': answer_log and answer_log.discuss_answer or '',
+                        }
+                        question_data['option'].append(item)
 
                 # 右侧习题类别列表
                 # 单选、多选、填空。选择答案后,可能会把答案清空,得加上NOTDONE过滤
@@ -267,12 +279,23 @@ class ExamLogViewSet(CustomModelViewSet):
                             'complete': answer_log and True or False,
                         }
                     )
+                # 论述题
+                discuss_questions_list = []
+                for discuss in questions.filter(question__type=ExamQuestion.DISCUSS):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=discuss).exclude(status=ExamAnswerLog.NOTDONE)
+                    discuss_questions_list.append(
+                        {
+                            'question_id': discuss,
+                            '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,  # 判断
+                    'discuss_questions_list': discuss_questions_list,  # 论述
                 }
                 return response_ok(result)
         except CustomError as e:
@@ -294,7 +317,7 @@ class ExamLogViewSet(CustomModelViewSet):
             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:
@@ -339,7 +362,7 @@ class ExamLogViewSet(CustomModelViewSet):
                             else:
                                 answer_log.status = ExamAnswerLog.WRONG
                                 ErrorBook.add_error(question, request.user, answer_log)
-                    else:
+                    elif question.type == ExamQuestion.JUDGMENT:
                         # 判断
                         if answer_log.status != ExamAnswerLog.NOTDONE:
                             if question.judgment == (answer_log.status == ExamAnswerLog.RIGHT):
@@ -348,6 +371,9 @@ class ExamLogViewSet(CustomModelViewSet):
                             else:
                                 answer_log.status = ExamAnswerLog.WRONG
                                 ErrorBook.add_error(question, request.user, answer_log)
+                    else:
+                        # 论述题 需要人工评分
+                        pass
                     answer_log.save()
 
                 instance.submit_time = timezone.now()
@@ -373,7 +399,7 @@ class ExamLogViewSet(CustomModelViewSet):
                 instance.exampaper.did_count += 1
                 instance.exampaper.save()
 
-                SysLog.objects.addnew(request.user, SysLog.INSERT, u"提交模拟考试题,id=%d" % (instance.id))
+                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:

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

@@ -143,6 +143,11 @@ class ExamLogViewSet(CustomModelViewSet):
                             ExamAnswerFillLog.objects.filter(main=answer_log).delete()
                             for a in range(0, answers_len):
                                 ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a] or '', order=a + 1)
+                        elif now_question.type == ExamQuestion.DISCUSS:
+                            # 论述题
+                            answer_log.status = ExamAnswerLog.WAIT_CHECK
+                            answer_log.discuss_answer = answers[0]
+                            answer_log.save()
                         else:
                             # 判断
                             if answers[0] == 1:
@@ -215,6 +220,12 @@ class ExamLogViewSet(CustomModelViewSet):
                                 'content': option_log and option_log.content or '',
                             }
                             question_data['option'].append(item)
+                    elif question.type == ExamQuestion.DISCUSS:  # 论述题
+                        item = {
+                            'id': question.id,  # 论述题序号
+                            'content': answer_log and answer_log.discuss_answer or '',
+                        }
+                        question_data['option'].append(item)
 
                 # 右侧习题类别列表
                 # 单选、多选、填空。选择答案后,可能会把答案清空,得加上NOTDONE过滤
@@ -259,12 +270,24 @@ class ExamLogViewSet(CustomModelViewSet):
                             'complete': answer_log and True or False,
                         }
                     )
+                # 论述题
+                discuss_questions_list = []
+                for discuss in questions.filter(question__type=ExamQuestion.DISCUSS):
+                    answer_log = ExamAnswerLog.objects.filter(main=instance, detail=discuss).exclude(
+                        status=ExamAnswerLog.NOTDONE)
+                    discuss_questions_list.append(
+                        {
+                            'question_id': discuss,
+                            '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,  # 判断
+                    'discuss_questions_list': discuss_questions_list,  # 论述
                 }
                 return response_ok(result)
         except CustomError as e:
@@ -328,7 +351,7 @@ class ExamLogViewSet(CustomModelViewSet):
                             else:
                                 answer_log.status = ExamAnswerLog.WRONG
                                 ErrorBook.add_error(question, request.user, answer_log)
-                    else:
+                    elif question.type == ExamQuestion.JUDGMENT:
                         # 判断
                         if answer_log.status != ExamAnswerLog.NOTDONE:
                             if question.judgment == (answer_log.status == ExamAnswerLog.RIGHT):
@@ -337,6 +360,9 @@ class ExamLogViewSet(CustomModelViewSet):
                             else:
                                 answer_log.status = ExamAnswerLog.WRONG
                                 ErrorBook.add_error(question, request.user, answer_log)
+                    else:
+                        # 论述题 需要人工评分
+                        pass
                     answer_log.save()
 
                 instance.submit_time = timezone.now()

+ 23 - 0
apps/api/staff/practise/views.py

@@ -123,6 +123,11 @@ class PractiseLogViewSet(CustomModelViewSet):
                                 answer_log.status = PractiseAnswerLog.RIGHT
                             else:
                                 answer_log.status = PractiseAnswerLog.WRONG
+                        elif now_question.type == ExamQuestion.DISCUSS:
+                            # 论述题
+                            answer_log.status = PractiseAnswerLog.WAIT_CHECK
+                            answer_log.discuss_answer = answers[0]
+                            answer_log.save()
                         else:
                             # 判断
                             if now_question.judgment == (answers[0] == 1):
@@ -203,6 +208,12 @@ class PractiseLogViewSet(CustomModelViewSet):
                                 'content': option_log and option_log.content or '',
                             }
                             question_data['option'].append(item)
+                    elif question.type == ExamQuestion.DISCUSS:  # 论述题
+                        item = {
+                            'id': question.id,  # 论述题序号
+                            'content': answer_log and answer_log.discuss_answer or '',
+                        }
+                        question_data['option'].append(item)
 
                 # 右侧习题类别列表
                 # 单选题
@@ -249,12 +260,24 @@ class PractiseLogViewSet(CustomModelViewSet):
                             'complete': answer_log and True or False,
                         }
                     )
+                # 论述题
+                discuss_questions_list = []
+                for discuss in questions.filter(type=ExamQuestion.DISCUSS):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=discuss)
+                    discuss_questions_list.append(
+                        {
+                            'question_id': discuss,
+                            '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,  # 判断
+                    'discuss_questions_list': discuss_questions_list,  # 论述
                 }
                 return response_ok(result)
         except CustomError as e:

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

@@ -108,11 +108,14 @@ class ExamLog(models.Model):
     multiple_answer_scores = models.IntegerField(verbose_name=u'多选题得分', default=0, editable=False)
     fill_answer_scores = models.IntegerField(verbose_name=u'填空题得分', default=0, editable=False)
     judgment_answer_scores = models.IntegerField(verbose_name=u'判断题得分', default=0, editable=False)
+    discuss_answer_scores = models.IntegerField(verbose_name=u'论述题得分', default=0, editable=False)
 
     single_answer_count = models.IntegerField(verbose_name=u'单选题答对数', default=0, editable=False)
     multiple_answer_count = models.IntegerField(verbose_name=u'多选题答对数', default=0, editable=False)
     fill_answer_count = models.IntegerField(verbose_name=u'填空题答对数', default=0, editable=False)
     judgment_answer_count = models.IntegerField(verbose_name=u'判断题答对数', default=0, editable=False)
+    discuss_answer_count = models.IntegerField(verbose_name=u'论述题答对数', default=0, editable=False)
+
     delete = models.BooleanField(verbose_name=u'删除', db_column='is_delete', default=False, editable=False)
 
     class Meta:
@@ -125,16 +128,20 @@ class ExamAnswerLog(models.Model):
     RIGHT = 1
     WRONG = 2
     NOTDONE = 3
+    WAIT_CHECK = 4
     STATUS_CHOICES = (
         (RIGHT, u'正确'),
         (WRONG, u'错误'),
         (NOTDONE, u'未做'),
+        (WAIT_CHECK, u'待评分'), # todo 论述题答题后,状态待确定
     )
     STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
 
     main = models.ForeignKey(ExamLog, verbose_name=u"考试记录", on_delete=models.PROTECT)
-    detail = models.ForeignKey(ExamPaperDetail, verbose_name=u"试题", on_delete=models.PROTECT)
+    detail = models.ForeignKey(ExamPaperDetail, verbose_name=u"试卷中试题", on_delete=models.PROTECT)
     status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'回答状态', default=NOTDONE)
+    discuss_answer = models.TextField(verbose_name=u"论述题答案", null=True, blank=True)
+    discuss_scores = models.TextField(verbose_name=u"论述题得分", default=0)
 
     class Meta:
         db_table = "exam_answer_log"

+ 14 - 3
apps/examination/exam/serializers.py

@@ -66,6 +66,8 @@ class FormalExamLogSerializer(serializers.ModelSerializer):
     fill_scores = serializers.IntegerField(source='exampaper.fill_total_scores', read_only=True)
     judgment_count = serializers.IntegerField(source='exampaper.judgment_total_count', read_only=True)
     judgment_scores = serializers.IntegerField(source='exampaper.judgment_total_scores', read_only=True)
+    discuss_count = serializers.IntegerField(source='exampaper.discuss_total_count', read_only=True)
+    discuss_scores = serializers.IntegerField(source='exampaper.discuss_total_scores', read_only=True)
     use_time = serializers.SerializerMethodField()
 
     class Meta:
@@ -89,10 +91,10 @@ class StaffExamLogSerializer(serializers.ModelSerializer):
                   'scores', 'right_count','wrong_count','rank',)
 
     def get_right_count(self, obj):
-        return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count
+        return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count +  obj.discuss_answer_count
 
     def get_wrong_count(self, obj):
-        return obj.exampaper.question_total_count - (obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count)
+        return obj.exampaper.question_total_count - (obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count +  obj.discuss_answer_count)
 
 class StaffExamLogRetrieveSerializer(serializers.ModelSerializer):
     exampaper_name = serializers.CharField(source='exampaper.name', read_only=True)
@@ -107,7 +109,7 @@ class StaffExamLogRetrieveSerializer(serializers.ModelSerializer):
         fields = "__all__"
 
     def get_total_right_count(self, obj):
-        return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count
+        return obj.single_answer_count + obj.multiple_answer_count + obj.fill_answer_count +  obj.judgment_answer_count +  obj.discuss_answer_count
 
     def get_answer_items(self, obj):
         data = []
@@ -147,6 +149,15 @@ class StaffExamLogRetrieveSerializer(serializers.ModelSerializer):
                 'answer_scores': obj.judgment_answer_scores,
             }
             data.append(item)
+        if obj.exampaper.discuss_total_count:
+            item = {
+                'name': '论述题',
+                'total_count': obj.exampaper.discuss_total_count,
+                'total_right': obj.discuss_answer_count,
+                'total_scores': obj.exampaper.discuss_total_scores,
+                'answer_scores': obj.discuss_answer_scores,
+            }
+            data.append(item)
         return data
 
 

+ 27 - 2
apps/examination/exampaper/models.py

@@ -31,29 +31,37 @@ class ExamPaper(models.Model):
     multiple_simple_count = models.IntegerField(verbose_name=u'简单多选题数量', default=0)
     fill_simple_count = models.IntegerField(verbose_name=u'简单填空题数量', default=0)
     judgment_simple_count = models.IntegerField(verbose_name=u'简单判断题数量', default=0)
+    discuss_simple_count = models.IntegerField(verbose_name=u'简单论述题数量', default=0)
+
     single_mid_count = models.IntegerField(verbose_name=u'中等单选题数量', default=0)
     multiple_mid_count = models.IntegerField(verbose_name=u'中等多选题数量', default=0)
     fill_mid_count = models.IntegerField(verbose_name=u'中等填空题数量', default=0)
     judgment_mid_count = models.IntegerField(verbose_name=u'中等判断题数量', default=0)
+    discuss_mid_count = models.IntegerField(verbose_name=u'中等论述题数量', default=0)
+
     single_hard_count = models.IntegerField(verbose_name=u'困难单选题数量', default=0)
     multiple_hard_count = models.IntegerField(verbose_name=u'困难多选题数量', default=0)
     fill_hard_count = models.IntegerField(verbose_name=u'困难填空题数量', default=0)
     judgment_hard_count = models.IntegerField(verbose_name=u'困难判断题数量', default=0)
+    discuss_hard_count = models.IntegerField(verbose_name=u'困难论述题数量', default=0)
 
     single_scores = models.IntegerField(verbose_name=u'单选题单题分数', default=0)
     multiple_scores = models.IntegerField(verbose_name=u'多选题单题分数', default=0)
     fill_scores = models.IntegerField(verbose_name=u'填空题单题分数', default=0)
     judgment_scores = models.IntegerField(verbose_name=u'判断题单题分数', default=0)
+    discuss_scores = models.IntegerField(verbose_name=u'论述题单题分数', default=0)
 
     single_total_count = models.IntegerField(verbose_name=u'单选题总数量', default=0, editable=False)
     multiple_total_count = models.IntegerField(verbose_name=u'多选题总数量', default=0, editable=False)
     fill_total_count = models.IntegerField(verbose_name=u'填空题总数量', default=0, editable=False)
     judgment_total_count = models.IntegerField(verbose_name=u'判断题总数量', default=0, editable=False)
+    discuss_total_count = models.IntegerField(verbose_name=u'论述题总数量', default=0, editable=False)
 
     single_total_scores = models.IntegerField(verbose_name=u'单选题总分数', default=0, editable=False)
     multiple_total_scores = models.IntegerField(verbose_name=u'多选题总分数', default=0, editable=False)
     fill_total_scores = models.IntegerField(verbose_name=u'填空题总分数', default=0, editable=False)
     judgment_total_scores = models.IntegerField(verbose_name=u'判断题总分数', default=0, editable=False)
+    discuss_total_scores = models.IntegerField(verbose_name=u'论述题总分数', default=0, editable=False)
 
     question_total_count  = models.IntegerField(verbose_name=u'试题总数量', default=0, editable=False)
     question_total_scores = models.IntegerField(verbose_name=u'试题总分数', default=0, editable=False)
@@ -94,6 +102,7 @@ class ExamPaper(models.Model):
         if self.single_hard_count:
             self._generate_detail(self.single_hard_count, ExamQuestion.SINGLE, ExamQuestion.HARD, begin_order)
             begin_order += self.single_hard_count
+
         if self.multiple_simple_count:
             self._generate_detail(self.multiple_simple_count, ExamQuestion.MULTIPLE, ExamQuestion.SIMPLE, begin_order)
             begin_order += self.multiple_simple_count
@@ -103,6 +112,7 @@ class ExamPaper(models.Model):
         if self.multiple_hard_count:
             self._generate_detail(self.multiple_hard_count, ExamQuestion.MULTIPLE, ExamQuestion.HARD, begin_order)
             begin_order += self.multiple_hard_count
+
         if self.fill_simple_count:
             self._generate_detail(self.fill_simple_count, ExamQuestion.FILL, ExamQuestion.SIMPLE, begin_order)
             begin_order += self.fill_simple_count
@@ -112,6 +122,7 @@ class ExamPaper(models.Model):
         if self.fill_hard_count:
             self._generate_detail(self.fill_hard_count, ExamQuestion.FILL, ExamQuestion.HARD, begin_order)
             begin_order += self.fill_hard_count
+
         if self.judgment_simple_count:
             self._generate_detail(self.judgment_simple_count, ExamQuestion.JUDGMENT, ExamQuestion.SIMPLE, begin_order)
             begin_order += self.judgment_simple_count
@@ -122,17 +133,31 @@ class ExamPaper(models.Model):
             self._generate_detail(self.judgment_hard_count, ExamQuestion.JUDGMENT, ExamQuestion.HARD, begin_order)
             begin_order += self.judgment_hard_count
 
+        if self.discuss_simple_count:
+            self._generate_detail(self.discuss_simple_count, ExamQuestion.DISCUSS, ExamQuestion.SIMPLE, begin_order)
+            begin_order += self.discuss_simple_count
+        if self.discuss_mid_count:
+            self._generate_detail(self.discuss_mid_count, ExamQuestion.DISCUSS, ExamQuestion.MID, begin_order)
+            begin_order += self.discuss_mid_count
+        if self.discuss_hard_count:
+            self._generate_detail(self.discuss_hard_count, ExamQuestion.DISCUSS, ExamQuestion.HARD, begin_order)
+            begin_order += self.discuss_hard_count
+
     def update_count(self):
         self.single_total_count = self.single_simple_count + self.single_mid_count + self.single_hard_count
         self.multiple_total_count = self.multiple_simple_count + self.multiple_mid_count + self.multiple_hard_count
         self.fill_total_count = self.fill_simple_count + self.fill_mid_count + self.fill_hard_count
         self.judgment_total_count = self.judgment_simple_count + self.judgment_mid_count + self.judgment_hard_count
+        self.discuss_total_count = self.discuss_simple_count + self.discuss_mid_count + self.discuss_hard_count
+
         self.single_total_scores = self.single_scores * self.single_total_count
         self.multiple_total_scores = self.multiple_scores * self.multiple_total_count
         self.fill_total_scores = self.fill_scores * self.fill_total_count
         self.judgment_total_scores = self.judgment_scores * self.judgment_total_count
-        self.question_total_count = self.single_total_count + self.multiple_total_count + self.fill_total_count + self.judgment_total_count
-        self.question_total_scores = self.single_total_scores + self.multiple_total_scores + self.fill_total_scores + self.judgment_total_scores
+        self.discuss_total_scores = self.discuss_scores * self.discuss_total_count
+
+        self.question_total_count = self.single_total_count + self.multiple_total_count + self.fill_total_count + self.judgment_total_count + self.discuss_total_count
+        self.question_total_scores = self.single_total_scores + self.multiple_total_scores + self.fill_total_scores + self.judgment_total_scores+ self.discuss_total_scores
         return self
 
     def _generate_detail(self, count, type, difficulty, begin_order):

+ 6 - 0
apps/examination/exampaper/serializers.py

@@ -45,6 +45,9 @@ class ExamPaperSerializer(serializers.ModelSerializer):
         old_count.append(instance.judgment_simple_count)
         old_count.append(instance.judgment_mid_count)
         old_count.append(instance.judgment_hard_count)
+        old_count.append(instance.discuss_simple_count)
+        old_count.append(instance.discuss_mid_count)
+        old_count.append(instance.discuss_hard_count)
 
         if instance.delete:
             raise CustomError(u'试卷[%s]已经被删除,禁止操作' % instance.name)
@@ -69,6 +72,9 @@ class ExamPaperSerializer(serializers.ModelSerializer):
         new_count.append(instance.judgment_simple_count)
         new_count.append(instance.judgment_mid_count)
         new_count.append(instance.judgment_hard_count)
+        new_count.append(instance.discuss_simple_count)
+        new_count.append(instance.discuss_mid_count)
+        new_count.append(instance.discuss_hard_count)
 
         change_detail = False
         for i in range(0, len(old_count)):

+ 3 - 0
apps/examination/examquestion/models.py

@@ -13,11 +13,13 @@ class ExamQuestion(models.Model):
     MULTIPLE = 2
     FILL = 3
     JUDGMENT = 4
+    DISCUSS = 5
     TYPE_CHOICES = (
         (SINGLE, u'单选题'),
         (MULTIPLE, u'多选题'),
         (FILL, u'填空题'),
         (JUDGMENT, u'判断题'),
+        (DISCUSS, u'论述题'),
     )
     TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
 
@@ -38,6 +40,7 @@ class ExamQuestion(models.Model):
     title = models.TextField(verbose_name=u"题目")
     judgment = models.BooleanField(verbose_name=u'判断题答案', default=False)
     analysis = models.TextField(verbose_name=u"解析", null=True, blank=True)
+    discuss_answer = models.TextField(verbose_name=u"论述题答案", null=True, blank=True)
     create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'录入人',related_name='exam_question_user', editable=False, on_delete=models.PROTECT)
     create_time = models.DateTimeField(verbose_name=u"录入时间", default=timezone.now, editable=False)
     delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)

+ 1 - 1
apps/examination/examquestion/serializers.py

@@ -147,7 +147,7 @@ class ExamQuestionSimpleSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = ExamQuestion
-        fields = ('title', 'judgment', 'items', 'type', 'analysis', )
+        fields = ('title', 'judgment', 'discuss_answer', 'items', 'type', 'analysis', )
 
 
 class ExamQuestionOptionSimpleSerializer(serializers.ModelSerializer):

+ 4 - 0
apps/practise/practiselog/models.py

@@ -37,15 +37,19 @@ class PractiseLog(models.Model):
 class PractiseAnswerLog(models.Model):
     RIGHT = 1
     WRONG = 2
+    WAIT_CHECK = 3
     STATUS_CHOICES = (
         (RIGHT, u'正确'),
         (WRONG, u'错误'),
+        (WAIT_CHECK, u'待评分'),
     )
     STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
 
     main = models.ForeignKey(PractiseLog, verbose_name=u"练习记录", on_delete=models.PROTECT)
     question = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
     status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'回答状态', null=True)
+    discuss_answer = models.TextField(verbose_name=u"论述题答案", null=True, blank=True)
+    discuss_scores = models.TextField(verbose_name=u"论述题得分", default=0)
 
     class Meta:
         db_table = "practise_answer_log"

+ 43 - 1
uis/admin/exam/exampaper_edit.html

@@ -181,6 +181,36 @@
                             </div>
                         </div>
 
+                        <div class="layui-form-item">
+                            <label class="layui-form-label"></label>
+                                <div class="layui-inline" style="width: 80%">
+                                <label class="layui-form-label">论述题:</label>
+                                <div class="layui-inline" style="width: 85%">
+                                    每题
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="discuss_scores" autocomplete="off" class="layui-input">
+                                      </div>
+                                    分
+                                    ,简单
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="discuss_simple_count" autocomplete="off" class="layui-input discuss">
+                                      </div>
+
+                                    ,中等
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="discuss_mid_count" autocomplete="off" class="layui-input discuss">
+                                      </div>
+                                    ,困难
+                                    <div class="layui-inline" style="width: 10%">
+                                        <input type="text" lay-verify="intGtz" name="discuss_hard_count" autocomplete="off" class="layui-input discuss">
+                                      </div>
+                                    <div class="layui-inline" style="width: 10%">
+                                        <label id="id_discuss_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                                      </div>
+                                </div>
+                            </div>
+                        </div>
+
                     </div>
 
                 </div>
@@ -278,6 +308,18 @@
             var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
             $('#id_fill_total_count').html(txt);
         });
+        $('.discuss').on("input", function (e) {
+            var total = 0;
+            $('.discuss').each(function (i) {
+                var target =  $('.discuss')[i];
+                var val = parseInt(target.value);
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_discuss_total_count').html(txt);
+        });
         $('.judgment').on("input", function (e) {
             var total = 0;
             $('.judgment').each(function (i) {
@@ -349,7 +391,7 @@
             {title: '添加人', field: 'create_user_text', width: '10%', },
             {title: '时间', field: 'create_time', width: '22%', },
         ]
-        
+
         var layTableId = 'layTable';
         var tableIns = table.render({
             elem: '#dataTable',

+ 5 - 4
uis/admin/exam/index.html

@@ -156,10 +156,11 @@
                 {title: '编号', type: 'numbers'}
                 , {field: 'name', title: '名称', width: 200}
                 , {field: 'subject_name', title: '科目', width: 120}
-                , {field: 'exam_time', title: '考试时间', width: 180}
-                , {field: 'type_text', title: '试卷类型', width: 120}
-                , {field: 'examinee_count', title: '考试人数', width: 120}
-                , {field: 'duration', title: '时长', width: 120}
+                , {field: 'exam_time', title: '考试时间', width: 170}
+                , {field: 'type_text', title: '试卷类型', width: 90}
+                , {field: 'exampaper_name', title: '试卷名称', width: 160}
+                , {field: 'examinee_count', title: '考试人数', width: 90}
+                , {field: 'duration', title: '时长', width: 90}
                 , {field: 'question_total_scores', title: '总分', width: 80}
                 , {field: 'passline', title: '及格线', width: 80}
                 , {field: 'create_user_text', title: '添加人', width: 100}

+ 23 - 1
uis/admin/examlog/details.html

@@ -50,6 +50,18 @@
             float: left;
             color: white;
         }
+        .wait_check{
+            background-color: orange;
+            height: 30px;
+            width: 30px;
+            margin: 5px 5px;
+            border: 1px orange;
+            border-radius: 100%;
+            text-align: center;
+            line-height: 30px;
+            float: left;
+            color: white;
+        }
     </style>
 </head>
 <body>
@@ -134,6 +146,13 @@
                                 <td class="cell" id="judgment_scores"></td>
                                 <td class="cell" id="judgment_answer_scores"></td>
                             </tr>
+                            <tr>
+                                <td>论述题</td>
+                                <td class="cell" id="discuss_count"></td>
+                                <td class="cell" id="discuss_answer_count"></td>
+                                <td class="cell" id="discuss_scores"></td>
+                                <td class="cell" id="discuss_answer_scores"></td>
+                            </tr>
 
 
                         </table>
@@ -142,7 +161,8 @@
                         作答详情
                         <span class="layui-badge-dot layui-bg-green"></span>正确
                         <span class="layui-badge-dot layui-bg-red"></span>错误
-                          <span class="layui-badge-dot layui-bg-gray"></span>未做
+                        <span class="layui-badge-dot layui-bg-gray"></span>未做
+                        <span class="layui-badge-dot layui-bg-orange"></span>待评分
 
                         <div style="margin-top: 10px" id="answer_detail">
 
@@ -188,6 +208,8 @@
                             txt = '<span class="wrong_anwer">' + num + '</span>'
                         }else if(res.data[i] === 3){
                             txt = '<span class="not_done">' + num + '</span>'
+                        }else if(res.data[i] === 4){
+                            txt = '<span class="wait_check">' + num + '</span>'
                         }
 
                         $('#answer_detail').append(txt)

+ 141 - 0
uis/admin/examlog/discuss_check.html

@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>商品折扣</title>
+    <meta name="renderer" content="webkit">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="viewport"
+          content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
+    <link rel="stylesheet" href="../../layuiadmin/layui/css/layui.css" media="all">
+    <link rel="stylesheet" href="../../layuiadmin/style/admin.css" media="all">
+</head>
+<body>
+
+<div class="layui-fluid">
+    <div class="layui-col-md12">
+        <div class="layui-card">
+            <form id='fm' class="layui-form" action="" lay-filter="component-form-element">
+                <button class="layui-btn" id="id_save" lay-submit lay-filter="component-form-element"
+                        style="display: none">保存
+                </button>
+            </form>
+        </div>
+        <div class="layui-card">
+            <div class="layui-card-body">
+                <div id="tableRes" class="table-overlay">
+                    <table id="dataTable" lay-filter="dataTable" class="layui-hide"></table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script src="../../layuiadmin/layui/layui.js"></script>
+
+<script>
+    layui.config({
+        base: '../../../layuiadmin/' //静态资源所在路径
+    }).extend({
+        index: 'lib/index' //主入口模块
+    }).use(['index', 'table', 'layer', 'form',], function () {
+        var $ = layui.$
+            , admin = layui.admin
+            , form = layui.form
+            , table = layui.table
+            , layer = layui.layer;
+
+
+        var id = layui.view.getParameterByName('id');
+        var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
+
+        var tbWidth = $("#tableRes").width();
+        var layTableId = "layTable";
+        var tableIns = table.render({
+            elem: '#dataTable',
+            id: layTableId,
+            data: [],
+            width: tbWidth,
+            page: false,
+            limit: 100,
+            loading: true,
+            even: true, //不开启隔行背景
+            cols: [[
+                {title: '序号', type: 'numbers'},
+                {field: 'question', title: '问题', width: '20%',},
+                {field: 'answer', title: '标准答案', width: '20%',},
+                {field: 'discuss_answer', title: '学员答案', width: '20%',},
+                {field: 'discuss_scores', title: '该题分数', width: '10%',},
+                {field: 'user_scores', title: '学员得分', edit: 'text', width: '10%',},
+            ]],
+        });
+
+        admin.req({
+            url: '/admin/exam/examlog/' + id + '/get_discuss/',
+            done: function (res) {
+                var rows = res.data;
+                var oldData = table.cache[layTableId];
+                for (var k in rows) {
+                    oldData.push(
+                        {
+                            id: rows[k].id,
+                            question: rows[k].question,
+                            answer: rows[k].answer,
+                            discuss_answer: rows[k].discuss_answer,
+                            discuss_scores: rows[k].discuss_scores,
+                            user_scores: rows[k].user_scores,
+                        }
+                    )
+                }
+                tableIns.reload({
+                    data: oldData
+                });
+            }
+        });
+
+        //保存
+        form.on('submit(component-form-element)', function (data) {
+            var rows = table.cache[layTableId];
+            var items = [];
+
+            for (var k in rows) {
+                if (isNaN(parseFloat(rows[k].user_scores))){
+                    layer.msg('第' + (parseInt(k) + 1) + '行,学员分数格式填写错误!', {icon: 2});
+                    return false;
+                }
+                if (rows[k].user_scores > rows[k].discuss_scores || rows[k].user_scores < 0){
+                    layer.msg('第' + (parseInt(k) + 1) + '行,学员分数填写错误!', {icon: 2});
+                    return false;
+                }
+                var item = {
+                    id: rows[k].id,
+                    user_scores: rows[k].user_scores,
+                };
+                items.push(item);
+            }
+
+            if (items.length === 0) {
+                layer.msg('请填写学员分数!', {icon: 2});
+                return false;
+            }
+            admin.req({
+                url: '/admin/exam/examlog/' + id + '/discuss_check/'
+                , data: {items:JSON.stringify(items)}
+                //, contentType: false
+                //, processData: false
+                , type: 'post'
+                , done: function (res) {
+                    parent.layui.onSubmitChild(res);
+                }
+            });
+
+            return false;
+        });
+        parent.layui.submitChild = function () {
+            $("#id_save").click();
+        };
+    });
+
+</script>
+</body>
+</html>

+ 23 - 1
uis/admin/examlog/index.html

@@ -100,6 +100,8 @@
                         <div class="layui-btn-group">
                             <a class="layui-btn layui-btn-xs" lay-event="examlog_detail"
                             >查看</a>
+                            <a class="layui-btn layui-btn-xs" lay-event="discuss_check"
+                            >论述评分</a>
                         </div>
                     </script>
                 </div>
@@ -154,7 +156,7 @@
                 , {field: 'rank', title: '排名', width: 80}
                 , {field: 'submit_time', title: '交卷时间', width: 165}
                 , {field: 'use_time', title: '用时', width: 150}
-                , {title: '操作', width: 80, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
+                , {title: '操作', width: 140, align: 'center', fixed: 'right', toolbar: '#exam_question-operate-bar'}
             ]]
             , page: true
             , height: 'full-108'
@@ -177,6 +179,26 @@
                     content: 'details.html?id=' + data.id
                 });
             }
+            else if (obj.event === 'discuss_check') {
+                layer.open({
+                    type: 2,
+                    title: '论述题评分',
+                    shadeClose: false,
+                    area: ['70%', '80%'],
+                    btn: ['保存', '取消'],
+                    yes: function (index, dom) {
+                        layui.onSubmitChild = function (res) {
+                            layer.close(index);
+                            table.reload('exam_datagrid', {});
+                        };
+                        layui.submitChild();
+                    },
+                    btn2: function (index, layero) {
+                        layer.close(index);//关闭当前按钮
+                    },
+                    content: 'discuss_check.html?id=' + data.id
+                });
+            }
         });
 
         form.on('submit(query-form-element)', function (data) {

+ 7 - 0
uis/admin/exampaper/details.html

@@ -68,6 +68,10 @@
                                 <td class="title">填空题:</td>
                                 <td colspan="3" id="fill" class="cell"></td>
                             </tr>
+                            <tr id="id_analysis">
+                                <td class="title">论述题:</td>
+                                <td colspan="3" id="discuss" class="cell"></td>
+                            </tr>
 
                             <tr id="id_analysis">
                                 <td class="title">备注:</td>
@@ -101,11 +105,14 @@
             '中等' + editdata.judgment_mid_count + '题,困难' + editdata.judgment_hard_count + '题';
         var fill = '共' + editdata.fill_total_count + '题,每题' + editdata.fill_scores + '分,简单' + editdata.fill_simple_count + '题,' +
             '中等' + editdata.fill_mid_count + '题,困难' + editdata.fill_hard_count + '题';
+        var discuss = '共' + editdata.discuss_total_count + '题,每题' + editdata.discuss_scores + '分,简单' + editdata.discuss_simple_count + '题,' +
+            '中等' + editdata.discuss_mid_count + '题,困难' + editdata.discuss_hard_count + '题';
 
         $('#single').html(single);
         $('#multiple').html(multiple);
         $('#judgment').html(judgment);
         $('#fill').html(fill);
+        $('#discuss').html(discuss);
 
     });
 </script>

+ 43 - 0
uis/admin/exampaper/edit.html

@@ -165,6 +165,35 @@
                     </div>
                 </div>
 
+                <div class="layui-form-item">
+                    <div class="layui-inline" style="width: 100%">
+                        <label class="layui-form-label">论述题:</label>
+                        <div class="layui-inline" style="width: 85%">
+                            每题
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="discuss_scores" autocomplete="off" class="layui-input">
+                              </div>
+                            分
+                            ,简单
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="discuss_simple_count" autocomplete="off" class="layui-input discuss">
+                              </div>
+
+                            ,中等
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="discuss_mid_count" autocomplete="off" class="layui-input discuss">
+                              </div>
+                            ,困难
+                            <div class="layui-inline" style="width: 10%">
+                                <input type="text" lay-verify="intGtz" name="discuss_hard_count" autocomplete="off" class="layui-input discuss">
+                              </div>
+                            <div class="layui-inline" style="width: 10%">
+                                <label id="id_discuss_total_count" class="layui-form-label">共&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;题</label>
+                              </div>
+                        </div>
+                    </div>
+                </div>
+
 
                 <div class="layui-form-item">
                     <label class="layui-form-label">备注:</label>
@@ -213,6 +242,7 @@
                 var editdata = JSON.parse(JSON.stringify(parent.layui.table.editdata)); // 框架有Bug所以这么转换
                 form.val("component-form-element", editdata);
                 $('#id_fill_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.fill_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
+                $('#id_discuss_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.discuss_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
                 $('#id_judgment_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.judgment_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
                 $('#id_multiple_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.multiple_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
                 $('#id_single_total_count').html('共&nbsp;&nbsp;&nbsp;&nbsp;' + editdata.single_total_count + '&nbsp;&nbsp;&nbsp;&nbsp;题');
@@ -259,6 +289,19 @@
             var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
             $('#id_fill_total_count').html(txt);
         });
+        $('.discuss').on("input", function (e) {
+            var total = 0;
+            $('.discuss').each(function (i) {
+                var target =  $('.discuss')[i];
+                var val = parseInt(target.value);
+                // 是不是可以在这控制输入框的值 不能是负数之类的
+                if(!isNaN(val) && val){
+                    total += val
+                }
+            });
+            var txt = '共&nbsp;&nbsp;&nbsp;&nbsp;' + total + '&nbsp;&nbsp;&nbsp;&nbsp;题';
+            $('#id_discuss_total_count').html(txt);
+        });
         $('.judgment').on("input", function (e) {
             var total = 0;
             $('.judgment').each(function (i) {

+ 1 - 0
uis/admin/examquestion_feedback/edit.html

@@ -44,6 +44,7 @@
                                 <option value="2">多选题</option>
                                 <option value="3">填空题</option>
                                 <option value="4">判断题</option>
+                                <option value="5">论述题</option>
                             </select>
                         </div>
                     </div>