Example #1
0
 def _allowed_to_participate(self, message):
     if message.reporter:
         iavi_reporter = IaviReporter.objects.get(pk=message.reporter.pk)
         if iavi_reporter.pin:
             return True
         else:
             message.respond(_(strings["rejection_no_pin"], get_language_code(message.persistant_connection)))
     else:
         message.respond(_(strings["rejection_unknown_user"], get_language_code(message.persistant_connection)))
     return False
Example #2
0
 def _process_pin(self, message):
     language = get_language_code(message.persistant_connection)
     incoming_pin = message.text.strip()
     reporter = IaviReporter.objects.get(pk=message.reporter.pk)
     if self.pending_pins[reporter.pk]:
         # this means it has already been set once 
         # check if they are equal and if so save
         pending_pin = self.pending_pins.pop(reporter.pk)
         if incoming_pin == pending_pin:
             # success!
             reporter.pin = pending_pin
             reporter.save()
             message.respond(_(strings["pin_set"], language))
         else:
             # oops they didn't match.  send a failure string
             message.respond(_(strings["pin_mismatch"], language) % {"alias": reporter.study_id})
             # put an empty value back in the list of pins
             self.pending_pins[reporter.pk] = None
     else:
         # this is their first try.  make sure 
         # it's 4 numeric digits and if so ask for confirmation
         if re.match(r"^(\d{4})$", incoming_pin):
             self.pending_pins[reporter.pk] = incoming_pin
             message.respond(_(strings["pin_request_again"], language))
         else:
             # bad format.  send a failure string and prompt again
             message.respond(_(strings["bad_pin_format"], language) % {"alias": reporter.study_id})
     return True
Example #3
0
 def _initiate_tree_sequence(self, user, initiator=None):
     user_conn = user.connection()
     if user_conn:
         db_backend = user_conn.backend
         # we need to get the real backend from the router 
         # to properly send it 
         real_backend = self.router.get_backend(db_backend.slug)
         if real_backend:
             connection = Connection(real_backend, user_conn.identity)
             text = self._get_tree_sequence(user)
             if not text:
                 return _(strings["unknown_survey_location"], get_language_code(user.connection)) % ({"location":user.location, "alias":user.study_id})
             else:
                 # first ask the tree app to end any sessions it has open
                 if self.tree_app:
                     self.tree_app.end_sessions(user_conn)
                 if initiator:
                     # if this was initiated by someone else
                     # create an entry for this so they can be
                     # notified upon completion, and also so 
                     # we can ignore the data
                     TestSession.objects.create(initiator=initiator, tester=user,
                                                status="A")
                 start_msg = Message(connection, text)
                 self.router.incoming(start_msg)
                 return
         else:
             error = "Can't find backend %s.  Messages will not be sent" % connection.backend.slug
             self.error(error)
             return error
     else:
         error = "Can't find connection %s.  Messages will not be sent" % user_conn
         self.error(error)
         return error
Example #4
0
    def actions(self, *args, **kwargs):
        message = args[0]
        form_entry = args[1]

        # we'll be using the language in all our responses so
        # keep it handy
        language = get_language_code(message.persistant_connection)
        
        if form_entry.form.code.abbreviation == "register": 
            # todo: registration
            alias = form_entry.reg_data["alias"]
            language = form_entry.reg_data["language"]
            location = form_entry.reg_data["location"]
            real_time = form_entry.reg_data["time"]
            study_id =  form_entry.reg_data["study_id"]
            
            # create the reporter object for this person 
            reporter = IaviReporter(alias=alias, language=language, location=location, registered=message.date)
            reporter.save()
            
            # create the study participant for this too.  Assume they're starting
            # tomorrow and don't set a stop date.  
            start_date = (datetime.today() + timedelta(days=1)).date()
            participant = StudyParticipant.objects.create(reporter=reporter, 
                                                          start_date = start_date,
                                                          notification_time = real_time)
            
            # also attach the reporter to the connection 
            message.persistant_connection.reporter=reporter
            message.persistant_connection.save()
            
            message.respond(_(strings["registration_complete"], language) % {"alias": study_id })
            
            # also send the PIN request and add this user to the 
            # pending pins
            self.app.pending_pins[reporter.pk] = None
            message.respond(_(strings["pin_request"], language))

        elif form_entry.form.code.abbreviation == "test":
            
            # initiate the tree sequence for the user we set 
            # during validation
            user = form_entry.test_data["reporter"]
            errors = self.app._initiate_tree_sequence(user, message.persistant_connection)
            if errors:
                message.respond(errors)
            else:
                message.respond(_(strings["test_initiated"], language) % {"alias" : form_entry.test_data["study_id"]})
