views.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. # coding=utf-8
  2. import json
  3. import traceback
  4. from django.utils import timezone
  5. from rest_framework.decorators import action
  6. from django.db import transaction
  7. from django.db.models import F
  8. from utils.custom_modelviewset import CustomModelViewSet
  9. from utils import response_ok, response_error
  10. from utils.permission import IsStaff
  11. from apps.examination.exam.serializers import *
  12. from apps.examination.exam.filters import *
  13. from apps.system.models import SysLog
  14. from apps.examination.exam.models import ExamAnswerLog, ExamAnswerOptionLog, ExamAnswerFillLog
  15. from apps.examination.exampaper.models import ExamPaperDetail, ExamQuestion
  16. from apps.examination.examquestion.models import ExamQuestionOption, ExamQuestionFill, ExamQuestionFeedback
  17. from apps.examination.examquestion.serializers import ExamQuestionFeedbackSerializer
  18. from apps.practise.errorbook.models import ErrorBook
  19. from utils.format import strftime
  20. class ExamLogViewSet(CustomModelViewSet):
  21. permission_classes = [IsStaff, ]
  22. queryset = ExamLog.objects.filter(delete=False, exam__delete=False, type=ExamLog.FORMAL,)
  23. serializer_class = StaffExamLogSerializer
  24. def filter_queryset(self, queryset):
  25. queryset = queryset.filter(user=self.request.user)
  26. return queryset
  27. def retrieve(self, request, *args, **kwargs):
  28. instance = self.get_object()
  29. serializer = StaffExamLogRetrieveSerializer(instance)
  30. answer_log = []
  31. answer_logs = ExamAnswerLog.objects.filter(main=instance).order_by('detail__order')
  32. for al in answer_logs:
  33. item = {
  34. 'id':al.id,
  35. 'status':al.status,
  36. }
  37. answer_log.append(item)
  38. ranks = []
  39. rank_data = ExamLog.objects.filter(exam=instance.exam, rank__isnull=False, delete=False).order_by('rank').values('user__name','scores','rank')
  40. for rank in rank_data:
  41. item = {
  42. 'name':rank['user__name'],
  43. 'scores':rank['scores'],
  44. 'rank':rank['rank'],
  45. }
  46. ranks.append(item)
  47. result = {
  48. 'question': serializer.data,
  49. 'answer_log': answer_log,
  50. 'ranks': ranks,
  51. }
  52. return response_ok(result)
  53. @action(methods=['get'], detail=False)
  54. def get_home_exam(self, request):
  55. # 首页,考试提示
  56. start_time = timezone.now() + datetime.timedelta(minutes=30)
  57. queryset = ExamLog.objects.filter(delete=False,
  58. type=ExamLog.FORMAL,
  59. exam__exam_time__lte=start_time,
  60. user=request.user,
  61. exam__exam_end_time__gte=timezone.now()
  62. )
  63. data = []
  64. for exam_log in queryset:
  65. if exam_log.exam.exam_time < timezone.now() < exam_log.exam.exam_end_time:
  66. name = '您有一个考试,正在进行中,点击这里立即参加!'
  67. id = exam_log.id
  68. else:
  69. time = (exam_log.exam.exam_time - timezone.now()).seconds
  70. name = '您有一个考试,将在{}分钟后开始,请做好准备!'.format(int(time/60) + 1)
  71. id = ''
  72. item = {
  73. 'id':id,
  74. 'name':name,
  75. 'exam_end_time':exam_log.exam.exam_end_time,
  76. }
  77. data.append(item)
  78. return response_ok(data)
  79. @action(methods=['get'], detail=False)
  80. def get_exam(self, request):
  81. # 正式考试,获取考试列表,提前半小时
  82. start_time = timezone.now() + datetime.timedelta(minutes=30)
  83. queryset = ExamLog.objects.filter(delete=False,
  84. type=ExamLog.FORMAL,
  85. exam__exam_time__lte=start_time,
  86. user=request.user,
  87. exam__exam_end_time__gte=timezone.now()
  88. )
  89. data = []
  90. for exam_log in queryset:
  91. item = {
  92. 'id':exam_log.id,
  93. 'name':exam_log.exam.name,
  94. 'subject':exam_log.exam.subject.name,
  95. 'exam_time':strftime(exam_log.exam.exam_time),
  96. 'exam_end_time':strftime(exam_log.exam.exam_end_time),
  97. 'duration':exam_log.exam.duration,
  98. 'submit_time':exam_log.submit_time and True or False,
  99. }
  100. data.append(item)
  101. return response_ok(data)
  102. @action(methods=['post'], detail=True)
  103. def get_next_practise(self, request, pk):
  104. now_practise = request.data.get('now_practise') # 当前提交的模拟考试明细id。第一题或继续答题时,该参数为空
  105. answers = json.loads(request.data.get('answers')) # 答案, 第一题或继续答题时,该参数为空
  106. next_practise = request.data.get('next_practise') # 下一题id,首次加载第一题,传空
  107. submit = request.data.get('submit') # 交卷1,下一题为空
  108. try:
  109. with transaction.atomic():
  110. instance = self.get_object()
  111. if instance.submit_time:
  112. raise CustomError('您已交卷,禁止重复答题!')
  113. if timezone.now() < instance.exam.exam_time:
  114. raise CustomError('还未到考试时间,请稍等!')
  115. if not submit and timezone.now() > instance.exam.exam_end_time:
  116. raise CustomError('考试已结束,禁止答题!')
  117. # 点击下一题,保存
  118. if now_practise:
  119. detail = ExamPaperDetail.objects.filter(id=now_practise).first()
  120. if not detail:
  121. raise CustomError('提交的考试习题有误,请刷新重试!')
  122. now_question = detail.question
  123. has_answers = len(answers) > 0
  124. # 填空题,判断至少填了一个空
  125. if now_question.type == ExamQuestion.FILL:
  126. has_answers = False
  127. for a in range(0, len(answers)):
  128. if answers[a]:
  129. has_answers = True
  130. break
  131. if has_answers > 0:
  132. answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance,
  133. detail=detail, )
  134. if now_question.type <= ExamQuestion.MULTIPLE:
  135. # 单选、多选
  136. answers.sort()
  137. ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
  138. for a in answers:
  139. ExamAnswerOptionLog.objects.create(main=answer_log, option_id=a)
  140. elif now_question.type == ExamQuestion.FILL:
  141. # 填空
  142. answers_len = len(answers)
  143. ExamAnswerFillLog.objects.filter(main=answer_log).delete()
  144. for a in range(0, answers_len):
  145. ExamAnswerFillLog.objects.create(main=answer_log, content=answers[a] or '', order=a + 1)
  146. elif now_question.type == ExamQuestion.DISCUSS:
  147. # 论述题
  148. answer_log.status = ExamAnswerLog.WAIT_CHECK
  149. answer_log.discuss_answer = answers[0]
  150. answer_log.save()
  151. else:
  152. # 判断
  153. if answers[0] == 1:
  154. answer_log.status = ExamAnswerLog.RIGHT
  155. else:
  156. answer_log.status = ExamAnswerLog.WRONG
  157. answer_log.save()
  158. else:
  159. try:
  160. answer_log = ExamAnswerLog.objects.get(main=instance, detail=detail)
  161. ExamAnswerOptionLog.objects.filter(main=answer_log).delete()
  162. ExamAnswerFillLog.objects.filter(main=answer_log).delete()
  163. answer_log.status = ExamAnswerLog.NOTDONE
  164. answer_log.save()
  165. except ExamAnswerLog.DoesNotExist:
  166. # traceback.print_exc()
  167. pass
  168. question_data = {}
  169. # 返回下一题
  170. if next_practise:
  171. detail = ExamPaperDetail.objects.filter(id=next_practise, main=instance.exampaper,
  172. delete=False).first()
  173. else:
  174. detail = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).first()
  175. if detail:
  176. question = detail.question
  177. question_data = {
  178. 'id': detail.id,
  179. 'question': question.id,
  180. 'title': question.title,
  181. 'exam_title': instance.exampaper.name,
  182. 'next_type': question.type, # 下一题习题类别
  183. 'next_number': detail.order + 1, # 下下一题序号,
  184. 'option': [],
  185. }
  186. answer_log = ExamAnswerLog.objects.filter(main=instance, detail=detail).first()
  187. if question.type == ExamQuestion.JUDGMENT:
  188. item1 = {
  189. 'id': 1,
  190. 'content': '正确',
  191. 'answer': True if answer_log and answer_log.status == ExamAnswerLog.RIGHT else False
  192. }
  193. item0 = {
  194. 'id': 0,
  195. 'content': '错误',
  196. 'answer': True if answer_log and answer_log.status == ExamAnswerLog.WRONG else False
  197. }
  198. question_data['option'].append(item1)
  199. question_data['option'].append(item0)
  200. elif question.type <= ExamQuestion.MULTIPLE:
  201. rows = ExamQuestionOption.objects.filter(main=question, delete=False)
  202. for row in rows:
  203. option_log = ExamAnswerOptionLog.objects.filter(main=answer_log, option=row).first()
  204. item = {
  205. 'id': row.id,
  206. 'content': row.content,
  207. 'answer': option_log and True or False
  208. }
  209. question_data['option'].append(item)
  210. elif question.type == ExamQuestion.FILL:
  211. rows = ExamQuestionFill.objects.filter(main=question, delete=False)
  212. for row in rows:
  213. option_log = ExamAnswerFillLog.objects.filter(main=answer_log, order=row.order).first()
  214. item = {
  215. 'id': row.order, # 填空题序号
  216. 'content': option_log and option_log.content or '',
  217. }
  218. question_data['option'].append(item)
  219. elif question.type == ExamQuestion.DISCUSS: # 论述题
  220. item = {
  221. 'id': question.id, # 论述题序号
  222. 'content': answer_log and answer_log.discuss_answer or '',
  223. }
  224. question_data['option'].append(item)
  225. # 右侧习题类别列表
  226. # 单选、多选、填空。选择答案后,可能会把答案清空,得加上NOTDONE过滤
  227. questions = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False).values_list('id',
  228. flat=True)
  229. single_questions_list = []
  230. for single in questions.filter(question__type=ExamQuestion.SINGLE):
  231. answers = ExamAnswerOptionLog.objects.filter(main__main=instance, main__detail=single).first()
  232. single_questions_list.append(
  233. {
  234. 'question_id': single,
  235. 'complete': answers and True or False,
  236. }
  237. )
  238. # 多选题
  239. multiple_questions_list = []
  240. for multiple in questions.filter(question__type=ExamQuestion.MULTIPLE):
  241. answers = ExamAnswerOptionLog.objects.filter(main__main=instance, main__detail=multiple).first()
  242. multiple_questions_list.append(
  243. {
  244. 'question_id': multiple,
  245. 'complete': answers and True or False,
  246. }
  247. )
  248. # 填空题
  249. fill_questions_list = []
  250. for fill in questions.filter(question__type=ExamQuestion.FILL):
  251. answers = ExamAnswerFillLog.objects.filter(main__main=instance, main__detail=fill).first()
  252. fill_questions_list.append(
  253. {
  254. 'question_id': fill,
  255. 'complete': answers and True or False,
  256. }
  257. )
  258. # 判断题
  259. judgment_questions_list = []
  260. for judgment in questions.filter(question__type=ExamQuestion.JUDGMENT):
  261. answer_log = ExamAnswerLog.objects.filter(main=instance, detail=judgment).exclude(status=ExamAnswerLog.NOTDONE)
  262. judgment_questions_list.append(
  263. {
  264. 'question_id': judgment,
  265. 'complete': answer_log and True or False,
  266. }
  267. )
  268. # 论述题
  269. discuss_questions_list = []
  270. for discuss in questions.filter(question__type=ExamQuestion.DISCUSS):
  271. answer_log = ExamAnswerLog.objects.filter(main=instance, detail=discuss).exclude(status=ExamAnswerLog.NOTDONE)
  272. discuss_questions_list.append(
  273. {
  274. 'question_id': discuss,
  275. 'complete': answer_log and True or False,
  276. }
  277. )
  278. result = {
  279. 'question_data': question_data, # 下一题练习题
  280. 'single_questions_list': single_questions_list, # 单选
  281. 'multiple_questions_list': multiple_questions_list, # 多选
  282. 'fill_questions_list': fill_questions_list, # 填空
  283. 'judgment_questions_list': judgment_questions_list, # 判断
  284. 'discuss_questions_list': discuss_questions_list, # 论述
  285. }
  286. return response_ok(result)
  287. except CustomError as e:
  288. return response_error(e.get_error_msg())
  289. except Exception as e:
  290. traceback.print_exc()
  291. return response_error(str(e))
  292. @action(methods=['post'], detail=True)
  293. def submit_practise(self, request, pk):
  294. # 习题交卷,把上个接口的判断答案,放到此处
  295. try:
  296. instance = self.get_object()
  297. end_time = instance.exam.exam_end_time + datetime.timedelta(minutes=1)
  298. if timezone.now() > end_time:
  299. raise CustomError('考试已结束!')
  300. if instance.submit_time:
  301. raise CustomError('您已交卷,禁止重复交卷!')
  302. with transaction.atomic():
  303. paper_details = ExamPaperDetail.objects.filter(main=instance.exampaper, delete=False)
  304. for detail in paper_details:
  305. # 创建考试未答题记录,如果没有找到答案记录,则该题保留未答状态
  306. answer_log, create = ExamAnswerLog.objects.get_or_create(main=instance, detail=detail)
  307. # answer_logs = ExamAnswerLog.objects.filter(main=instance)
  308. # for answer_log in answer_logs:
  309. question = detail.question
  310. if question.type == ExamQuestion.SINGLE:
  311. # 单选
  312. answers = ExamAnswerOptionLog.objects.filter(main=answer_log).first()
  313. if answers:
  314. if answers.option.right:
  315. answer_log.status = ExamAnswerLog.RIGHT
  316. instance.single_answer_count += 1
  317. else:
  318. answer_log.status = ExamAnswerLog.WRONG
  319. ErrorBook.add_error(question, request.user, answer_log)
  320. elif question.type == ExamQuestion.MULTIPLE:
  321. # 多选
  322. answers = ExamAnswerOptionLog.objects.filter(main=answer_log).order_by('option_id').values_list(
  323. 'option_id', flat=True)
  324. if answers:
  325. right = ExamQuestionOption.objects.filter(main=question, right=True,
  326. delete=False).values_list('id', flat=True)
  327. if list(answers) == list(right):
  328. answer_log.status = ExamAnswerLog.RIGHT
  329. instance.multiple_answer_count += 1
  330. else:
  331. answer_log.status = ExamAnswerLog.WRONG
  332. ErrorBook.add_error(question, request.user, answer_log)
  333. elif question.type == ExamQuestion.FILL:
  334. # 填空
  335. fill_logs = ExamAnswerFillLog.objects.filter(main=answer_log)
  336. right = True
  337. if fill_logs:
  338. for fill_log in fill_logs:
  339. right_answer = ExamQuestionFill.objects.filter(main=question, content=fill_log.content,
  340. order=fill_log.order, delete=False)
  341. if not right_answer:
  342. right = False
  343. break
  344. if right:
  345. answer_log.status = ExamAnswerLog.RIGHT
  346. instance.fill_answer_count += 1
  347. else:
  348. answer_log.status = ExamAnswerLog.WRONG
  349. ErrorBook.add_error(question, request.user, answer_log)
  350. elif question.type == ExamQuestion.JUDGMENT:
  351. # 判断
  352. if answer_log.status != ExamAnswerLog.NOTDONE:
  353. if question.judgment == (answer_log.status == ExamAnswerLog.RIGHT):
  354. answer_log.status = ExamAnswerLog.RIGHT
  355. instance.judgment_answer_count += 1
  356. else:
  357. answer_log.status = ExamAnswerLog.WRONG
  358. ErrorBook.add_error(question, request.user, answer_log)
  359. else:
  360. # 论述题 需要人工评分
  361. pass
  362. answer_log.save()
  363. instance.submit_time = timezone.now()
  364. use_time = instance.submit_time - instance.exam_time
  365. instance.use_time = use_time.seconds
  366. single_answer_scores = instance.single_answer_count * instance.exampaper.single_scores
  367. multiple_answer_scores = instance.multiple_answer_count * instance.exampaper.multiple_scores
  368. fill_answer_scores = instance.fill_answer_count * instance.exampaper.fill_scores
  369. judgment_answer_scores = instance.judgment_answer_count * instance.exampaper.judgment_scores
  370. instance.single_answer_scores = single_answer_scores
  371. instance.multiple_answer_scores = multiple_answer_scores
  372. instance.fill_answer_scores = fill_answer_scores
  373. instance.judgment_answer_scores = judgment_answer_scores
  374. instance.scores = single_answer_scores + judgment_answer_scores + multiple_answer_scores + fill_answer_scores
  375. # 计算排名
  376. up_rank = ExamLog.objects.filter(exam=instance.exam, scores__gte=instance.scores).count()
  377. instance.rank = up_rank + 1
  378. ExamLog.objects.filter(exam=instance.exam, scores__lte=instance.scores).update(rank=F('rank') + 1)
  379. instance.save()
  380. instance.exampaper.did_count += 1
  381. instance.exampaper.save()
  382. SysLog.objects.addnew(request.user, SysLog.INSERT, u"提交正式考试题,id=%d" % (instance.id))
  383. except CustomError as e:
  384. return response_error(e.get_error_msg())
  385. except Exception as e:
  386. traceback.print_exc()
  387. return response_error(str(e))
  388. return response_ok()
  389. class ExamQuestionFeedbackViewSet(CustomModelViewSet):
  390. permission_classes = [IsStaff, ]
  391. queryset = ExamQuestionFeedback.objects.filter()
  392. serializer_class = ExamQuestionFeedbackSerializer
  393. def perform_create(self, serializer):
  394. super(ExamQuestionFeedbackViewSet, self).perform_create(serializer)
  395. instance = serializer.instance
  396. validated_data = serializer.validated_data
  397. SysLog.objects.addnew(self.request.user, SysLog.INSERT, u'添加试题错误反馈,id=%d' % instance.id, validated_data)