123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- # coding=utf-8
- import itertools
- import string
- from django.db import models
- from django.utils import timezone
- from django.conf import settings
- from utils.exceptions import CustomError
- from apps.foundation.models import Chapter
- class ExamQuestion(models.Model):
- SINGLE = 1
- 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]
- SIMPLE = 1
- MID = 2
- HARD = 3
- DIFFICULTY_CHOICES = (
- (SIMPLE, u'简单'),
- (MID, u'中等'),
- (HARD, u'困难'),
- )
- DIFFICULTY_JSON = [{'id': item[0], 'value': item[1]} for item in DIFFICULTY_CHOICES]
- chapter = models.ForeignKey(Chapter, verbose_name=u"章节", on_delete=models.PROTECT)
- type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"题型")
- difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, verbose_name=u"难度")
- scores = models.IntegerField(verbose_name=u'分数')
- 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)
- class Meta:
- db_table = "exam_question"
- ordering = ['-id']
- verbose_name = u"试题管理"
- default_permissions = ()
- @classmethod
- def batch_create(cls, subject, data, user):
- for row in data:
- # print("=====>", row)
- num = row.get('num', None)
- if not num:
- raise CustomError('序号不能为空')
- if not num.isdigit():
- raise CustomError('序号必须为数字')
-
- chapter = row.get('chapter', None)
- if not chapter:
- raise CustomError(f'题目{num}章节不能为空')
- else:
- chapter = Chapter.objects.filter(subject=subject, name=chapter).first()
- if not chapter:
- raise CustomError(f'题目{num}章节不存在')
-
- title = row.get('title', None)
- if not title:
- raise CustomError(f'题目{num}题文不能为空')
-
- type = row.get('type', None)
- if not type:
- raise CustomError(f'题目{num}题型不能为空')
- found = False
- for item in ExamQuestion.TYPE_CHOICES:
- if item[1] == type:
- type = item[0]
- found = True
- break
- if not found:
- raise CustomError(f'题目{num}题型不存在,可选类型有:填空题、单选题、多选题、判断题、论述题')
- difficulty = row.get('difficulty', None)
- if not difficulty:
- raise CustomError(f'题目{num}题型不能为空')
- found = False
- for item in ExamQuestion.DIFFICULTY_CHOICES:
- if item[1] == difficulty:
- difficulty = item[0]
- found = True
- break
- if not found:
- raise CustomError(f'题目{num}难度无效,可选类型有:简单、中等、困难')
- score = row.get('score', None)
- if not score:
- raise CustomError(f'题目{num}分数不能为空')
- if not score.isdigit():
- raise CustomError(f'题目{num}分数必须为数字')
- answer = row.get('answer', None)
- if not answer:
- raise CustomError(f'题目{num}答案不能为空')
-
- if type == ExamQuestion.JUDGMENT and not answer in ('正确', '错误',):
- raise CustomError(f'题目{num}答案无效,可选类型有:正确、错误')
-
- images = row.get('images', None)
- if images:
- for image in images:
- img = f'<img src="data:image/png;base64,{image}" />'
- title = title + '<br>' + img
- judgment = False
- if type == ExamQuestion.JUDGMENT:
- judgment = answer == '正确'
- discuss_answer = ''
- if type == ExamQuestion.DISCUSS:
- discuss_answer = answer
- analysis = row.get('analysis', None)
- question = cls.objects.create(
- title=title,
- type=type,
- difficulty=difficulty,
- scores=score,
- discuss_answer=discuss_answer,
- judgment=judgment,
- chapter=chapter,
- analysis=analysis,
- create_user=user
- )
- if type == ExamQuestion.SINGLE or type == ExamQuestion.MULTIPLE:
- options = row.get('options', None)
- options_data = ExamQuestionOption.validate(type, options, answer)
- for option in options_data:
- ExamQuestionOption.objects.create(main=question, content=option['content'], right=option['right'])
- if type == ExamQuestion.FILL:
- fill_data = ExamQuestionFill.validate(answer)
- for item in fill_data:
- ExamQuestionFill.objects.create(main=question, content=item['content'], order=item['order'])
- class ExamQuestionOption(models.Model):
- main = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
- content = models.TextField(verbose_name=u"内容")
- right = models.BooleanField(verbose_name=u'正确答案', default=False)
- delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
- class Meta:
- db_table = "exam_question_option"
- ordering = ['id']
- verbose_name = u"选择题选项" # 单选、多选
- default_permissions = ()
- @classmethod
- def validate(cls, type, options, answer):
- if not options:
- raise CustomError(f'选项不能为空')
- if len(options) < 2:
- raise CustomError(f'选项不能少于2个')
- if len(options) > 10:
- raise CustomError(f'选项不能多于10个')
-
- answers = list(answer)
- answers_len = len(answers)
- if type == ExamQuestion.SINGLE and answers_len > 1:
- raise CustomError(f'单选题答案只能有一个')
-
- alphabet = string.ascii_uppercase # 包含 A 到 Z 的字母序列
- cycle_alphabet = itertools.cycle(alphabet) # 创建无限循环的字母序列
- all_chars = list(itertools.islice(cycle_alphabet, answers_len)) # 截取所需长度的部分
- if len(set(answers).difference(all_chars)) > 0:
- raise CustomError(f'答案选项无效,可选项有:{all_chars}')
-
- validation_data = []
- index = 0
- for option in options:
- item = {
- 'content': option,
- 'right': False
- }
- for letter in answers:
- if index == ord(letter) - ord('A'):
- item['right'] = True
- break
- index += 1
- validation_data.append(item)
- return validation_data
-
- class ExamQuestionFill(models.Model):
- main = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
- content = models.TextField(verbose_name=u"内容")
- order = models.IntegerField(verbose_name=u'排序')
- delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
- class Meta:
- db_table = "exam_question_fill"
- ordering = ['order', 'id']
- verbose_name = u"试题填空"
- default_permissions = ()
- @classmethod
- def validate(cls, answer):
- answers = answer.split('$')
- validation_data = []
- index = 1
- for option in answers:
- item = {
- 'content': option,
- 'order': index
- }
- index += 1
- validation_data.append(item)
- return validation_data
- class ExamQuestionFeedback(models.Model):
- TYPE = 1
- ANSWER = 2
- ANALYSIS = 3
- OTHER = 4
- TYPE_CHOICES = (
- (TYPE, u'题目类型'),
- (ANSWER, u'题目答案'),
- (ANALYSIS, u'题目解析'),
- (OTHER, u'其它'),
- )
- TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
- UNTREATED = 1
- PROCESSED = 2
- STATUS_CHOICES = (
- (UNTREATED, u'未处理'),
- (PROCESSED, u'已处理'),
- )
- STATUS_JSON = [{'id': item[0], 'value': item[1]} for item in STATUS_CHOICES]
- main = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
- type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"错误类型")
- desc = models.TextField(verbose_name=u"错误描述", null=True, blank=True)
- create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'反馈人',related_name='exam_question_feedback_user', editable=False, on_delete=models.PROTECT)
- create_time = models.DateTimeField(verbose_name=u"反馈时间", default=timezone.now, editable=False)
- status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, verbose_name=u'处理状态', default=UNTREATED)
- process_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u"处理人", on_delete=models.PROTECT, null=True, related_name="exam_question_feedback_ref_process_user")
- process_time = models.DateTimeField(verbose_name=u'处理时间', null=True)
- class Meta:
- db_table = "exam_question_feedback"
- ordering = ['-id']
- verbose_name = u"错误反馈"
- default_permissions = ()
- @staticmethod
- def getById(id):
- instance = ExamQuestionFeedback.objects.filter(pk=id).first()
- if not instance:
- raise CustomError(u'未找到相应的错误反馈')
- return instance
- def process(self, user):
- self.process_user = user
- self.status = ExamQuestionFeedback.PROCESSED
- self.process_time = timezone.now()
|