Example #5
0
 def _send_question(self, session, msg=None):
     '''Sends the next question in the session, if there is one''' 
     state = session.state
     if state and state.question:
         response = _(state.question.text, get_language_code(session.connection))
         self.info("Sending: %s" % response)
         if msg:
             msg.respond(response)
         else:
             # we need to get the real backend from the router 
             # to properly send it 
             real_backend = self.router.get_backend(session.connection.backend.slug)
             if real_backend:
                 connection = Connection(real_backend, session.connection.identity)
                 outgoing_msg = Message(connection, response)
                 self.router.outgoing(outgoing_msg)
             else: 
                 # todo: do we want to fail more loudly than this?
                 error = "Can't find backend %s.  Messages will not be sent" % connection.backend.slug
                 self.error(error)
Example #6
0
 def _initiate_tree_sequence(self, user, initiator=None):
     user_conn = user.connection()
     if user_conn:
         tree= self._get_tree(user)
         if not tree:
             return _(strings["unknown_survey_location"], get_language_code(user.connection)) % ({"location":user.location, "alias":user.study_id})
         if initiator:
             # if this was initiated by someone else
             # create an entry for this so they can be
             # notified upon completion, and also so 
             # we can ignore the data
             TestSession.objects.create(initiator=initiator, tester=user,
                                        status="A")
         if self.tree_app:
             self.tree_app.start_tree(tree, user_conn)
         
     else:
         error = "Can't find connection %s.  Messages will not be sent" % user_conn
         self.error(error)
         return error
