Ver código fonte

练习题记录、提交

wushaodong 3 anos atrás
pai
commit
7a402d3ea9

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

@@ -22,10 +22,22 @@ class ExamQuestionViewSet(CustomModelViewSet):
         f = ExamQuestionFilter(self.request.GET, queryset=queryset)
         return f.qs
 
+    def perform_create(self, serializer):
+        super(ExamQuestionViewSet, 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)
+
+    def perform_update(self, serializer):
+        super(ExamQuestionViewSet, self).perform_update(serializer)
+        instance = serializer.instance
+        validated_data = serializer.validated_data
+        SysLog.objects.addnew(self.request.user, SysLog.UPDATE, u'修改试题库试题,id=%d' % (instance.id), validated_data)
+
     def destroy(self, request, *args, **kwargs):
         with transaction.atomic():
             instance = self.get_object()
             instance.delete = True
             instance.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)
         return response_ok()

+ 13 - 0
apps/api/staff/practise/urls.py

@@ -0,0 +1,13 @@
+# coding=utf-8
+from django.conf.urls import url, include
+from rest_framework.routers import SimpleRouter
+
+from .views import *
+
+urlpatterns = [
+    url(r'^dict/$', DictView.as_view()),
+]
+
+router = SimpleRouter()
+router.register(r'', PractiseLogViewSet)
+urlpatterns += router.urls

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

