def add_question_to_db(self, text_message, message): day = datetime.now().strftime("%d/%m/%Y, %H:%M:%S") new_question = Question(message_id=message.id, message_text=text_message, message_date=day) new_question.message = message self._session.add(new_question) self._session.commit() return new_question
def get_question_id_message(self, message): if message.is_replier_message: question_replier = QuestionReplier.get_by_message_id( self._session, message.id) question = Question.get_question_id(self._session, question_replier.question_id) else: question = Question.get_question(self._session, message.id) return question.id
def makeTestQuestions(): # create some questions to render st = Set.createNew("Test Set", 1) q1 = Question("Hello?", ["foo", "bar", "baz", "quux"], "c", st, 1) q2 = Question("'Goodbye' is a [...].", ["word", "tar", "taz", "tuux"], "a", st, 1) q3 = Question("Auf wiedersehen? // Goodbye?", ["me", "you", "they", "I"], "b", st, 1) qs = [q1, q2, q3] return qs
def testParseAndRead(self): cls = Class.createNew("MyClass") mama = Student.createNew("Bjornstad", "Jennifer", "1", "9A2DC6", "*****@*****.**", cls) soren = Student.createNew("Bjornstad", "Soren", "2", "9A2D9C", "*****@*****.**", cls) st = Set.createNew("fooset", 1) q1 = Question(u"das Buch", ['aa', 'bb', 'cc', 'dd'], 'b', st, 1) q2 = Question(u"die Lieblingsfarbe", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q3 = Question(u"die Tür", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q4 = Question(u"der Familienname", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q5 = Question(u"das Auto", ['aa', 'bb', 'cc', 'dd'], 'c', st, 1) q6 = Question(u"der Ball", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q7 = Question(u"der Stift", ['aa', 'bb', 'cc', 'dd'], 'b', st, 1) q8 = Question(u"die Tafel", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q9 = Question(u"der Deutschkurs", ['aa', 'bb', 'cc', 'dd'], 'c', st, 1) q10 = Question(u"der Fußball", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) ql = [q1, q2, q3, q4, q5, q6, q7, q8, q9, q10] qPickle = pickle.dumps(ql) # fake a quiz into the quizzes table, as it's quite complicated to do # otherwise q = '''INSERT INTO quizzes (zid, cid, qPickle, newNum, revNum, newSetNames, seq, resultsFlag, datestamp, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''' vals = (1, cls.getCid(), sqlite3.Binary(qPickle), 8, 2, "Foobar", 1, 0, '2015-08-01', '') d.inter.exQuery(q, vals) with open('tests/resources/tpStatsParse/mamasoren1.html') as f: data = f.read() responses = parseHtmlString(data) for resp in responses: writeResults(resp, cls, 1) for resp in responses: writeResults(resp, cls, 1, suppressCheck=True) # check the check :) c = d.inter.exQuery("SELECT * FROM results") answers = json.loads(c.fetchall()[0][3]) assert answers[0:2] == [[1, 'a'], [2, 'd']] # now try to read it back assert str(readResults(soren, 1)) == "[(1, u'a', 'b'), (2, u'b', 'd'),"\ " (3, u'c', 'd'), (4, u'd', 'a'), (5, u'b', 'c')," \ " (6, u'd', 'd'), (7, u'c', 'b'), (8, u'c', 'd')," \ " (9, u'd', 'c'), (10, u'b', 'a')]" # delete delResults(1) assert readResults(soren, 1) is None assert readResults(mama, 1) is None
def reply_question(self, event): """Handle messages that are sent to existing thread""" message = Message.get_message(self._session, event.thread_id, event.user) if self._is_resolved(message): self.send_closed_message(event, message) return if message.is_replier_message: question_replier = QuestionReplier.get_by_message_id( self._session, message_id=message.id) question = Question.get_question_id( self._session, question_id=question_replier.question_id) if not self.is_correct_replier(event.bot, message): return channel_id, message_id, replier_question_id = self.get_user_info_to_reply( message) self.delete_previous_scheduled_messages(event.bot, event.channel, replier_question_id, 3) event.bot.delete_message_in_history( event.channel, event.thread_id, [ WANT_ANSWER_QUESTION, WANT_CONTINUE_RESPONDING, M_ASKS_REPLIER_CLOSE_QUESTION ]) attachments_type = '' else: question = Question.get_question(self._session, message_id=message.id) channel_id, message_id, _ = self.get_expert_info_to_reply(message) attachments_type = ATTACHMENTS_keep_answering_or_not event.bot.delete_message_in_history(event.channel, event.thread_id, [QUESTION_ANSWERED]) message_txt = WANT_CONTINUE_RESPONDING # if this question has continous messages if question.new_message_user == 1: # add some text as alert in order to avoid this behaviour again event.bot.post_message( channel_id, USER_ALERT + question.message_text + '\n\n' + USER_ALERT_2, message_id, attachments_type) self._session.query(Question).filter_by(id=question.id).update( {'new_message_user': 0}) event.bot.post_message(channel_id, event.message_text, message_id) if attachments_type != '': event.bot.post_message(channel_id, message_txt, message_id, attachments_type) self.handle_scheduled_message(event, message)
def update_message_text(self, message_text, last_message, message): """Add a line for continuos messages in database.""" question = Question.get_question(self._session, last_message.id) updated_text = question.message_text + '\n' + message_text self._session.query(Question).filter_by( message_id=last_message.id).update({ "message_id": message.id, "message_text": updated_text, 'new_message_user': 1 })
def _is_resolved(self, message): """Check whether the question has been marked as resolved or not""" if message.is_replier_message: question_replier = QuestionReplier.get_by_message_id( self._session, message.id) question_is_resolved = question_replier.question.is_resolved else: question = Question.get_question(self._session, message.id) question_is_resolved = question.is_resolved return question_is_resolved
def testSaveRestore(self): cls = Class.createNew("TestClass") st = Set.createNew("TestSet", 1) st2 = Set.createNew("RevSet", 2) sl = [st, st2] q = Question("Hello?", ["foo", "bar", "baz", "quux"], "c", st, 1) #"new" q2 = Question("Goodbye?", ["1", "2", "3", "4"], "d", st, 2) # "new" q3 = Question("Sayonara.", ["1", "2", "3", "4"], "c", st2, 2) # "review" ql = [q, q2, q3] quizNum = 1 date = '2015-08-05' # use a datetime obj later on, format on display newNum = 2 #/ of course do this by checking list length in revNum = 1 #\ the Quiz object's lists later on newSet = st qPickle = pickle.dumps(ql) unPickledQl = pickle.loads(qPickle)
def get_expert_info_to_reply(self, message): if message.is_replier_message: question_replier = QuestionReplier.get_by_message_id( self._session, message.id) else: question = Question.get_question(self._session, message.id) question_replier = next((x for x in question.question_repliers if x.reply_status_id == 3)) user_id = question_replier.message.slack_user_id message_id = question_replier.message.slack_message_id channel_id = question_replier.replier.channel_id return channel_id, message_id, question_replier.id
def testWrongQuizError(self): # dupe of first part of above test, except with the wrong questions cls = Class.createNew("MyClass") mama = Student.createNew("Bjornstad", "Jennifer", "1", "9A2DC6", "*****@*****.**", cls) soren = Student.createNew("Bjornstad", "Soren", "2", "9A2D9C", "*****@*****.**", cls) st = Set.createNew("fooset", 1) q1 = Question(u"das Buch", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q2 = Question(u"die Lieblingsfarbe", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q3 = Question(u"die Tür", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q4 = Question(u"der Familienname", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q5 = Question(u"das Auto", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q6 = Question(u"der Ball", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q7 = Question(u"der Stift", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q8 = Question(u"die Tafel", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q9 = Question(u"this is stupid", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q10 = Question(u"something wrong", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) ql = [q1, q2, q3, q4, q5, q6, q7, q8, q9, q10] qPickle = pickle.dumps(ql) # fake a quiz into the quizzes table, as it's quite complicated to do # otherwise q = '''INSERT INTO quizzes (zid, cid, qPickle, newNum, revNum, newSetNames, seq, resultsFlag, datestamp, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''' vals = (1, cls.getCid(), sqlite3.Binary(qPickle), 8, 2, "Foobar", 1, 0, '2015-08-01', '') d.inter.exQuery(q, vals) with open('tests/resources/tpStatsParse/mamasoren1.html') as f: data = f.read() responses = parseHtmlString(data) with self.assertRaises(WrongQuizError) as garbage: for resp in responses: writeResults(resp, cls, 1)
def get_user_info_to_reply(self, message): if message.is_replier_message: question_replier = QuestionReplier.get_by_message_id( self._session, message.id) user_id = question_replier.question.message.slack_user_id message_id = question_replier.question.message.slack_message_id question_replier_id = question_replier.id else: question = Question.get_question(self._session, message.id) user_id = question.message.slack_user_id message_id = question.message.slack_message_id question_replier_id = None replier = Replier.get_replier_by_slack_id(self._session, user_id) channel_id = replier.channel_id return channel_id, message_id, question_replier_id
def testRTFRender(self): fname = 'testfile.rtf' against_fname = 'tests/resources/test_format_against.rtf' st = Set.createNew("Test Set", 2) questions = [Question("Hello [...]?", ["foo", "bar", "baz", "quux"], "c", st, 1)] renderRTF(questions, fname) try: f = open(fname) except IOError: self.assertTrue(False), "File was not created successfully" else: self.assertTrue(f.readlines()), "No data in file" assert filecmp.cmp(fname, against_fname), \ "Output different from saved correct output file" finally: f.close() os.remove(fname)
def forward_message(self, event, replier_level_id=MIN_LEVEL_ID): """Send the question to everyone on MIN_LEVEL_ID and save the info of any threads to database""" user = Replier.get_replier_by_channel_id(self._session, event.channel) message = Message.get_last_message_of_user(self._session, slack_user_id=user.slack_id, is_replier=False) question = Question.get_question(self._session, message_id=message.id) question_repliers = self.forward_first_message(event.bot, question.message_text, replier_level_id, user.slack_id) db_question_repliers = self.add_question_repliers_to_db( question, question_repliers) timeout = self.get_suitable_timeout() self.time_to_level_up(event.bot, db_question_repliers, timeout)
def save_message_history(self, bot, question_replier_id, next_level=False): """Save history of messages to database just when it will go to the next level. :type question_replier_id: int :type next_level: bool :rtype: str """ question_id = self._session.query(QuestionReplier).filter( QuestionReplier.id == question_replier_id).first().question_id question = Question.get_question_id(self._session, question_id) original_message = question.message slack_response = bot.get_history(original_message.slack_user_id, original_message.slack_message_id) history_list = self.handle_thread_messages(slack_response) history_text = self.handle_history(history_list, next_level=next_level) if not next_level: save_message = HistoryMessages(question_id=question_id, history_text=history_text) self._session.add(save_message) return history_text
def testFormatter(self): # set up a quiz with results cls = db.classes.Class.createNew("TestClass (no pun intended)") st = Set.createNew("fooset", 1) s = Student.createNew("Bjornstad", "Soren", "2", "c56al", "*****@*****.**", cls) s2 = Student.createNew("Almzead", "Maud,Her", "5", "55655", "*****@*****.**", cls) q1 = Question(u"das Buch", ['hourglass', 'book', 'Bach', 'cat'], 'b', st, 1) q2 = Question( u"die Lieblingsfarbe", ['favorite class', 'computer', 'color', 'favorite color'], 'd', st, 1) q3 = Question(u"die Tür", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q4 = Question(u"der Familienname", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) q5 = Question(u"das Auto", ['aa', 'bb', 'cc', 'dd'], 'c', st, 1) q6 = Question(u"der Ball", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q7 = Question(u"der Stift", ['aa', 'bb', 'cc', 'dd'], 'b', st, 1) q8 = Question(u"die Tafel", ['aa', 'bb', 'cc', 'dd'], 'd', st, 1) q9 = Question(u"der Deutschkurs", ['aa', 'bb', 'cc', 'dd'], 'c', st, 1) q10 = Question(u"der Fußball", ['aa', 'bb', 'cc', 'dd'], 'a', st, 1) ql = [q1, q2, q3, q4, q5, q6, q7, q8, q9, q10] qPickle = pickle.dumps(ql) # fake a quiz into the quizzes table, as it's quite complicated to do # otherwise q = '''INSERT INTO quizzes (zid, cid, qPickle, newNum, revNum, newSetNames, seq, resultsFlag, datestamp, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''' vals = (1, cls.getCid(), Binary(qPickle), 8, 2, "Foobar", 1, 0, '2015-08-01', '') d.inter.exQuery(q, vals) with open('tests/resources/tpStatsParse/mamasoren1.html') as f: data = f.read() responses = parseHtmlString(data) # we don't have a student with an ID of 1 in the db yet, so there's # nothing to match the one in the responses string with with self.assertRaises(MissingStudentError) as e: for resp in responses: writeResults(resp, cls, 1) # now fix that. s3 = Student.createNew("Bjornstad", "Jennifer", "1", "55655", "*****@*****.**", cls) for resp in responses: writeResults(resp, cls, 1) # now try some formatting things. optsDict = { 'fromName': 'Testy Tester', 'fromAddr': '*****@*****.**', 'subject': '[CQM $c] Quiz $n for $s: $r/$t ($p%)', 'body': 'Table of scores:\n$a\n\nAnnotated quiz:\n$Q\n\n' 'Original quiz:\n$q\n\nThe class average was ' '$R/$t ($P%). You won $$2 from your scores! ' 'To clarify, $$roll this.', 'hostname': 'mail.messagingengine.com', 'port': '465', 'ssl': 'SSL/TLS', 'username': '******', 'password': '******', #TODO: make sure this username/pass doesn't stay in here! } em = EmailManager(optsDict, cls, 1) assert em._expandFormatStr(optsDict['body'], s3, True).strip() == \ correctFormatStrTest.strip() assert em._expandFormatStr(optsDict['subject'], s3, False).strip() == \ correctFormatStrSubjTest.strip()
def _saveQuestion(self): """ Save the current question to the database, displaying an appropriate error if necessary. Return: - 2 if a new question was created. - 1 if an old question was updated. - 0 if nothing was changed due to an error. """ def saveError(msg): utils.errorBox(msg, "Invalid question") def handleError(err): """ Rewrite error messages returned by the db layer to the more specific context here, as appropriate. Not all errors are checked for, as some of them can only come up in this context due to a programming error. Returns true if error handled, False if we passed the db's error on to the user. """ msg = '' err = str(err) if "must have 2-5 answers" in err: msg = "You must enter at least two answer choices." elif "may not be blank" in err: msg = "You may not leave answer choices blank unless they " \ "are at the end." elif "correct answer must be specified" in err: msg = "You must choose a correct answer." elif "specified must be an answer choice" in err: msg = "The answer you have selected as correct does not have " \ "any text." elif "The question must have some text" in err: msg = "Please enter some text in the question field." elif "different choices cannot have the same text" in err: msg = "You may not use the same text for two different " \ "answers." if msg: utils.errorBox(msg, "Invalid question") return True else: # this shouldn't happen unless we screwed up utils.errorBox("Oops! The database returned the following " \ "error:\n\n%s" % qfe, "Save Error") return False sf = self.form ansDict = { 'a': unicode(self.ansChoices[0].text()), 'b': unicode(self.ansChoices[1].text()), 'c': unicode(self.ansChoices[2].text()), 'd': unicode(self.ansChoices[3].text()), 'e': unicode(self.ansChoices[4].text()) } question = unicode(sf.questionBox.toPlainText()) answersList = [unicode(i.text()) for i in self.ansChoices if i.text()] correctAnswer = unicode(sf.correctAnswerCombo.currentText()).lower() st = self._currentSet() order = sf.questionList.row( sf.questionList.findItems(question, QtCore.Qt.MatchExactly)[0]) # Make sure there are no gaps in answer choices; this can't be # detected on the db side because we skip over all blanks when filling # the answer list. reachedEnd = False for i in self.ansChoices: if i.text() and reachedEnd: saveError("You may not leave answer choices blank unless " \ "they are at the end.") return False elif not i.text(): reachedEnd = True try: if self.currentQid is not None: # update the existing question nq = self.qm.byId(self.currentQid) nq.prevalidate() # strip whitespace from question. If we don't update the text # box as well, the dialog goes haywire if it gets changed and # we try to look at a different question. question = question.strip() self.form.questionBox.setPlainText(question) nq.setQuestion(question) nq.setAnswersList(answersList) nq.setCorrectAnswer(correctAnswer) retVal = 1 # order and set can't have changed from this operation else: # create a new one nq = Question(question, answersList, correctAnswer, st, order) retVal = 2 except DuplicateError: saveError("You already have a question with that text.") return False except QuestionFormatError as qfe: handleError(qfe) return False self.qm.update() self.currentQid = self.qm.byName(question).getQid() self._updateQuestionTotal() self._enableList() return retVal
def testDeletions(self): c = Class.createNew("Greta and TI 101") st = Set.createNew("Test Set", 3) q = Question("Hello?", ["foo", "bar", "baz", "quux"], "c", st, 1) q2 = Question("Goodbye?", ["1", "2", "3", "4"], "d", st, 2) # test question-set cascade assert db.questions.getById(q.getQid()) is not None assert db.questions.getById(q2.getQid()) is not None st.delete() assert db.questions.getById(q.getQid()) is None assert db.questions.getById(q2.getQid()) is None # test student-class cascade s = db.students.Student.createNew("Almzead", "Maud", "1", "ff8836", "*****@*****.**", c) tpid = s.getTpid() assert db.students.findStudentByTpid(tpid, c) is not None db.classes.deleteClass(c.getName()) assert db.students.findStudentByTpid(tpid, c) is None # test history/quiz-class cascade # (much code, with checks removed, from test_genquiz) def do(): cls = db.classes.Class.createNew("Test Class") st = db.sets.Set.createNew("Test Set", 1) q = db.questions.Question("What is the answer?", ["foo", "bar", "baz", "42"], "d", st, 1) q4 = db.questions.Question("What is the answer ?", ["foo", "bar", "baz", "42"], "d", st, 4) st2 = db.sets.Set.createNew("Test Set", 1) q2 = db.questions.Question("What is the answer to this?", ["foo", "bar", "baz", "42"], "c", st2, 2) q3 = db.questions.Question("What is the answer to this question?", ["foo", "bar", "baz", "42"], "c", st2, 3) quiz = Quiz(cls) quiz.addNewSet(st) quiz.addNewSet(st2) quiz.setNewQuestions(1) quiz.setRevQuestions(2) quiz._fillNewItems() sr = SetRescheduler() quiz.newQ[0].reschedule(3, sr) quiz.newQ[1].reschedule(2, sr) sr.runResched() d.inter.exQuery('UPDATE classes SET setsUsed=? WHERE cid=?', (7, cls.getCid())) d.inter.forceSave() quiz.resetNewSets() quiz.addNewSet(st2) quiz.finishSetup() quiz._randNew() quiz._randRev() qPrev = quiz.generate() oldSetsUsed = getSetsUsed(cls) quiz.rewriteSchedule() return cls, st # We now have a history entry and a quizzes entry. cls, st = do() quizHistoryItem = db.history.historyForClass(cls)[0] c = d.inter.exQuery('SELECT * FROM history WHERE sid=? AND cid=?', (st.getSid(), cls.getCid())) assert quizHistoryItem assert c.fetchall() db.classes.deleteClass(cls.getName()) with self.assertRaises(IndexError): quizHistoryItem = db.history.historyForClass(cls)[0] c = d.inter.exQuery('SELECT * FROM history WHERE sid=? AND cid=?', (st.getSid(), cls.getCid())) assert not c.fetchall()