Example #7
0
 def handle(self, msg):
     # if this caller doesn't have a session attribute,
     # they're not currently answering a question tree, so
     # just search for triggers and return
     sessions = Session.objects.all().filter(state__isnull=False)\
         .filter(connection=msg.persistant_connection)
     if not sessions:
         try:
             tree = Tree.objects.get(trigger=msg.text)
             # start a new session for this person and save it
             session = Session(connection=msg.persistant_connection, 
                               tree=tree, state=tree.root_state, num_tries=0)
             session.save()
             self.debug("session %s saved" % session)
             
             # also notify any session listeners of this
             # so they can do their thing
             if self.session_listeners.has_key(tree.trigger):
                 for func in self.session_listeners[tree.trigger]:
                     func(session, False)
         
         # no trigger found? no big deal. the
         # message is probably for another app
         except Tree.DoesNotExist:
             return False
     
     # the caller is part-way though a question
     # tree, so check their answer and respond
     else:
         session = sessions[0]
         state = session.state
         
         self.debug(state)
         # loop through all transitions starting with  
         # this state and try each one depending on the type
         # this will be a greedy algorithm and NOT safe if 
         # multiple transitions can match the same answer
         transitions = Transition.objects.filter(current_state=state)
         found_transition = None
         for transition in transitions:
             if self.matches(transition.answer, msg):
                 found_transition = transition
                 break
         
         # the number of tries they have to get out of this state
         # if empty there is no limit.  When the num_retries is hit
         # a user's session will be terminated.
 
         # not a valid answer, so remind
         # the user of the valid options.
         if not found_transition:
             transitions = Transition.objects.filter(current_state=state)
             # there are no defined answers.  therefore there are no more questions to ask 
             if len(transitions) == 0:
                 # send back some precanned response
                 msg.respond(self.last_message)
                 # end the connection so the caller can start a new session
                 self._end_session(session)
                 return
             else:
                 # send them some hints about how to respond
                 if state.question.error_response:
                     response = (_(state.question.error_response, get_language_code(session.connection)))
                     if "%(answer)s" in response:
                         response = response % ({"answer" : msg.text})
                 else:
                     flat_answers = " or ".join([trans.answer.helper_text() for trans in transitions])
                     translated_answers = _(flat_answers, get_language_code(session.connection))
                     response = _('"%(answer)s" is not a valid answer. You must enter %(hint)s', 
                                  get_language_code(session.connection))% ({"answer" : msg.text, "hint": translated_answers})
                      
                 msg.respond(response)
                 
                 # update the number of times the user has tried
                 # to answer this.  If they have reached the 
                 # maximum allowed then end their session and
                 # send them an error message.
                 session.num_tries = session.num_tries + 1
                 if state.num_retries and session.num_tries >= state.num_retries:
                     session.state = None
                     msg.respond(_("Sorry, invalid answer %(retries)s times. Your session will now end. Please try again later.",
                                   get_language_code(session.connection)) % {"retries": session.num_tries })
                     
                 session.save()
                 return True
         
         # create an entry for this response
         # first have to know what sequence number to insert
         ids = Entry.objects.all().filter(session=session).order_by('sequence_id').values_list('sequence_id', flat=True)
         if ids:
             # not sure why pop() isn't allowed...
             sequence = ids[len(ids) -1] + 1
         else:
             sequence = 1
         entry = Entry(session=session,sequence_id=sequence,transition=found_transition,text=msg.text)
         entry.save()
         self.debug("entry %s saved" % entry)
             
         # advance to the next question, or remove
         # this caller's state if there are no more
         
         # this might be "None" but that's ok, it will be the 
         # equivalent of ending the session
         session.state = found_transition.next_state
         session.num_tries = 0
         session.save()
         
         # if this was the last message, end the session, 
         # and also check if the tree has a defined 
         # completion text and if so send it
         if not session.state:
             self._end_session(session)
             if session.tree.completion_text:
                 msg.respond(_(session.tree.completion_text, get_language_code(session.connection)))
             
     # if there is a next question ready to ask
     # (and this includes THE FIRST), send it along
     sessions = Session.objects.all().filter(state__isnull=False).filter(connection=msg.persistant_connection)
     if sessions:
         state = sessions[0].state
         if state.question:
             msg.respond(_(state.question.text, get_language_code(sessions[0].connection)))
             self.info(_(state.question.text, get_language_code(sessions[0].connection)))
     
     # if we haven't returned long before now, we're
     # long committed to dealing with this message
     return True
Example #8
0
 def _handle_session(self, session, is_ending, klass):
     self.debug("%s session: %s" % (klass, session))
     
     # get the reporter object
     reporter = session.connection.reporter
     iavi_reporter = IaviReporter.objects.get(pk=reporter.pk)
         
     if not is_ending:
         # check if the reporter has an active test session
         # and if so, link it. Otherwise treat it normally
         try: 
             test_session = TestSession.objects.get(tester=iavi_reporter, 
                                               status="A")
             test_session.tree_session = session
             test_session.save()
         except TestSession.DoesNotExist:
             # not an error, just means this wasn't a test
             # create a new report for this
             klass.objects.create(reporter=iavi_reporter, 
                              started=session.start_date, 
                              session=session, status="A")
         
     else:
         # if we have a test session mark the status and save
         try:
             test_session = TestSession.objects.get(tree_session=session)
             if session.canceled:
                 test_session.status = "F"
                 response = _(strings["test_fail"], get_language_code(test_session.initiator)) % ({"alias": iavi_reporter.study_id})
             else:
                 test_session.status = "P"
                 response = _(strings["test_pass"], get_language_code(test_session.initiator)) % ({"alias": iavi_reporter.study_id})
             
             test_session.save()
             
             # also have to initiate a callback to the 
             # original person who initiated the test
             db_backend = test_session.initiator.backend
             real_backend = self.router.get_backend(db_backend.slug)
             if real_backend:
                 real_connection = Connection(real_backend, test_session.initiator.identity)
                 response_msg = Message(real_connection, response)
                 self.router.outgoing(response_msg)
             else:
                 error = "Can't find backend %s.  Messages will not be sent" % connection.backend.slug
                 self.error(error)
         except TestSession.DoesNotExist:
             # not a big deal.  it wasn't a test.  
             # if we have a report
             # update the data and save
             try:
                 report = klass.objects.get(session=session)
                 for entry in session.entry_set.all():
                     answer = entry.transition.answer
                     column = self._get_column(entry.transition.current_state)
                     if column:
                         clean_answer = self._get_clean_answer(answer, entry.text)
                         setattr(report, column, clean_answer)
                 report.completed = datetime.now()
                 if session.canceled:
                     report.status = "C"
                 else:
                     report.status = "F"
                 report.save()
             except klass.DoesNotExist:
                 # oops, not sure how this could happen, but we don't
                 # want to puke
                 self.error("No report found for session %s" % session)