@@ -0,0 +1,291 @@
+# coding=utf-8
+import json
+import traceback
+from django.db.models import Sum, F
+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
+from utils.custom_modelviewset import CustomModelViewSet
+from utils import response_ok, response_error
+from utils.permission import IsAdministrator, IsStaff
+from apps.examination.examquestion.serializers import *
+from apps.examination.examquestion.filters import *
+from apps.system.models import SysLog
+from apps.foundation.serializers import SubjectSerializer, ChapterSerializer, SubjectSimpleSerializer, \
+    ChapterSimpleSerializer, Subject
+from apps.practise.practiselog.models import *
+from apps.practise.practiselog.serializers import PractiseLogSerializer
+
+
+class PractiseLogViewSet(CustomModelViewSet):
+    permission_classes = [IsStaff, ]
+    queryset = PractiseLog.objects.filter()
+    serializer_class = PractiseLogSerializer
+
+    def filter_queryset(self, queryset):
+        queryset = queryset.filter(create_user=self.request.user)
+        return queryset
+
+    def create(self, request, *args, **kwargs):
+        subject = request.data.get('subject')
+        chapter = request.data.get('chapter')
+        type = PractiseLog.CHAPTER
+        try:
+            with transaction.atomic():
+                if not chapter:
+                    # 进入练习题,没有选择章节,使用该科目下的第一个章节
+                    # chapter = Chapter.objects.filter(subject=subject, delete=False).order_by('id').first()
+                    type = PractiseLog.SUBJECT
+
+                data = {
+                    'subject': int(subject),
+                    'chapter': chapter and int(chapter) or None,
+                    'type': type,
+                    'create_user': request.user.id,
+                    'submit_time': timezone.now()
+                }
+                serializer = PractiseLogSerializer(data=data)
+                if serializer.is_valid(raise_exception=True):
+                    instance = serializer.save()
+                    result = {
+                        'practise': 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')  # 当前提交的练习题, 第一题或继续答题时,该参数为空
+        answers = json.loads(request.data.get('answers'))  # 答案, 第一题或继续答题时,该参数为空
+        next_number = int(request.data.get('next_number'))  # 下一题序号,最后一题提交时,此参数为0
+        next_type = int(request.data.get('next_type'))  # 下一题题型,默认1为单选题
+        button_type = request.data.get('button_type')  #  上一题、下一题按钮类型,next下一题,previous上一题
+        previous_practise = request.data.get('previous_practise')  #  上一题id
+        try:
+            with transaction.atomic():
+                instance = self.get_object()
+                # 点击下一题,保存、判断当前习题答案
+                if now_practise and button_type == 'next':
+                    if len(answers) == 0:
+                        raise CustomError('请选择该题的答案!')
+                    now_question = ExamQuestion.objects.filter(id=now_practise).first()
+                    if not now_question:
+                        raise CustomError('提交的习题有误,请刷新重试!')
+                    answer_log, create = PractiseAnswerLog.objects.get_or_create(main=instance, question=now_question,
+                                                                  status=PractiseAnswerLog.WRONG)
+                    if now_question.type == ExamQuestion.SINGLE:
+                        # 单选
+                        if len(answers) != 1:
+                            raise CustomError(u'请提交一个答案!')
+                        answer = answers[0]
+                        PractiseAnswerOptionLog.objects.create(main=answer_log, option_id=answer)
+                        right = ExamQuestionOption.objects.filter(main=now_question, id=answer, right=True, )
+                        if right:
+                            instance.right_count += 1
+                            answer_log.status = PractiseAnswerLog.RIGHT
+                        else:
+                            instance.wrong_count += 1
+                    elif now_question.type == ExamQuestion.MULTIPLE:
+                        # 多选
+                        if len(answers) <= 1:
+                            raise CustomError(u'请提交两个以上答案!')
+                        answers.sort()
+                        for a in answers:
+                            PractiseAnswerOptionLog.objects.create(main=answer_log, option_id=a)
+                        right = ExamQuestionOption.objects.filter(main=now_question, right=True,
+                                                                  delete=False).values_list('id', flat=True)
+                        right.sort()
+                        if answers == right:
+                            answer_log.status = PractiseAnswerLog.RIGHT
+                            instance.right_count += 1
+                        else:
+                            instance.wrong_count += 1
+                    elif now_question.type == ExamQuestion.FILL:
+                        # 填空
+                        answers_len = len(answers)
+                        right = 1
+                        for a in range(1, answers_len):
+                            PractiseAnswerFillLog.objects.create(main=answer_log, content=answers[a], order=a)
+                            right_answer = ExamQuestionFill.objects.filter(main=now_question, content=answers[a], order=a)
+                            if not right_answer:
+                                # 有一个填空错误,整题错误
+                                right = 0
+                        if right:
+                            answer_log.status = PractiseAnswerLog.RIGHT
+                            instance.right_count += 1
+                        else:
+                            instance.wrong_count += 1
+                    else:
+                        # 判断
+                        if now_question.judgment == (answers[0] == 1):
+                            answer_log.status = PractiseAnswerLog.RIGHT
+                            instance.right_count += 1
+                        else:
+                            instance.wrong_count += 1
+                    instance.total_count += 1
+                    # 第一题
+                    if next_number == 1:
+                        instance.begin_answer = answer_log
+                    instance.end_answer = answer_log
+                    instance.submit_time = timezone.now()
+                    instance.save()
+                    answer_log.order = next_number
+                    answer_log.save()
+
+                question_data = {}
+                if previous_practise:
+                    # 上一题
+                    questions = ExamQuestion.objects.filter(id=previous_practise)
+                elif instance.type == PractiseLog.SUBJECT:
+                    questions = ExamQuestion.objects.filter(chapter__subject=instance.subject, delete=False).order_by(
+                        'type')
+                else:
+                    questions = ExamQuestion.objects.filter(chapter=instance.chapter, delete=False).order_by('type')
+                # 返回下一题
+                if next_number:
+                    # 循环查询4个题型的试题,只到查询到试题
+                    # question = None
+                    # while not question and next_type <= ExamQuestion.JUDGMENT:
+                    #     question = questions.filter(type=int(next_type))[next_number - 1:next_number].first()
+                    #     if question:
+                    #         break
+                    #     next_type += 1
+                    #     next_number = 1
+                    question = questions.filter()[next_number - 1:next_number].first()
+
+                    if question:
+                        # 查询到下一题,返回题目和答案
+                        # 根据下一题,查询下下一题类型
+                        # next_question = None
+                        # while not next_question and next_type <= ExamQuestion.JUDGMENT:
+                        #     next_question = questions.filter(type=int(next_type))[next_number:next_number + 1].first()
+                        #     if next_question:
+                        #         if next_number == 0:
+                        #             next_number = 1
+                        #         break
+                        #     next_type += 1
+                        #     next_number = 0
+                        # next_question = questions.filter()[next_number:next_number + 1]
+                        # if next_question:
+                        #     next_number += 1
+                        # else:
+                        #     next_number = 0
+                        question_data = {
+                            'id': question.id,
+                            'title': question.title,
+                            'next_type': question.type,  # 下一题类别 id,不在使用
+                            'next_number': next_number + 1,  # 下下一题序号,如果为0,则没有下一题了
+                            'previous_practise': now_practise,  # 上一题 id
+                            'option': [],
+                        }
+                        answer_log = PractiseAnswerLog.objects.filter(main=instance, question=question).first()
+                        if question.type == ExamQuestion.JUDGMENT:
+                            item1 = {
+                                'id': 1,
+                                'content': '正确',
+                                'answer': True if answer_log.status == PractiseAnswerLog.RIGHT else False
+                            }
+                            item0 = {
+                                'id': 0,
+                                'content': '错误',
+                                'answer': True if answer_log.status == PractiseAnswerLog.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 = PractiseAnswerOptionLog.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 = PractiseAnswerFillLog.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)
+
+                # 右侧习题类别列表
+                # 单选题
+                questions = questions.values_list('id', flat=True)
+                single_questions_list = []
+                for single in questions.filter(type=ExamQuestion.SINGLE):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=single)
+                    single_questions_list.append(
+                        {
+                            'question_id':single,
+                            'complete':answer_log and True or False,
+                        }
+                    )
+                # 多选题
+                multiple_questions_list = []
+                for multiple in questions.filter(type=ExamQuestion.MULTIPLE):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=multiple)
+                    multiple_questions_list.append(
+                        {
+                            'question_id':multiple,
+                            'complete':answer_log and True or False,
+                        }
+                    )
+                # 填空题
+                fill_questions_list = []
+                for fill in questions.filter(type=ExamQuestion.FILL):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=fill)
+                    fill_questions_list.append(
+                        {
+                            'question_id':fill,
+                            'complete':answer_log and True or False,
+                        }
+                    )
+                # 判断题
+                judgment_questions_list = []
+                for judgment in questions.filter(type=ExamQuestion.MULTIPLE):
+                    answer_log = PractiseAnswerLog.objects.filter(main=instance, question_id=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))
+
+class DictView(APIView):
+    permission_classes = [IsStaff, ]
+
+    def get(self, request):
+        data = []
+        subjects = Subject.objects.filter(delete=False).values('id', 'name').order_by('id')
+        for subject in subjects:
+            chapters = ChapterSimpleSerializer(
+                Chapter.objects.filter(delete=False, subject__delete=False, subject_id=subject['id']).order_by('id'),
+                many=True).data
+            item = {
+                'id': subject['id'],
+                'name': subject['name'],
+                'chapters': chapters,
+            }
+            data.append(item)
+        return response_ok(data)

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

