# 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'
'
title = title + '
' + 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()