Example #9
0
 def handle (self, message):
     # there are three things this app deals with primarily:
     # registration, pin setup, and testing
     
     # but we'll also use it to circumvent some logic in the tree
     # app with a few custom codes.
     if message.text.lower() == "iavi uganda" or message.text.lower() == "iavi kenya":
         # if they are allowed to participate, return false so 
         # that the message propagates to the tree app.  If they
         # aren't this will come back as handled and the tree app
         # will never see it.  
         # ASSUMES ORDERING OF APPS AND THAT THIS IS BEFORE TREE
         return not self._allowed_to_participate(message)
     
     # we'll be using the language in all our responses so
     # keep it handy
     language = get_language_code(message.persistant_connection)
     
     # check pin conditions and process if they match
     if message.reporter and message.reporter.pk in self.pending_pins:
         return self._process_pin(message)
         
     # registration block
     # first make sure the string starts and ends with the *# - #* combination
     match = re.match(r"^\*\#(.*?)\#\*$", message.text)
     if match:
         self.info("Message matches! %s", message)
         body_groups = match.groups()[0].split("#")
         if len(body_groups)== 3 and body_groups[0] == "8377":
             # this is the testing format
             # this is the (extremely ugly) format of testing
             # *#8377#<Site Number>#<Last 4 Digits of Participant ID>#*
             # TODO: implement testing
             
             code, site, id = body_groups
             alias = IaviReporter.get_alias(site, id)
             try: 
                 # lookup the user in question and initiate the tree
                 # sequence for them.  If there are errors, respond
                 # with them
                 user = IaviReporter.objects.get(alias=alias)
                 
                 errors = self._initiate_tree_sequence(user, message.persistant_connection)
                 if errors:
                     message.respond(errors)
             except IaviReporter.DoesNotExist:
                 message.respond(_(strings["unknown_user"], language) % {"alias":id})
             return True
         
         else:
             # assume this is the registration format
             # this is the (extremely ugly) format of registration
             # time is optional
             # *#<Country/Language Group>#<Site Number>#<Last 3 Digits of Participant ID>#<time?>#*
             if len(body_groups) == 3:
                 language, site, id = body_groups
                 study_time = "1600"
             elif len(body_groups) == 4:
                 language, site, id, study_time = body_groups
             else:
                 message.respond(_(strings["unknown_format"], get_language_code(message.persistant_connection)))
             
             # validate the format of the id, existence of location
             if not re.match(r"^\d{3}$", id):
                 message.respond(_(strings["id_format"], get_language_code(message.persistant_connection)) % {"alias" : id})
                 return True
             try:
                 location = Location.objects.get(code=site)
             except Location.DoesNotExist:
                 message.respond(_(strings["unknown_location"], get_language_code(message.persistant_connection)) % {"alias" : id, "location" : site})
                 return True
             
             # TODO: validate the language
             
             # validate and get the time object
             if re.match(r"^\d{4}$", study_time):
                 hour = int(study_time[0:2])
                 minute = int(study_time[2:4])
                 if hour < 0 or hour >= 24 or minute < 0 or minute >= 60:
                     message.respond(_(strings["time_format"], get_language_code(message.persistant_connection)) % {"alias" : id, "time" : study_time})
                     return
                 real_time = dtt(hour, minute)
             else:
                 message.respond(_(strings["time_format"], get_language_code(message.persistant_connection)) % {"alias" : id, "time" : study_time})
                 return 
             
             # user ids are unique per-location so use location-id
             # as the alias
             alias = IaviReporter.get_alias(location.code, id)
             
             # make sure this isn't a duplicate alias
             if len(IaviReporter.objects.filter(alias=alias)) > 0:
                 message.respond(_(strings["already_registered"], language) % {"alias": id, "location":location.code})
                 return True
             
             # create the reporter object for this person 
             reporter = IaviReporter(alias=alias, language=language, location=location, registered=message.date)
             reporter.save()
             
             # create the study participant for this too.  Assume they're starting
             # tomorrow and don't set a stop date.  
             start_date = (datetime.today() + timedelta(days=1)).date()
             participant = StudyParticipant.objects.create(reporter=reporter, 
                                                           start_date = start_date,
                                                           notification_time = real_time)
             
             # also attach the reporter to the connection 
             message.persistant_connection.reporter=reporter
             message.persistant_connection.save()
             
             message.respond(_(strings["registration_complete"], language) % {"alias": id })
             
             # also send the PIN request and add this user to the 
             # pending pins
             self.pending_pins[reporter.pk] = None
             message.respond(_(strings["pin_request"], language))
     else:
         self.info("Message doesn't match. %s", message)
