# coding: utf-8 ''' Created on 27.07.2015 ☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦ @author: tr0glo)|(I╠╣ ☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦☦ ''' from bs4 import BeautifulSoup import datetime import string import json import random from java.sql import Timestamp from java.util import Date, Calendar from uuid import uuid4 import base64 import array import time import re from __builtin__ import None import logging from ru.curs.celesta.showcase.utils import XMLJSONConverter from org.apache.poi.xssf.usermodel import XSSFWorkbook from org.apache.poi.ss.usermodel import Cell from qt._qt_orm import QtiVariantCursor, ContentCursor, \ Content_AttributeValueCursor, listAttributeContentCursor, \ TestingVariantAssignCursor, QtiQuestionCursor, QtiThemeCursor, \ QtiVariantQuestionCursor, QtiVariantDestructorCursor, QtiDestructorCursor, \ QtiVariantAnswerCursor, QtiVariantDestructorAnswerCursor, QtiVariantParamsCursor,\ QtiResourceCursor from qt._qt_orm import accessToVariantCursor from java.io import BufferedReader, InputStreamReader from nci._nci_orm import test_matrix_themesCursor, global_settingsCursor from fileRepository._fileRepository_orm import fileCursor from fileRepository.functions import getRelativePathById # _default_params = { # # Произвольное(0)/Последовательное(1) формирование вопросов # 'NoRandom': 1, # # Время прохождения теста, мин # 'TimeTest': 20, # # Количество вопросов # 'QuantityQuestions': 20, # # Процент правильных ответов для оценки 3 # 'Percent3': 60, # # Процент правильных ответов для оценки 4 # 'Percent4': 70, # # Процент правильных ответов для оценки 5 # 'Percent5': 80, # # Формировать(0)/Не формировать(1) протокол # 'NoProtocol': 0, # # Контроль(1)/Самоконтроль(0) # 'NoSelfTesting': 1, # # Отображать оставшееся время выполнения теста # 'ShowRemainingTime': 1, # # Возможность возврата к вопросам после окончания теста (если осталось время) # 'BackToStart': 1, # # Количество повторных возвратов (-1 - без ограничений) # 'BackToStartCount': -1, # # Варианты доступности вопросов при повторном проходе: # # 0 - Доступны все вопросы теста # # 1 - Доступны только вопросы, пропущенные при первом проходе # # 2 - Доступны только вопросы, на которые дан неправильный ответ # # 3 - Доступны вопросы,пропущенные при первом проходе и на которые дан неправильный ответ # 'BackToStartQuestions': 0} MATCHING_ATTRIBUTE_PACKAGE = { 'organization': u'Учебная организация', 'department':u'Кафедра', 'department_head': u'Зав. кафедрой', 'faculty': u'Факультет', 'study_form': u'Форма обучения', 'discipline': u'Учебная дисциплина', 'subject': u'Учебный предмет', 'specialty': u'Специальность', 'module': u'Модуль', 'theme': u'Тема', 'year': u'Учебный год составления', 'address': u'Адрес (база)', 'responsible_person': u'Ответственный составитель', 'email': u'E-mail', 'phone': u'Моб. телефон', 'cabinet': u'Кабинет №', 'source': u'Источник' } MATCHING_NUMBERS_SHORT = { 0: 'organization', 1: 'specialty', 2: 'module', 3: 'responsible_person', 4: 'phone', 5: 'email' } MATCHING_NUMBERS = { 1: 'department', 2: 'faculty', 3: 'address', 4: 'department_head', 5: 'responsible_person', 6: 'email', 7: 'phone', 8: 'cabinet', 9: 'discipline', 10: 'subject', 11: 'year', 12: 'specialty', 13: 'study_form', 14: 'module', 15: 'theme', 19: 'source', 20: 'organization' } def completeTest(context, variantId): u'''Функция завершения варианта теста и проставления оценки за тестрирование''' QtiVariantAnswer = QtiVariantAnswerCursor(context) QtiVariantAnswer.setRange('VariantID', variantId) QtiVariantAnswer.first() # Количество правильно отвеченных вопросов positiveCount = 0 # Балл за тестирование score = 0 # Завершаем тестирование, если оно ещё не было завершено if QtiVariantAnswer.TimeFinish is None: QtiVariantQuestionCur = QtiVariantQuestionCursor(context) QtiVariantQuestionCur.setRange('VariantID', variantId) # Общее количество вопросов в тестировании questionCount = QtiVariantQuestionCur.count() QtiQuestion = QtiQuestionCursor(context) QtiDestructor = QtiDestructorCursor(context) QtiVariantDestructor = QtiVariantDestructorCursor(context) QtiVariantDestructorAnswer = QtiVariantDestructorAnswerCursor(context) # Цикл по вопросам тестирования for QtiVariantQuestion in QtiVariantQuestionCur.iterate(): checkFlag = True # Очистка курсоров для корректной работы QtiDestructor.clear() QtiVariantDestructor.clear() QtiQuestion.get(QtiVariantQuestion.QuestionID) question_type = QtiQuestion.QuestionType question_id = QtiQuestion.QuestionID # Вопросы с одиночным или множественным выбором if question_type == 1 or question_type == 2: # Правильные ответы хранятся в QtiDestructor и у правильных ответов поле isTrue равно True QtiDestructor.setRange('QuestionID', question_id) QtiDestructor.setRange('IsTrue', True) answerList = [] # Формируе список правильных вариантов отвтов for QtiDestructor in QtiDestructor.iterate(): answerList.append(QtiDestructor.DestructorID) QtiVariantDestructor.setRange('VariantQuestionID', QtiVariantQuestion.VariantQuestionID) # Цикл по вариантам ответов слушателя for QtiVariantDestructor in QtiVariantDestructor.iterate(): QtiVariantDestructorAnswer.setRange('VariantDestructorID', QtiVariantDestructor.VariantDestructorID) # Если слушатель в ходе тестирования выбрал вариант текущий вариант, то запись об этом будет в таблице QtiVariantDestructorAnswer if QtiVariantDestructorAnswer.tryFirst(): # Если слушатель выбрал вариант, которого нет в списке правильных, то вопрос не засчитывается if QtiVariantDestructor.DestructorID not in answerList: checkFlag = False else: # Если слушатель выбрал правльный вариант, то удаляем этот вариант из списка правальных answerList.remove(QtiVariantDestructor.DestructorID) # Список правильных ответов будет непуст, если слушатель не указал какой-либо правильный вариант, следовательно, вопрос не засчитывается if answerList != []: checkFlag = False # Вопросы с текстовым ответом elif question_type == 3: QtiDestructor.setRange('QuestionID', question_id) QtiDestructor.first() # Правильный ответ хранится в таблице QtiDestructor в полу DestructorHtml. Для вопросов такого типа в QtiDestructor только одна запись соответствует вопросу correctAnswer = QtiDestructor.DestructorHtml QtiVariantDestructor.setRange('VariantQuestionID', QtiVariantQuestion.VariantQuestionID) QtiVariantDestructor.first() QtiVariantDestructorAnswer.setRange('VariantDestructorID', QtiVariantDestructor.VariantDestructorID) # Получаем ответ на вопрос, данный слушателем if QtiVariantDestructorAnswer.tryFirst(): # Если ответв на вопрос не совпадает с верным, то вопрос не засчитывается if QtiVariantDestructorAnswer.DestructorText != correctAnswer: checkFlag = False else: # Если ответа нет, то вопрос не засчитывается checkFlag = False # Вопрос-сопоставление elif question_type == 4: # Для вопросов-сопоставлений в таблице QtiDestructor в полях DestructorHtml и DestructorHtml2 хранятся значение из левой колонки, # в поле AnswerText правильное значений их правой колонки. В QtiDestructorAnswer для каждого варианта в поле DestructorID пишется id, # того варианта значение из правой колонки которого было помещено в соответствие текущему варианту. QtiVariantDestructor.setRange('VariantQuestionID', QtiVariantQuestion.VariantQuestionID) for QtiVariantDestructor in QtiVariantDestructor.iterate(): QtiVariantDestructorAnswer.setRange('VariantDestructorID', QtiVariantDestructor.VariantDestructorID) if QtiVariantDestructorAnswer.tryFirst(): if QtiVariantDestructorAnswer.DestructorID != QtiVariantDestructor.DestructorID: checkFlag = False else: checkFlag = False # Вопрос-сортировка elif question_type == 5: # В QtiDestructor правильный порядок задаётся полем DestructorOrder. В QtiVariantDestructorAnswer в поле DestructorText заносится номер выбранной строки QtiVariantDestructor.setRange('VariantQuestionID', QtiVariantQuestion.VariantQuestionID) for QtiVariantDestructor in QtiVariantDestructor.iterate(): QtiDestructor.get(QtiVariantDestructor.DestructorID) QtiVariantDestructorAnswer.setRange('VariantDestructorID', QtiVariantDestructor.VariantDestructorID) if QtiVariantDestructorAnswer.tryFirst(): if int(QtiDestructor.DestructorOrder) != int(QtiVariantDestructorAnswer.DestructorText): checkFlag = False else: checkFlag = False # Вопрос-классификация elif question_type == 6: # Ответы хранятся аналогично вопросу-сопоставлению QtiVariantDestructor.setRange('VariantQuestionID', QtiVariantQuestion.VariantQuestionID) for QtiVariantDestructor in QtiVariantDestructor.iterate(): QtiDestructor.get(QtiVariantDestructor.DestructorID) correctAnswer = QtiDestructor.AnswerText QtiVariantDestructorAnswer.setRange('VariantDestructorID', QtiVariantDestructor.VariantDestructorID) if QtiVariantDestructorAnswer.tryFirst(): QtiDestructor.get(QtiVariantDestructorAnswer.DestructorID) if QtiDestructor.AnswerText != correctAnswer: checkFlag = False else: checkFlag = False # Если ответ правильный, увеличиваем счётчик правильных ответов, в таблицу пишем что на вопрос был дан правильный ответ if checkFlag: positiveCount += 1 QtiVariantQuestion.CorrectAnswer = True QtiVariantQuestion.update() QtiVariantDestructorAnswer.close() QtiVariantDestructor.close() QtiDestructor.close() QtiQuestion.close() QtiVariantQuestionCur.close() # Расчёт баллов за тест score = round((positiveCount * 1.0 / questionCount) * 100) now = Date() # Пишет в таблицу время завершения варианта, балл, количество правильных ответов QtiVariantAnswer.TimeFinish = now if QtiVariantAnswer.TimeEnd > now else QtiVariantAnswer.TimeEnd QtiVariantAnswer.Score = score QtiVariantAnswer.PositiveCount = positiveCount mark = 2 # Получаем пороговый уровни для оценок и записываем оценку # percent3 = int(getGlobalAttributeValue(Content,Content_Attribute,listAttributeContent,packageId,'Percent3')) # percent4 = int(getGlobalAttributeValue(Content,Content_Attribute,listAttributeContent,packageId,'Percent4')) # percent5 = int(getGlobalAttributeValue(Content,Content_Attribute,listAttributeContent,packageId,'Percent5')) QtiVariantParams = QtiVariantParamsCursor(context) percent3 = int(getAttributeValue(QtiVariantParams, variantId, 'Percent3')) percent4 = int(getAttributeValue(QtiVariantParams, variantId, 'Percent4')) percent5 = int(getAttributeValue(QtiVariantParams, variantId, 'Percent5')) QtiVariantParams.close() if score >= percent5: mark = 5 elif score >= percent4: mark = 4 elif score >= percent3: mark = 3 QtiVariantAnswer.TestingMark = mark QtiVariantAnswer.update() def checkParams(QtiVariantParams, variantId): u'''Функция проверяет все ли необхомиые атрибуты БТЗ заданы''' paramsList = ['NoRandom', 'TimeTest', 'QuantityQuestions', 'Percent3', 'Percent4', 'Percent5', 'NoProtocol', 'NoSelfTesting'] QtiVariantParams.setRange('VariantID', variantId) for param in paramsList: QtiVariantParams.setRange('ParamID', param) if QtiVariantParams.count() == 0: return False return True def getGlobalAttributeValue(Content, Content_Attribute, listAttributeContent, packageId, attributeName): listAttributeContent.setRange('AttributeContentName', attributeName) if listAttributeContent.tryFirst(): attributeId = listAttributeContent.AttributeContentID Content.setRange('PackageID', packageId) if Content.tryFirst(): Content_Attribute.setRange('ContentID', Content.ContentID) Content_Attribute.setRange('AttributeContentID', attributeId) if Content_Attribute.tryFirst(): return Content_Attribute.AttributeValue return None def getAttributeValue(QtiVariantParams, variantId, paramId): u'''Функция получения значение атрибута БТЗ''' QtiVariantParams.get(variantId, paramId) return QtiVariantParams.Value def setTestTimeByParam(context, variant_id): QtiVariantParams = QtiVariantParamsCursor(context) timeTest = int(getAttributeValue(QtiVariantParams, variant_id, 'TimeTest')) QtiVariantParams.close() QtiVariantAnswer = QtiVariantAnswerCursor(context) QtiVariantAnswer.VariantAnswerID = unicode(uuid4()) QtiVariantAnswer.VariantID = variant_id timeBegin = Date() QtiVariantAnswer.TimeBegin = timeBegin QtiVariantAnswer.TimeEnd = Timestamp(timeBegin.getTime() + timeTest * 60 * 1000) QtiVariantAnswer.insert() QtiVariantAnswer.close() def createTestingVariantAssign(context, variant_id, packageId, sid, personId=None, params=None, cycleId=None): u'''Функция добавления записи в таблицу TestingVariantAssign. Если не передаютяс параметры, то пишутся глобальные параметры''' TVA = TestingVariantAssignCursor(context) QtiVariantParams = QtiVariantParamsCursor(context) Content = ContentCursor(context) Content_Attribute = Content_AttributeValueCursor(context) listAttributeContent = listAttributeContentCursor(context) TVA.VariantID = variant_id TVA.AlgorithmTestingID = 2 TVA.DateAssign = Date() TVA.WhenAdd = Date() TVA.UserName = 'admin' TVA.ServerName = 'local' TVA.PackageID = packageId TVA.personUid = personId TVA.cycleid = cycleId TVA.testtype = 2 TVA.insert() if params is not None: # Если есть параметры, пишем их в таблицу for param in params: QtiVariantParams.VariantID = TVA.VariantID QtiVariantParams.ParamID = param QtiVariantParams.Value = params[param] QtiVariantParams.insert() else: # Если нет параметров, то получаем глобальные параметры и записываем их # params = ['NoRandom', 'TimeTest', # 'QuantityQuestions', 'Percent3', # 'Percent4', 'Percent5', # 'NoProtocol', 'NoSelfTesting'] global_settingsCur = global_settingsCursor(context) for item in global_settingsCur.iterate(): param = item.id value = item.value QtiVariantParams.VariantID = TVA.VariantID QtiVariantParams.ParamID = param QtiVariantParams.Value = getGlobalAttributeValue(Content, Content_Attribute, listAttributeContent, packageId, param) if QtiVariantParams.Value is None or QtiVariantParams.Value == '': QtiVariantParams.Value = value QtiVariantParams.insert() return TVA.VariantID def generateVariantFlute(context, params): u'''Генерация варианта - функция для флейты. ''' parameters = json.loads(params.params) packageId = parameters['packageId'] variantId = parameters['variantId'] generateVariant(context, packageId, variantId) def getPackageThemes(context, packageId, themesList): QtiTheme = QtiThemeCursor(context) QtiTheme.setRange('PackageID', packageId) QtiTheme.orderBy('ThemeOrder') for theme in QtiTheme.iterate(): themesList.append(theme.ThemeID) QtiTheme.close() return themesList def generateRandomCountList(count_1, count_2): h = count_1 // count_2 m = count_1 % count_2 count_list = list() for k in range(count_2): l = 1 if m>0 else 0 count_list.insert(k, h + l) if m > 0: m -= 1 random.shuffle(count_list) return count_list def generateRandomQuestionsCountDict(context, theme_list, count): td = dict() td_corr = dict() # Получаем количество вопросов в каждой теме QtiQuestion = QtiQuestionCursor(context) questions = 0 for item in theme_list: QtiQuestion.setRange('ThemeID', item) c = QtiQuestion.count() td[item] = c questions += c QtiQuestion.close() # Уменьшаем количество вопросов в теме для получения требуемого количества rc = 0 for item in theme_list: qc = int(round(td[item] * count / float(questions))) rc += qc td_corr[item] = qc # Сумма округленных значений может отличаться if rc > count: n = rc - count sorted_td_corr = OrderedDict(sorted(td_corr.items(), key=operator.itemgetter(1))) for key in reversed(sorted_td_corr): value = sorted_td_corr[key] if value > 0: td_corr[key] = value - 1 n -= 1 if n == 0: break elif rc < count: n = count - rc for key, value in td_corr.iteritems(): if (td[key] - value) > 0: td_corr[key] = value + 1 n -= 1 if n == 0: break return td_corr def generateVariant(context, packageId, variantId, debugFlag=False): u'''Функция генерации варианта''' # Получение режима выбора воропросов: '1' - выбор по порядку, '0' - случайным образом QtiVariantParams = QtiVariantParamsCursor(context) noRandom = getAttributeValue(QtiVariantParams, variantId, 'NoRandom') # Получение количества вопросов в варианте quantityQuest = getAttributeValue(QtiVariantParams, variantId, 'QuantityQuestions') QtiVariantParams.close() # themeDict = dict() # themeList = list() # questionDict = dict() # count = 0 # # QtiQuestion = QtiQuestionCursor(context) # QtiTheme = QtiThemeCursor(context) # QtiTheme.setRange('PackageID', packageId) # QtiTheme.orderBy('ThemeOrder') # # Формируется список тем и словарь списков вопросов по темам. Темы и вопросы упорядочены по соответствующим табличным полям. # for theme in QtiTheme.iterate(): # QtiQuestion.setRange('ThemeID', theme.ThemeID) # themeDict[theme.ThemeID] = QtiQuestion.count() # themeList.append(theme.ThemeID) # count += QtiQuestion.count() # questionDict[theme.ThemeID] = list() # QtiQuestion.orderBy('QuestionOrder') # for question in QtiQuestion.iterate(): # questionDict[theme.ThemeID].append(question.QuestionID) # # QtiQuestion.close() # QtiTheme.close() # Формируется список тем и словарь списков вопросов по темам. Темы и вопросы упорядочены по соответствующим табличным полям. themeList = list() themeDict = dict() test_matrix_themesCur = test_matrix_themesCursor(context) test_matrix_themesCur.setRange('tm_id', packageId) is_btz = True for test_item in test_matrix_themesCur.iterate(): if is_btz: is_btz = False #Формируем список БТЗ, если для генерации варианта используется Матрица тем package_id = test_item.package_id theme_id = test_item.theme_id if theme_id: themeList.append(theme_id) themeDict[theme_id] = test_item.question_count else: tmpList = list() getPackageThemes(context, package_id, tmpList) tmpDict = generateRandomQuestionsCountDict(context, tmpList, test_item.question_count) for k in tmpDict: themeDict[k] = tmpDict[k] themeList.extend(tmpList) if is_btz: # Если не из Матрицы тем, то в packageId передан ИД БТЗ getPackageThemes(context, packageId, themeList) test_matrix_themesCur.close() # print themeList questionDict = dict() questionTypeDict = dict() count = 0 QtiQuestion = QtiQuestionCursor(context) for theme_id in themeList: QtiQuestion.setRange('ThemeID', theme_id) count += QtiQuestion.count() questionDict[theme_id] = list() QtiQuestion.orderBy('QuestionOrder') for question in QtiQuestion.iterate(): questionDict[theme_id].append(question.QuestionID) questionTypeDict[question.QuestionID] = question.QuestionType QtiQuestion.close() # Если количество вопросов не определено, то берутся все доступные вопросы. if quantityQuest is None: quantityQuest = count noRandom = '1' QtiVariant = QtiVariantCursor(context) QtiVariant.setRange('PackageID', packageId) variantName = u'Вариант №%s' % (QtiVariant.count() + 1) # Вставляем запись в таблицу сгенерированных вариантов QtiVariant.VariantID = variantId QtiVariant.PackageID = packageId QtiVariant.VariantName = variantName QtiVariant.DateCreated = datetime.datetime.now() QtiVariant.insert() QtiVariant.close() QtiVariantQuestion = QtiVariantQuestionCursor(context) if is_btz: # Если режим отладки, то выбираются все вопросы по порядку, иначе берётся количество вопросов из параметров БТЗ if debugFlag: remainQuest = count else: remainQuest = min(int(quantityQuest), count) # number - определяет номер вопроса в варианте number = 1 # Цикл до тех пор, пока не будет набрано необходимое коичество вопросовю while remainQuest > 0: # raise Exception(remainQuest,len(themeList),themeList) print remainQuest, len(themeList), themeList # Пытаемя равномерно распределить вопросы по темам questCountInt = remainQuest / len(themeList) print questCountInt k = len(themeList) # Если нужно вопросов меньше, чем тем, то случайным образом выбираем темы. if questCountInt == 0: k = remainQuest questCountInt = 1 print questCountInt deleteThemeList = list() # В режиме отладки выбиратся все вопросы по порядку if debugFlag: remainQuest = 0 for theme in themeList: for question in questionDict[theme]: QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = question QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number QtiVariantQuestion.QuestionType = questionTypeDict[question] QtiVariantQuestion.insert() number += 1 else: # Перебираем в случайном порядке темы for theme in random.sample(themeList, k): print theme, len(questionDict[theme]) # Если в текущй теме осталось вопросов меньше чем нужно взять, то берём все доступные вопросы и помечаем тему для удаления из списка доступных тем if len(questionDict[theme]) < questCountInt: remainQuest -= len(questionDict[theme]) deleteThemeList.append(theme) for question in questionDict[theme]: QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = question QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number QtiVariantQuestion.QuestionType = questionTypeDict[question] QtiVariantQuestion.insert() number += 1 else: # Если вопросов в теме ровно столько, скольки и нужно, то помечаем тему для удаления из списка доступных тем if len(questionDict[theme]) == questCountInt: deleteThemeList.append(theme) deleteQuestionList = list() remainQuest -= questCountInt # Выбор вопросов по порядку if noRandom == '1': for i in range(questCountInt): qid = questionDict[theme][i] deleteQuestionList.append(qid) QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = qid QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number QtiVariantQuestion.QuestionType = questionTypeDict[qid] QtiVariantQuestion.insert() number += 1 # Выбор случайных вопросов elif noRandom == '0': questions = random.sample(questionDict[theme], questCountInt) for question in questions: deleteQuestionList.append(question) QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = question QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number QtiVariantQuestion.QuestionType = questionTypeDict[question] QtiVariantQuestion.insert() number += 1 # Удаляем выбранные вопросы из списка доступных вопросов по теме. for question in deleteQuestionList: questionDict[theme].remove(question) # Удаялем темы, в которых нет вопросов из списка доступных тем. for theme in deleteThemeList: themeList.remove(theme) else: # Вариант "Матрица" number = 1 for theme_id, question_count in themeDict.items(): questions = random.sample(questionDict[theme_id], question_count) for question in questions: QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = question QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number QtiVariantQuestion.QuestionType = questionTypeDict[question] QtiVariantQuestion.insert() number += 1 # Переносим варианты ответов на вопросы в таблицы, соответствующие сгенерированному варианту. QtiVariantQuestion.setRange('VariantID', variantId) QtiDestructor = QtiDestructorCursor(context) QtiVariantDestructor = QtiVariantDestructorCursor(context) for QtiVariantQuestion in QtiVariantQuestion.iterate(): QtiDestructor.setRange('QuestionID', QtiVariantQuestion.QuestionID) for destructor in QtiDestructor.iterate(): QtiVariantDestructor.VariantDestructorID = unicode(uuid4()) QtiVariantDestructor.VariantQuestionID = QtiVariantQuestion.VariantQuestionID QtiVariantDestructor.DestructorID = destructor.DestructorID QtiVariantDestructor.insert() def generateThemesVariant(context, packageId, variantId, themeList): u'''Функция генерации варианта''' QtiVariant = QtiVariantCursor(context) # Content = ContentCursor(context) # Content_Attribute = Content_AttributeValueCursor(context) # listAttributeContent = listAttributeContentCursor(context) QtiVariantDestructor = QtiVariantDestructorCursor(context) QtiDestructor = QtiDestructorCursor(context) QtiQuestion = QtiQuestionCursor(context) QtiVariantQuestion = QtiVariantQuestionCursor(context) # QtiVariantParams = QtiVariantParamsCursor(context) QtiTheme = QtiThemeCursor(context) QtiVariant.setRange('PackageID', packageId) variantName = u'Вариант №%s' % (QtiVariant.count() + 1) QtiVariant.VariantID = variantId QtiVariant.PackageID = packageId QtiVariant.VariantName = variantName QtiVariant.DateCreated = datetime.datetime.now() QtiVariant.insert() number = 1 for theme in themeList: QtiTheme.get(theme) QtiQuestion.setRange('ThemeID',theme) QtiQuestion.orderBy('QuestionOrder') for question in QtiQuestion.iterate(): QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = question.QuestionID QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number QtiVariantQuestion.QuestionType = question.QuestionType QtiVariantQuestion.insert() number += 1 # Переносим варианты ответов на вопросы в таблицы, соответствующие сгенерированному варианту. QtiVariantQuestion.setRange('VariantID', variantId) for QtiVariantQuestion in QtiVariantQuestion.iterate(): QtiDestructor.setRange('QuestionID', QtiVariantQuestion.QuestionID) for destructor in QtiDestructor.iterate(): QtiVariantDestructor.VariantDestructorID = unicode(uuid4()) QtiVariantDestructor.VariantQuestionID = QtiVariantQuestion.VariantQuestionID QtiVariantDestructor.DestructorID = destructor.DestructorID QtiVariantDestructor.insert() def generateQuestionsVariant(context, packageId, variantId, questionList): u'''Функция генерации варианта''' QtiVariant = QtiVariantCursor(context) # Content = ContentCursor(context) # Content_Attribute = Content_AttributeValueCursor(context) # listAttributeContent = listAttributeContentCursor(context) QtiVariantDestructor = QtiVariantDestructorCursor(context) QtiDestructor = QtiDestructorCursor(context) # QtiQuestion = QtiQuestionCursor(context) QtiVariantQuestion = QtiVariantQuestionCursor(context) # QtiVariantParams = QtiVariantParamsCursor(context) # QtiTheme = QtiThemeCursor(context) QtiVariant.setRange('PackageID', packageId) variantName = u'Вариант №%s' % (QtiVariant.count() + 1) QtiVariant.VariantID = variantId QtiVariant.PackageID = packageId QtiVariant.VariantName = variantName QtiVariant.DateCreated = datetime.datetime.now() QtiVariant.insert() QtiQuestion = QtiQuestionCursor(context) number = 1 for question in questionList: QtiVariantQuestion.VariantQuestionID = unicode(uuid4()) QtiVariantQuestion.QuestionID = question QtiVariantQuestion.VariantID = variantId QtiVariantQuestion.QuestionNumber = number if QtiQuestion.tryGet(question): QtiVariantQuestion.QuestionType = QtiQuestion.QuestionType QtiVariantQuestion.insert() number += 1 QtiQuestion.close() # Переносим варианты ответов на вопросы в таблицы, соответствующие сгенерированному варианту. QtiVariantQuestion.setRange('VariantID', variantId) for QtiVariantQuestion in QtiVariantQuestion.iterate(): QtiDestructor.setRange('QuestionID', QtiVariantQuestion.QuestionID) for destructor in QtiDestructor.iterate(): QtiVariantDestructor.VariantDestructorID = unicode(uuid4()) QtiVariantDestructor.VariantQuestionID = QtiVariantQuestion.VariantQuestionID QtiVariantDestructor.DestructorID = destructor.DestructorID QtiVariantDestructor.insert() def setAccessPermission(context, sid, mode, variantId): u'''Функция, которая даёт разрешение на просмотр тестирования в режиме отладки или режиме тьютора''' accessToVariant = accessToVariantCursor(context) # Даём доступ на день dateTo = Date(Date().getTime() + 24 * 60 * 60 * 1000) # Если доступ уже есть, то продлеваем его длительность до суток if accessToVariant.tryGet(variantId, mode, sid): accessToVariant.dateTo = dateTo accessToVariant.update() else: accessToVariant.VariantID = variantId accessToVariant.mode = mode accessToVariant.sid = sid accessToVariant.dateTo = dateTo accessToVariant.insert() def getShowCorrectAnswerPermission(context, session, variantId): u'''функция, которая проверяет нужно ли отображать правильные ответы в тестировании''' accessToVariant = accessToVariantCursor(context) sid = session['sessioncontext']['sid'] now = Date() # Если в urlparams нет параметров, то правильные ответы не отображаются if 'urlparam' not in session['sessioncontext']['urlparams']: return False urlparams = session['sessioncontext']['urlparams']['urlparam'] if not isinstance(urlparams, list): urlparams = [urlparams] mode = '' # Находим режим доступа к варианту for urlparam in urlparams: if urlparam['@name'] == 'mode': mode = urlparam['@value'][0] # Проверяем наличие доступа у данного пользователя к данному варианту в данном режиме if accessToVariant.tryGet(variantId, mode, sid): if accessToVariant.dateTo < now: return False return True else: return False def checkTestPermission(context, session, variantId): u'''функция, которая проверяет нужно ли отображать правильные ответы в тестировании''' accessToVariant = accessToVariantCursor(context) sid = session['sessioncontext']['sid'] now = Date() # Если в urlparams нет параметров, то правильные ответы не отображаются if 'urlparam' not in session['sessioncontext']['urlparams']: return False urlparams = session['sessioncontext']['urlparams']['urlparam'] if not isinstance(urlparams, list): urlparams = [urlparams] mode = '' # Находим режим доступа к варианту for urlparam in urlparams: if urlparam['@name'] == 'mode': mode = urlparam['@value'][0] # Проверяем наличие доступа у данного пользователя к данному варианту в данном режиме if accessToVariant.tryGet(variantId, mode, sid): if accessToVariant.dateTo < now: return False return True else: return False def getPermissionForViewTest(context, session, variantId): u'''Проверка разрешения на доступ к варианту теста. Пользователи имеют доступ только к своим тестам''' TVA = TestingVariantAssignCursor(context) sid = session['sessioncontext']['sid'] TVA.get(variantId) # Если в TestingVariantAssign записан sid пользователя, то проверяем по нему, иначе по personUid if TVA.sid is not None: if TVA.sid == sid: return True else: return False else: from journals.functions import personIdFromSID personId = personIdFromSID(context, sid) if TVA.personUid == personId: return True else: return False def getBase64Image(imageStream): u'''Функция получения строки с данным из потока для вставки изображения''' stringout = u'' byteArray = [-1, -1, -1] while True: byteArray[0] = imageStream.read() byteArray[1] = imageStream.read() byteArray[2] = imageStream.read() if byteArray[0] == -1: break elif byteArray[1] == -1: stringout += base64.b64encode(array.array('B', byteArray[0:1]).tostring()) break elif byteArray[2] == -1: stringout += base64.b64encode(array.array('B', byteArray[0:2]).tostring()) break else: stringout += base64.b64encode(array.array('B', byteArray).tostring()) return stringout def getImageById(QtiResource, resourceId, isImage=True): u'''Функция получения изображения по идентификатору''' result = '' if (resourceId is not None) and (QtiResource.tryGet(resourceId)): QtiResource.calcResourceData() try: imgBlob = QtiResource.ResourceData.getInStream() if imgBlob: if isImage: result = '''<image src=%s />''' % (u"data:image/png;base64," + getBase64Image(imgBlob)) else: result = BufferedReader(InputStreamReader(imgBlob, 'utf-8')).readLine() finally: imgBlob.close() return result def getTestJsonFromFile(data_file): u'''Загрузка по формату 2МЕД''' begin = time.time() html = BeautifulSoup(data_file, 'html5lib') print 'HTML loaded', time.time() - begin tables = html('table') options_table = tables[0] options_rows = options_table('tr') count = 0 options = list() test = dict() subthemes = list() subtheme = dict() test['organization'] = u'' for row in options_rows: cols = row('td') count_cols = 0 for col in cols: strings = col.stripped_strings value = [s.replace('\xa0' , ' ') for s in strings] value = ''.join(value) if count_cols == 0: number = int(value) elif count_cols == 1: name = value elif count_cols == 2: col_value = value count_cols += 1 options.append([number, name, col_value]) if number < 16 or number == 19: test[MATCHING_NUMBERS[number]] = col_value if number == 16: subtheme['name'] = col_value if number == 17: subtheme['quest_count'] = int(col_value) if number == 18: subtheme['quest_type'] = col_value subtheme['questions'] = [] subthemes.append(subtheme) subtheme = dict() # print(test) print 'First table finish', time.time() - begin questions_table = tables[1] question_rows = questions_table('tr') subthemeFlag = False currentSubtheme = -1 newQuestionFlag = False choiceFlag = False stopTagSet = {'font-size', 'font', 'line-height'} for row in question_rows: values = ['' for i in range(6)] cols = row('td') for i in range(6): paragraphs = cols[i]('p') if len(paragraphs) > 0: strings = cols[i].stripped_strings val = [s for s in strings] val = ''.join(val) noneFlag = True for ch in val: if not (ch in string.whitespace or ord(ch) == 160): noneFlag = False break if noneFlag: continue listTag = [f for f in cols[i].descendants if hasattr(f, 'attrs')] for tag in listTag: if 'class' in tag.attrs: del tag['class'] if 'style' in tag.attrs: styleList = tag['style'].split(';') newStyleList = [] for style in styleList: styleName = style.split(':')[0].lower().strip() if styleName not in stopTagSet: newStyleList.append(style) if newStyleList: tag['style'] = ';'.join(newStyleList) else: del tag['style'] if i == 3 and values[i] is not None: textList = list() for par in paragraphs: if 'class' in par.attrs: del par['class'] if 'style' in par.attrs: del par['style'] textList.append(unicode(par.text)) values[i] = ''.join(textList) else: values[i] = val # print values if not any(values): newQuestionFlag = True choiceFlag = False subthemes[currentSubtheme]['questions'].append(question) elif values[0].isdigit() and values[1].isdigit() and values[2].isdigit(): subthemeFlag = True currentSubtheme += 1 newQuestionFlag = True elif choiceFlag: choice = dict() if values[1] == '*': choice['correct'] = 'true' else: choice['correct'] = 'false' choice['text'] = values[3] question['choices'].append(choice) elif newQuestionFlag: newQuestionFlag = False choiceFlag = True question = dict() question['text'] = values[3] question['choices'] = [] test['subthemes'] = subthemes print 'Second table finish', time.time() - begin return (u'', test) def cleanString(s): text = u'' ss = s.replace(u' ', '') if ss: pattern = re.compile(r'[\t\n\r\f\v]+') text = re.sub(pattern, '', s) text = (u' '.join(text.split())).strip() return text def getTestJsonFromFile2(data_file): u'''Загрузка по формату Московский врач''' html = BeautifulSoup(data_file, 'html5lib') tables = html('table') options_table = tables[0] options_rows = options_table('tr') test = { 'organization': u'', 'department': u'', 'department_head': u'', 'study_form': u'', 'faculty': u'', 'specialty': u'', 'discipline': u'', 'subject': u'', 'module': u'', 'theme': u'', 'year': u'', 'address': u'', 'responsible_person': u'', 'email': u'', 'phone': u'', 'cabinet': u'', 'source': u''} subthemes = list() subtheme = dict() subtheme['quest_type'] = 'single' subtheme['questions'] = [] subtheme['quest_count'] = 0 qti_name = u'' for i, row in enumerate(options_rows): index = i + 1 td = row('td') param = cleanString(' '.join(td[2].stripped_strings)) try: test[MATCHING_NUMBERS_SHORT[index]] = param except: pass if index == 2: qti_name = param elif index == 3: subtheme['name'] = param questions_table = tables[1] question_rows = questions_table('tr') question = dict() quest_count = 0 for ee in question_rows: td = ee('td') td1 = cleanString(u' '.join(td[0].stripped_strings)) td2 = cleanString(u' '.join(td[1].stripped_strings)) td3 = cleanString(u' '.join(td[2].stripped_strings)) if td1 and td2 and td3: if td1 == u'В': question = dict() question['text'] = td3 question['choices'] = [] elif td1 == u'О' or td1 == u'0': choice = dict() choice['correct'] = 'true' if td[2]('b') else 'false' choice['text'] = td3 question['choices'].append(choice) elif question: subtheme['questions'].append(question) quest_count += 1 subtheme['quest_count'] = quest_count subthemes.append(subtheme) test['subthemes'] = subthemes return (qti_name, test) def getTestJsonFromExcel(data_file): # logging.info(u'Начало обработки Excel-файла') wb = XSSFWorkbook(data_file) test = { 'organization': u'', 'department': u'', 'department_head': u'', 'study_form': u'', 'faculty': u'', 'specialty': u'', 'discipline': u'', 'subject': u'', 'module': u'', 'theme': u'', 'year': u'', 'address': u'', 'responsible_person': u'', 'email': u'', 'phone': u'', 'cabinet': u'', 'source': u''} subthemes = dict() sheetBtz = wb.getSheet(u'БТЗ') if sheetBtz is None: # logging.error(u'Лист "БТЗ" не найден') raise Exception(u'Лист "БТЗ" не найден') # Наименование БТЗ row = sheetBtz.getRow(0) cell = row.getCell(1) if cell is None or cell.getCellType() == Cell.CELL_TYPE_BLANK: # logging.error(u'Отсутствует наименование БТЗ') raise Exception(u'Лист "БТЗ", строка 0. Отсутствует наименование БТЗ.') qti_name = cleanString(row.getCell(1).toString()) if qti_name == '': raise Exception(u'Лист "БТЗ", строка 0. Отсутствует наименование БТЗ.') # Получение параметров БТЗ for r in range(1, 17, 1): if r < 16: row = sheetBtz.getRow(r) else: row = sheetBtz.getRow(r + 3) cell = row.getCell(1) if cell != None: test[MATCHING_NUMBERS[r]] = cleanString(row.getCell(1).toString()) # Получение списка тем rows = 100000 for r in range(21, rows, 1): row = sheetBtz.getRow(r) if (row != None): themeIdCell = row.getCell(0) if themeIdCell is None or themeIdCell.getCellType() == Cell.CELL_TYPE_BLANK: raise Exception(u'Лист "БТЗ", строка {}. Отсутствует ИД темы.'.format(r + 1)) themeNameCell = row.getCell(1) if themeNameCell is None or themeNameCell.getCellType() == Cell.CELL_TYPE_BLANK: raise Exception(u'Лист "БТЗ", строка {}. Отсутствует наименование темы.'.format(r + 1)) themeName = cleanString(row.getCell(1).toString()) if themeName == '': raise Exception(u'Лист "БТЗ", строка {}. Отсутствует наименование темы.'.format(r + 1)) subtheme = dict() themeId = int(themeIdCell.getNumericCellValue()) subtheme['id'] = themeId subtheme['name'] = themeName subtheme['quest_type'] = u'none' subtheme['questions'] = [] subtheme['quest_count'] = 0 subthemes[themeId] = subtheme # Получение вопросов sheetQuestions = wb.getSheet(u'Вопросы') if sheetQuestions is None: raise Exception(u'Лист "Вопросы" не найден') question_types = { 1: 'single', 2: 'multiple' # 3: 'compare', # 4: 'fill', # 5: 'sort', # 6: 'classify' } CORRECT_CHOISE = '*' rows = sheetQuestions.getPhysicalNumberOfRows() for r in range(1, rows, 1): row = sheetQuestions.getRow(r) if (row != None): themeIdCell = row.getCell(0) if themeIdCell is None or themeIdCell.getCellType() == Cell.CELL_TYPE_BLANK: raise Exception(u'Лист "Вопросы", строка {}. Отсутствует ИД темы.'.format(r + 1)) # break questionTypeCell = row.getCell(1) if questionTypeCell is None or questionTypeCell.getCellType() == Cell.CELL_TYPE_BLANK: raise Exception(u'Лист "Вопросы", строка {}. Отсутствует тип вопроса.'.format(r + 1)) # continue questionType = formatNumeric(questionTypeCell.getNumericCellValue()) if not question_types.has_key(questionType): raise Exception(u'Лист "Вопросы", строка {}. Некорректный тип вопроса - {}.'.format(r + 1, questionType)) # continue questionTextCell = row.getCell(2) if questionTextCell is None or questionTextCell.getCellType() == Cell.CELL_TYPE_BLANK: raise Exception(u'Лист "Вопросы", строка {}. Отсутствует текст вопроса.'.format(r + 1)) # continue questionText = cleanString(questionTextCell.toString()) if questionText == '': raise Exception(u'Лист "Вопросы", строка {}. Отсутствует текст вопроса.'.format(r + 1)) question = dict() question['type'] = question_types[questionType] question['text'] = questionText question['choices'] = [] # Получение ответов на вопросы has_correct_choice = False count_correct_choices = 0 for c in range(3, 13, 1): cell = row.getCell(c) if cell != None: choice = dict() choice['correct'] = 'false' cellType = cell.getCellType() if cellType == Cell.CELL_TYPE_STRING: cellText = cleanString(cell.toString()) if cellText != '': if cellText.startswith(CORRECT_CHOISE): choice['correct'] = 'true' choice['text'] = cellText.strip(CORRECT_CHOISE) has_correct_choice = True count_correct_choices = count_correct_choices + 1 else: choice['text'] = cellText question['choices'].append(choice) elif cellType == Cell.CELL_TYPE_NUMERIC: choice['text'] = str(formatNumeric(cell.getNumericCellValue())) question['choices'].append(choice) if questionType == 1 and not has_correct_choice: raise Exception(u'Лист "Вопросы", строка {}. Отсутствует правильный (отмеченный знаком "*") вариант ответа на вопрос.'.format(r + 1)) elif questionType == 1 and count_correct_choices > 1: raise Exception(u'Лист "Вопросы", строка {}. Должен быть только один правильный (отмеченный знаком "*") вариант ответа на вопрос.'.format(r + 1)) elif questionType == 2 and not has_correct_choice: raise Exception(u'Лист "Вопросы", строка {}. Отсутствует хотя бы один правильный (отмеченный знаком "*") вариант ответа на вопрос.'.format(r + 1)) elif len(question['choices']) > 0: subthemes[int(themeIdCell.getNumericCellValue())]['questions'].append(question) else: raise Exception(u'Лист "Вопросы", строка {}. Отсутствуют варианты ответа на вопрос.'.format(r + 1)) test['subthemes'] = subthemes.values() return (qti_name, test) def formatNumeric(f): trunc = int(f) if (f - trunc) != 0 : return f else: return trunc def convertTestHtml(data_file, filename, QtiPackage, QtiPackageAttribute, QtiTheme, QtiQuestion, QtiDestructor, param = "med"): TEST_JSON_FUNCTIONS = { "med": getTestJsonFromFile, "mv": getTestJsonFromFile2, 'excel': getTestJsonFromExcel} begin = time.time() # test_json = getTestJsonFromFile(data_file) test = TEST_JSON_FUNCTIONS[param](data_file) test_json = test[1] test_name = filename if param == "med" else test[0] print 'HTML parsing finish', time.time() - begin # print test_json package_version = 1 QtiPackage.setRange('PackageName',test_name) if QtiPackage.count() > 0: QtiPackage.first() packageID = QtiPackage.PackageID package_version = QtiPackage.PackageVersion + 1 QtiPackage.PackageVersion = package_version QtiPackage.update # QtiTheme.setRange('PackageID',QtiPackage.PackageID) # for theme in QtiTheme.iterate(): # QtiQuestion.setRange('ThemeID',theme.ThemeID) # for question in QtiQuestion.iterate(): # QtiDestructor.setRange('QuestionID',question.QuestionID) # QtiDestructor.deleteAll() # QtiQuestion.deleteAll() # QtiTheme.deleteAll() # # QtiPackageAttribute.setRange('PackageID',QtiPackage.PackageID) # QtiPackageAttribute.deleteAll() # # QtiPackage.delete() else: packageID = unicode(uuid4()) QtiPackage.PackageID = packageID QtiPackage.PackageName = test_name QtiPackage.ConversionStatus = 0 QtiPackage.PackageVersion = package_version QtiPackage.DateCreated = datetime.datetime.now() QtiPackage.Status = u'forming' QtiPackage.insert() for rec in MATCHING_ATTRIBUTE_PACKAGE: QtiPackageAttribute.setRange('PackageID', packageID) QtiPackageAttribute.setRange('AttributeName', MATCHING_ATTRIBUTE_PACKAGE[rec]) if QtiPackageAttribute.tryFirst(): QtiPackageAttribute.AttributeValue = test_json[rec][0:254] if test_json[rec] else u'' QtiPackageAttribute.update() else: QtiPackageAttribute.AttributeID = unicode(uuid4()) QtiPackageAttribute.AttributeName = MATCHING_ATTRIBUTE_PACKAGE[rec] QtiPackageAttribute.AttributeValue = test_json[rec][0:254] if test_json[rec] else u'' QtiPackageAttribute.PackageID = packageID QtiPackageAttribute.insert() subthemeCount = 1 for subtheme in test_json['subthemes']: themeName = subtheme['name'] QtiTheme.setRange('PackageID', packageID) if QtiTheme.count() > 0: subthemeCount = QtiTheme.count() + 1 QtiTheme.setRange('ThemeName', subtheme['name']) if QtiTheme.tryFirst(): themeName = u"{} copy".format(subtheme['name']) QtiTheme.clear() QtiTheme.ThemeID = unicode(uuid4()) QtiTheme.ThemeName = themeName QtiTheme.PackageID = packageID QtiTheme.ThemeOrder = subthemeCount QtiTheme.insert() subthemeCount += 1 questionCount = 1 for question in subtheme['questions']: QtiQuestion.QuestionID = unicode(uuid4()) QtiQuestion.QuestionHtml = question['text'] if subtheme['quest_type'] == u'none': questionType = question['type'] else: questionType = subtheme['quest_type'] if questionType == u'single': QtiQuestion.QuestionType = 1 else: QtiQuestion.QuestionType = 2 QtiQuestion.ThemeID = QtiTheme.ThemeID QtiQuestion.QuestionOrder = questionCount questionCount += 1 QtiQuestion.insert() destructorCount = 1 for choice in question['choices']: QtiDestructor.DestructorID = unicode(uuid4()) QtiDestructor.DestructorHtml = choice['text'] QtiDestructor.QuestionID = QtiQuestion.QuestionID QtiDestructor.DestructorOrder = destructorCount destructorCount += 1 if choice['correct'] == 'true': QtiDestructor.IsTrue = True else: QtiDestructor.IsTrue = False QtiDestructor.insert() print 'Database updated', time.time() - begin return packageID def getPrevNextQuestionId(context, variant_questionCur, variant_question_id): u'''Функция поиска предыдущего и следующего вопроса ''' variant_questionCur.setRange("QuestionNumber",1) variant_questionCur.first() first_question_id = variant_questionCur.VariantQuestionID variant_questionCur.setRange("QuestionNumber") variant_questionCur.get(variant_question_id) next_question_id = 'ID_FINISH' if variant_questionCur.navigate('>'): next_question_id = variant_questionCur.VariantQuestionID prev_question_id = first_question_id variant_questionCur.get(variant_question_id) if variant_questionCur.navigate('<'): prev_question_id = variant_questionCur.VariantQuestionID return prev_question_id, next_question_id def getNotAsweredQuestions(context, main=None, add=None, filterinfo=None, session=None, data=None): u'''Получение количества неотвеченных вопросов''' data_json = json.loads(data)['schema'] variant_id = None if data_json.get('description'): variant_id = data_json['description']['variantId'] elif data_json.get('testDescription'): variant_id = data_json['testDescription']['variantId'] count = 0 QtiVariantQuestionCur = QtiVariantQuestionCursor(context) QtiVariantQuestionCur.setRange('VariantID', variant_id) QtiVariantQuestionCur.setRange('HasAnswer', False) count = QtiVariantQuestionCur.count() QtiVariantQuestionCur.setRange('HasAnswer', None) count += QtiVariantQuestionCur.count() QtiVariantQuestionCur.close() message = {"schema": {"@xmlns":"", "count": count}} return XMLJSONConverter.jsonToXml(json.dumps(message)) def getQuestionImg(context, img_id): question_img = '' if img_id: QtiResource = QtiResourceCursor(context) images_json = json.loads(getImageById(QtiResource, img_id, isImage=False)) QtiResource.close() if images_json: image_files = {'image_file': []} file_cursor = fileCursor(context) image_items = images_json['question_img'] if isinstance(image_items, dict): image_items = [image_items] for item in image_items: image_id = item['@id'] if file_cursor.tryGet(image_id): image_files['image_file'].append({'#text': '/'.join(getRelativePathById(context, str(image_id), file_version_id=None))}) else: image_files['image_file'].append({'#text': ''}) file_cursor.close() question_img = image_files return question_img def getTimeJs(time_start, time_end): calNow = Calendar.getInstance() time_start = Date() calNow.setTime(time_start) calEnd = Calendar.getInstance() calEnd.setTime(time_end) #Скрипт, который вставляет счётчик оставшегося времени js = '''javascript:injectCounter(new Date(%s,%s,%s,%s,%s,%s),new Date(%s,%s,%s,%s,%s,%s))'''%\ (calEnd.get(Calendar.YEAR),calEnd.get(Calendar.MONTH),calEnd.get(Calendar.DAY_OF_MONTH),\ calEnd.get(Calendar.HOUR_OF_DAY),calEnd.get(Calendar.MINUTE),calEnd.get(Calendar.SECOND),\ calNow.get(Calendar.YEAR),calNow.get(Calendar.MONTH),calNow.get(Calendar.DAY_OF_MONTH),\ calNow.get(Calendar.HOUR_OF_DAY),calNow.get(Calendar.MINUTE),calNow.get(Calendar.SECOND)) return js def getVariantParams(context, variant_question_id): params = {} QtiVariantQuestion = QtiVariantQuestionCursor(context) QtiVariantQuestion.get(variant_question_id) params['variant_id'] = QtiVariantQuestion.VariantID params['question_id'] = QtiVariantQuestion.QuestionID params['has_answer'] = 1 if QtiVariantQuestion.HasAnswer else 0 QtiVariantQuestion.setRange('VariantID', params['variant_id']) QtiVariantQuestion.orderBy('QuestionNumber') params['question_number'] = QtiVariantQuestion.QuestionNumber params['question_count'] = QtiVariantQuestion.count() params['prev_variant_question_id'], params['next_variant_question_id'] = getPrevNextQuestionId(context, QtiVariantQuestion, variant_question_id) QtiVariantQuestion.close() return params def getQuestionFields(context, question_id): fields = {} QtiQuestion = QtiQuestionCursor(context) QtiQuestion.get(question_id) fields['question_html'] = QtiQuestion.QuestionHtml if QtiQuestion.QuestionHtml is not None else '' fields['question_html2'] = QtiQuestion.QuestionHtml2 if QtiQuestion.QuestionHtml2 is not None else '' fields['img_id'] = QtiQuestion.QuestionImg QtiQuestion.close() return fields def getTestingAlgorithm(context, variant_id): testing_algorithm = u'Тестирование' TVA = TestingVariantAssignCursor(context) TVA.get(variant_id) if TVA.AlgorithmTestingID == 1: testing_algorithm = u'Тестирование для самоконтроля' elif TVA.AlgorithmTestingID == 2: testing_algorithm = u'Аудиторное тестирование' TVA.close() return testing_algorithm def getVariantAnswerParams(context, variant_id): params = {} QtiVariantAnswer = QtiVariantAnswerCursor(context) QtiVariantAnswer.setRange('VariantID', variant_id) QtiVariantAnswer.first() #Тестирование завершилось или нет params['timeEnd'] = QtiVariantAnswer.TimeEnd params['timeout'] = '1' if QtiVariantAnswer.TimeEnd < Date() else '0' if QtiVariantAnswer.TimeFinish is None and QtiVariantAnswer.TimeEnd < Date(): params['testingStatus'] = '2' elif QtiVariantAnswer.TimeFinish is not None: params['testingStatus'] = '1' else: params['testingStatus'] = '0' QtiVariantAnswer.close() return params