@@ -9,4 +9,6 @@ urlpatterns = [
     url(r'^token/obtain/$', StaffUserLoginView.as_view()),
     url(r'^token_refresh/$', StaffUserRefreshTokenView.as_view()),
     url(r'^token_verify/$', StaffUserVerifyTokenView.as_view()),
-]
+
+    url(r'^practise/', include('apps.api.staff.practise.urls')),
+]

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

@@ -38,4 +38,4 @@ class StaffUserRefreshTokenView(RefreshJSONWebToken):
             if ser.is_valid(raise_exception=True):
                 return response_ok({'token': ser.validated_data['token']})
         except ValidationError as e:
-            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')
+            return response_error(u'登录状态失效,请重新登录[' + e.detail['error'][0] + ']')

+ 6 - 5
apps/practise/practiselog/models.py

@@ -15,10 +15,10 @@ class PractiseLog(models.Model):
     )
     TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
 
-    subject = models.ForeignKey(Chapter, verbose_name=u"科目", on_delete=models.PROTECT)
-    chapter = models.ForeignKey(Subject, verbose_name=u"章节", on_delete=models.PROTECT)
+    subject = models.ForeignKey(Subject, verbose_name=u"科目", on_delete=models.PROTECT)
+    chapter = models.ForeignKey(Chapter, verbose_name=u"章节", on_delete=models.PROTECT, null=True,)
     type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
-    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'练习人', editable=False, on_delete=models.PROTECT)
+    create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'练习人', on_delete=models.PROTECT)
     create_time = models.DateTimeField(verbose_name=u"开始时间", default=timezone.now, editable=False)
     submit_time = models.DateTimeField(verbose_name=u"结束时间", null=True, blank=True)
 
@@ -46,6 +46,7 @@ class PractiseAnswerLog(models.Model):
     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'回答状态')
+    order = models.IntegerField(verbose_name=u'当前答题序号', default=1)
 
     class Meta:
         db_table = "practise_answer_log"
@@ -54,7 +55,7 @@ class PractiseAnswerLog(models.Model):
         default_permissions = ()
 
 class PractiseAnswerOptionLog(models.Model):
-    main = models.ForeignKey(PractiseLog, verbose_name=u"练习答题记录", on_delete=models.PROTECT)
+    main = models.ForeignKey(PractiseAnswerLog, verbose_name=u"练习答题记录", on_delete=models.PROTECT)
     option = models.ForeignKey(ExamQuestionOption, verbose_name=u"选项", on_delete=models.PROTECT)
 
     class Meta:
@@ -64,7 +65,7 @@ class PractiseAnswerOptionLog(models.Model):
         default_permissions = ()
 
 class PractiseAnswerFillLog(models.Model):
-    main = models.ForeignKey(PractiseLog, verbose_name=u"练习答题记录", on_delete=models.PROTECT)
+    main = models.ForeignKey(PractiseAnswerLog, verbose_name=u"练习答题记录", on_delete=models.PROTECT)
     content = models.TextField(verbose_name=u"内容")
     order = models.IntegerField(verbose_name=u'排序')
 

+ 9 - 0
apps/practise/practiselog/serializers.py

@@ -0,0 +1,9 @@
+# coding=utf-8
+from rest_framework import serializers
+from .models import *
+
+class PractiseLogSerializer(serializers.ModelSerializer):
+
+    class Meta:
+        model = PractiseLog
+        fields = '__all__'