Example #10
0
    def validate(self, *args, **kwargs):
        message = args[0]
        form_entry = args[1]
        
        data = form_entry.to_dict()
        
        if form_entry.form.code.abbreviation == "register": 
            language = data["language"]
            site = data["location"]
            id = data["study_id"]
            # time is optional
            study_time = data["time"]
            if not study_time:
                study_time = "1600" 

            # validate the format of the id, existence of location
            if not re.match(r"^\d{3}$", id):
                return [(_(strings["id_format"], get_language_code(message.persistant_connection)) % {"alias" : id})]
            try:
                location = Location.objects.get(code=site)
            except Location.DoesNotExist:
                return[(_(strings["unknown_location"], get_language_code(message.persistant_connection)) % {"alias" : id, "location" : site})]
                
            # TODO: validate the language
            
            # validate and get the time object
            if re.match(r"^\d{4}$", study_time):
                hour = int(study_time[0:2])
                minute = int(study_time[2:4])
                if hour < 0 or hour >= 24 or minute < 0 or minute >= 60:
                    return [(_(strings["time_format"], get_language_code(message.persistant_connection)) % {"alias" : id, "time" : study_time})]
                real_time = dtt(hour, minute)
            else:
                return [(_(strings["time_format"], get_language_code(message.persistant_connection)) % {"alias" : id, "time" : study_time})]
                
            # user ids are unique per-location so use location-id
            # as the alias
            alias = IaviReporter.get_alias(location.code, id)
            
            # make sure this isn't a duplicate alias
            if len(IaviReporter.objects.filter(alias=alias)) > 0:
                return [(_(strings["already_registered"], language) % {"alias": id, "location":location.code})]
                return True
            
            data["alias"] = alias
            data["location"] = location
            data["time"] = real_time
            # all fields were present and correct, so copy them into the
            # form_entry, for "actions" to pick up again without re-fetching
            form_entry.reg_data = data
            
            return []
            
        elif form_entry.form.code.abbreviation == "test":
            print "test!"
            print data
            site = data["location"]
            id = data["study_id"]
            alias = IaviReporter.get_alias(site, id)
            try: 
                # make sure the user in question exists.  If not,
                # respond with an error
                user = IaviReporter.objects.get(alias=alias)
                
                # set some data in the form_entry so we have easy
                # access to it from within actions  
                data["reporter"] = user
                data["alias"] = alias
                form_entry.test_data = data
            except IaviReporter.DoesNotExist:
                return [(_(strings["unknown_user"], language) % {"alias":id})]