예제 #1
0
 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
예제 #2
0
 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
예제 #4
0
    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
예제 #5
0
    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)
예제 #6
0
 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
         })
예제 #7
0
    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)
예제 #9
0
 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
예제 #10
0
    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)
예제 #11
0
 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)
예제 #13
0
    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)
예제 #14
0
    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()
예제 #16
0
    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
예제 #17
0
    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()