123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- # coding=utf-8
- import random
- import re
- import base64
- from io import BytesIO
- from PIL import Image
- from django.db import models
- from django.utils import timezone
- from django.conf import settings
- from docx import Document
- from docx.shared import Inches, Pt
- from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
- from docx.oxml.ns import qn
- from docx.oxml import OxmlElement
- from bs4 import BeautifulSoup
- from utils.exceptions import CustomError
- from apps.foundation.models import Subject
- from apps.examination.examquestion.models import ExamQuestion, ExamQuestionOption
- class ExamPaper(models.Model):
- MOCK = 1
- FORMAL = 2
- RANDOM = 3
- TYPE_CHOICES = (
- (MOCK, u'模拟试卷'),
- (FORMAL, u'正式试卷'),
- (RANDOM, u'随机试卷'),
- )
- TYPE_JSON = [{'id': item[0], 'value': item[1]} for item in TYPE_CHOICES]
- name = models.CharField(max_length=200, verbose_name=u"名称")
- subject = models.ForeignKey(Subject, verbose_name=u"科目", on_delete=models.PROTECT)
- type = models.PositiveSmallIntegerField(choices=TYPE_CHOICES, verbose_name=u"类型")
- passline = models.IntegerField(verbose_name=u'及格线')
- desc = models.TextField(verbose_name=u"备注", null=True, blank=True)
- single_simple_count = models.IntegerField(verbose_name=u'简单单选题数量', default=0)
- 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)
- create_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'添加人', 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)
- did_count = models.IntegerField(verbose_name=u'做过人数', default=0, editable=False)
- class Meta:
- db_table = "exam_paper"
- ordering = ['-id']
- verbose_name = u"试卷管理"
- default_permissions = ()
- @staticmethod
- def getById(id):
- instance = ExamPaper.objects.filter(pk=id).first()
- if not instance:
- raise CustomError(u'未找到相应的试卷')
- return instance
- def generate_passline(self):
- self.passline = int(self.question_total_scores * 0.6)
- def clear_detail(self):
- ExamPaperDetail.objects.filter(main=self).update(delete=True)
- def generate_detail(self):
- begin_order = 1
- if self.single_simple_count:
- self._generate_detail(self.single_simple_count, ExamQuestion.SINGLE, ExamQuestion.SIMPLE, begin_order)
- begin_order += self.single_simple_count
- if self.single_mid_count:
- self._generate_detail(self.single_mid_count, ExamQuestion.SINGLE, ExamQuestion.MID, begin_order)
- begin_order += self.single_mid_count
- 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
- if self.multiple_mid_count:
- self._generate_detail(self.multiple_mid_count, ExamQuestion.MULTIPLE, ExamQuestion.MID, begin_order)
- begin_order += self.multiple_mid_count
- 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
- if self.fill_mid_count:
- self._generate_detail(self.fill_mid_count, ExamQuestion.FILL, ExamQuestion.MID, begin_order)
- begin_order += self.fill_mid_count
- 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
- if self.judgment_mid_count:
- self._generate_detail(self.judgment_mid_count, ExamQuestion.JUDGMENT, ExamQuestion.MID, begin_order)
- begin_order += self.judgment_mid_count
- if self.judgment_hard_count:
- 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.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):
- questions = ExamQuestion.objects.filter(
- chapter__subject=self.subject,
- difficulty = difficulty,
- type = type,
- delete=False
- )
- questions_count = questions.count()
- if questions_count < count:
- raise CustomError(u'[%s][%s]数量不足!' % (ExamQuestion.DIFFICULTY_CHOICES[difficulty-1][1], ExamQuestion.TYPE_CHOICES[type-1][1]))
- question_ids = questions.values_list('id', flat=True)
- rows = random.sample(list(question_ids), count)
- random.shuffle(rows)
- data = []
- for row in rows:
- item = ExamPaperDetail(
- main=self,
- question_id=row,
- order=begin_order
- )
- begin_order += 1
- data.append(item)
- ExamPaperDetail.objects.bulk_create(data)
- def _html_to_docx(self, html, doc, para):
- soup = BeautifulSoup(html, 'html.parser')
- for element in soup.descendants:
- if element.name is None: # Text node
- run = para.add_run(element)
- elif element.name == 'b' or element.name == 'strong':
- run = para.add_run(element.get_text())
- run.bold = True
- elif element.name == 'i' or element.name == 'em':
- run = para.add_run(element.get_text())
- run.italic = True
- elif element.name == 'u':
- run = para.add_run(element.get_text())
- run.underline = True
- elif element.name == 'br':
- para.add_run().add_break()
- elif element.name == 'p':
- para.add_run().add_break()
- elif element.name == 'span':
- run = para.add_run(element.get_text())
- elif element.name == 'img' and element.get('src').startswith('data:image'):
- # 如果是 base64 编码的图片
- img_data = element.get('src').split(',', 1)[1] # 提取 base64 数据部分
- try:
- img_bytes = base64.b64decode(img_data)
- img_stream = BytesIO(img_bytes)
- image = Image.open(img_stream)
- # 插入图片到文档中
- doc.add_picture(img_stream, width=Inches(4.0)) # 根据需要调整宽度
- image.close()
- except Exception as e:
- print(f"Error inserting image: {e}")
- def _add_category_title(self, doc, category, num_index):
- num_chars = ('一、', '二、', '三、', '四、', '五、', )
- para = doc.add_paragraph()
- text = f'{num_chars[num_index]}{category}:本大题共有 {self.single_total_count}小题,每小题{self.single_scores}分,共{self.single_scores * self.single_total_count}分。'
- run = para.add_run(text)
- run.font.size = Pt(12) # 设置字体大小
- run.bold = True # 设置加粗
- def _add_title(self, doc, order, title):
- if title.startswith('<p>') and title.endswith('</p>'):
- title = title[3:-4]
- para = doc.add_paragraph()
- html_content = f'{order}.{title}'
- self._html_to_docx(html_content, doc, para)
- def _add_option(self, doc, index, content):
- option_chars = ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')
-
- pattern = r'^[A-Z]\..*'
- if re.match(pattern, content) is None:
- content = f'{option_chars[index]}.{content}'
- para = doc.add_paragraph()
- para.add_run(content)
- def create_docx(self):
- # 创建一个新的文档
- doc = Document()
- para = doc.add_paragraph()
- run = para.add_run(self.name)
- run.font.size = Pt(16) # 设置字体大小
- run.bold = True # 设置加粗
- para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 添加居中对齐的段落
- para = doc.add_paragraph()
- run = para.add_run("姓名:___________________________日期:___________________________分数:___________________________")
- num_index = 0
-
- # 单选题
- rows = ExamPaperDetail.objects.filter(main=self, question__type=ExamQuestion.SINGLE, delete=False).order_by('order')
- if rows.count() > 0:
- self._add_category_title(doc, '单选题', num_index)
-
- seq = 1
- for row in rows:
- self._add_title(doc, seq, row.question.title)
- seq += 1
- options = ExamQuestionOption.objects.filter(main=row.question, delete=False)
- index = 0
- for option in options:
- self._add_option(doc, index, option.content)
- index += 1
- num_index += 1
- # 多选题
- rows = ExamPaperDetail.objects.filter(main=self, question__type=ExamQuestion.MULTIPLE, delete=False).order_by('order')
- if rows.count() > 0:
- self._add_category_title(doc, '多选题', num_index)
- seq = 1
- for row in rows:
- self._add_title(doc, seq, row.question.title)
- seq += 1
- options = ExamQuestionOption.objects.filter(main=row.question, delete=False)
- index = 0
- for option in options:
- self._add_option(doc, index, option.content)
- index += 1
- num_index += 1
- # 填空题
- rows = ExamPaperDetail.objects.filter(main=self, question__type=ExamQuestion.FILL, delete=False).order_by('order')
- if rows.count() > 0:
- self._add_category_title(doc, '填空题', num_index)
- seq = 1
- for row in rows:
- self._add_title(doc, seq, row.question.title)
- seq += 1
- num_index += 1
- # 判断题
- rows = ExamPaperDetail.objects.filter(main=self, question__type=ExamQuestion.JUDGMENT, delete=False).order_by('order')
- if rows.count() > 0:
- self._add_category_title(doc, '判断题', num_index)
- seq = 1
- for row in rows:
- self._add_title(doc, seq, row.question.title)
- seq += 1
- num_index += 1
- # 论述题
- rows = ExamPaperDetail.objects.filter(main=self, question__type=ExamQuestion.DISCUSS, delete=False).order_by('order')
- if rows.count() > 0:
- self._add_category_title(doc, '论述题', num_index)
- seq = 1
- for row in rows:
- #print(row.question.title)
- self._add_title(doc, seq, row.question.title)
- doc.add_paragraph('')
- doc.add_paragraph('')
- doc.add_paragraph('')
- doc.add_paragraph('')
- doc.add_paragraph('')
- seq += 1
- num_index += 1
- return doc
- class ExamPaperDetail(models.Model):
- main = models.ForeignKey(ExamPaper, verbose_name=u"试卷", on_delete=models.PROTECT)
- question = models.ForeignKey(ExamQuestion, verbose_name=u"试题", on_delete=models.PROTECT)
- order = models.IntegerField(verbose_name=u'序号', editable=False)
- delete = models.BooleanField(verbose_name=u'删除', default=False, editable=False)
- class Meta:
- db_table = "exam_paper_detail"
- ordering = ['order']
- verbose_name = u"试卷明细"
- default_permissions = ()
|