def relative_date(date_string): """ Return a relative date string corresponding to date_string, parsed using parsedatetime. Returned string will be "today @ hh:mm p" if it's today, "tomorrow @ hh:mm p" if it's tomorrow, or "<day of week> @hh:mm p" if it's within a week. Anything else is "mm/dd/yyyy" (note the lack of a time). """ try: # attempt to parse it in the super-wonky way that django passes # dates to filters (e.g. not as nice text, sadly) thedate = datetime.datetime.strptime(date_string.partition('.')[0], "%Y-%m-%d %H:%M:%S") except ValueError: # now that we've exhausted our best effort, let's try the next one thedate = parsedt(date_string) try: # actually gets the time it was this morning rightnow = datetime.datetime.combine(datetime.datetime.now(), datetime.time.min) # and compute the difference so we can give relative dates diff = thedate - rightnow except: # any exception here should return nothing return None if diff.days == 0: # Today return 'today @' + thedate.strftime("%-I:%M %p (%m/%d/%Y)") ## at 05:45 PM elif diff.days == 1: # Tomorrow return 'tomorrow @' + thedate.strftime("%-I:%M %p (%m/%d/%Y)") ## at 05:45 PM Tomorrow elif diff.days < 7: # one week from now return thedate.strftime("%A @%-I:%M %p (%m/%d/%Y)") ## at 05:45 PM Tuesday else: return 'on ' + thedate.strftime("%m/%d/%Y") ## on 10/03/1980
def pdtest(request): fieldvars = {} if 'incoming_datetime' in request.POST: # attempt to parse it fieldvars['incoming_datetime_out'] = request.POST['incoming_datetime'] fieldvars['parsed_datetime'] = parsedt(request.POST['incoming_datetime']) return render_to_response('pdtest.html', fieldvars, context_instance=RequestContext(request))
def pdtest(request): fieldvars = {} if 'incoming_datetime' in request.POST: # attempt to parse it fieldvars['incoming_datetime_out'] = request.POST['incoming_datetime'] fieldvars['parsed_datetime'] = parsedt( request.POST['incoming_datetime']) return render_to_response('pdtest.html', fieldvars, context_instance=RequestContext(request))
def satisfied(self, **kwargs): try: # see if we have the necessary values # if not, this check definitely doesn't apply msg = kwargs['msg'] except KeyError: return False try: result = utilities.parsedt(msg.text, verify_complete=True) except ValueError: # it was not parseable as a datetime :( return False # it matched! stuff our context with useful data and return true self.context['message'] = msg.text self.context['parsed_datetime'] = result return True
def relative_date(date_string): """ Return a relative date string corresponding to date_string, parsed using parsedatetime. Returned string will be "today @ hh:mm p" if it's today, "tomorrow @ hh:mm p" if it's tomorrow, or "<day of week> @hh:mm p" if it's within a week. Anything else is "mm/dd/yyyy" (note the lack of a time). """ try: # attempt to parse it in the super-wonky way that django passes # dates to filters (e.g. not as nice text, sadly) thedate = datetime.datetime.strptime( date_string.partition('.')[0], "%Y-%m-%d %H:%M:%S") except ValueError: # now that we've exhausted our best effort, let's try the next one thedate = parsedt(date_string) try: # actually gets the time it was this morning rightnow = datetime.datetime.combine(datetime.datetime.now(), datetime.time.min) # and compute the difference so we can give relative dates diff = thedate - rightnow except: # any exception here should return nothing return None if diff.days == 0: # Today return 'today @' + thedate.strftime( "%-I:%M %p (%m/%d/%Y)") ## at 05:45 PM elif diff.days == 1: # Tomorrow return 'tomorrow @' + thedate.strftime( "%-I:%M %p (%m/%d/%Y)") ## at 05:45 PM Tomorrow elif diff.days < 7: # one week from now return thedate.strftime( "%A @%-I:%M %p (%m/%d/%Y)") ## at 05:45 PM Tuesday else: return 'on ' + thedate.strftime("%m/%d/%Y") ## on 10/03/1980
def signupform(request): if request.method == 'POST': # If the form has been submitted... # form = ASAPParticipantForm(request.POST) # A form bound to the POST data # if form.is_valid(): # All validation rules pass # subject = form.cleaned_data['subject'] processed_phone = request.POST['cellphone'] # ensure that their phone number is in the correct format if not processed_phone.startswith("+1"): processed_phone = "+1" + processed_phone # create a Patient for them, too try: np = Patient(address=processed_phone, email=request.POST['email'], contact_pref='sms', first_name=request.POST['firstname'], last_name=request.POST['lastname']) np.save() except IntegrityError as ex: # either their address or email address is already in use...what to do? raise # we're assigning all ASAP signups to the ASAP Admin account np.clinicians.add( Clinician.objects.get(user=User.objects.get( username='******'))) # also create an ASAPParticipant and put all the form data into their instance participant = ASAPParticipant( patient=np, firstname=request.POST['firstname'], lastname=request.POST['lastname'], cellphone=request.POST['cellphone'], email=request.POST['email'], age=request.POST['age'] if is_number(request.POST['age']) else None, zipcode=request.POST['zipcode'] if is_number(request.POST['zipcode']) else None, questionnaire_pref=request.POST['questionnaire_pref'], other_diagnosis=request.POST['diagnosis_other_description']) participant.save() # enumerate their diagnoses and associate them with the user for d in request.POST.getlist('diagnoses'): participant.diagnoses.add(Diagnosis.objects.get(proper_name=d)) # finally, schedule all of their tasks to run at various times... start_date = utilities.parsedt(settings.ASAP_INITIAL_GOAL_DELAY) for goalid in [ id for id in request.POST['goals_list_hidden'].split(',') ]: try: goalid = int(goalid) except ValueError: # probably a 'no goal' entry, just continue continue # get the goal goal = ASAPGoal.objects.get(pk=goalid) # add to the patient's list of goals participant.goals.add(goal) # then create a taskinstance for this template TaskInstance.objects.create_task( patient=np, task=goal.tasktemplate.task, params=goal.tasktemplate.arguments, schedule_date=start_date, creator="asap_admin", name=goal.tasktemplate.name) # increment the start date by 2 weeks start_date = utilities.parsedt(settings.ASAP_BETWEEN_GOALS_DELAY, start_date) return HttpResponseRedirect('/ASAP/thanks/') # Redirect after POST else: form = ASAPParticipantForm() # An unbound form return render_to_response('signup.html', { 'form': form, 'goal_categories': ASAPGoalCategory.objects.all() }, context_instance=RequestContext(request))
def signupform(request): if request.method == 'POST': # If the form has been submitted... # form = ASAPParticipantForm(request.POST) # A form bound to the POST data # if form.is_valid(): # All validation rules pass # subject = form.cleaned_data['subject'] processed_phone = request.POST['cellphone'] # ensure that their phone number is in the correct format if not processed_phone.startswith("+1"): processed_phone = "+1" + processed_phone # create a Patient for them, too try: np = Patient( address = processed_phone, email = request.POST['email'], contact_pref = 'sms', first_name = request.POST['firstname'], last_name = request.POST['lastname'] ) np.save() except IntegrityError as ex: # either their address or email address is already in use...what to do? raise # we're assigning all ASAP signups to the ASAP Admin account np.clinicians.add(Clinician.objects.get(user=User.objects.get(username='******'))) # also create an ASAPParticipant and put all the form data into their instance participant = ASAPParticipant( patient=np, firstname=request.POST['firstname'], lastname=request.POST['lastname'], cellphone=request.POST['cellphone'], email=request.POST['email'], age=request.POST['age'] if is_number(request.POST['age']) else None, zipcode=request.POST['zipcode'] if is_number(request.POST['zipcode']) else None, questionnaire_pref=request.POST['questionnaire_pref'], other_diagnosis=request.POST['diagnosis_other_description'] ) participant.save() # enumerate their diagnoses and associate them with the user for d in request.POST.getlist('diagnoses'): participant.diagnoses.add(Diagnosis.objects.get(proper_name=d)) # finally, schedule all of their tasks to run at various times... start_date = utilities.parsedt(settings.ASAP_INITIAL_GOAL_DELAY) for goalid in [id for id in request.POST['goals_list_hidden'].split(',')]: try: goalid = int(goalid) except ValueError: # probably a 'no goal' entry, just continue continue # get the goal goal = ASAPGoal.objects.get(pk=goalid) # add to the patient's list of goals participant.goals.add(goal) # then create a taskinstance for this template TaskInstance.objects.create_task( patient=np, task=goal.tasktemplate.task, params=goal.tasktemplate.arguments, schedule_date=start_date, creator="asap_admin", name=goal.tasktemplate.name ) # increment the start date by 2 weeks start_date = utilities.parsedt(settings.ASAP_BETWEEN_GOALS_DELAY, start_date) return HttpResponseRedirect('/ASAP/thanks/') # Redirect after POST else: form = ASAPParticipantForm() # An unbound form return render_to_response('signup.html', { 'form': form, 'goal_categories': ASAPGoalCategory.objects.all() }, context_instance=RequestContext(request))
def _exec_children(self, top, context=None): print "--> Executing children of %s..." % (top) # store the node we're on as the last-expanded node # we'll use this to allow repeating the last taken action on a poke self.last_expansion = self.tree.getpath(top) self.last_expansion_context = context # first off, clear all pre-existing conditions self.conditions = [] self.instance.timeout_date = None self.instance.save() # construct a default context for this evaluation # and add any parent context info to the dict if context: self.context.update(context) default_context = Context({ 'patient': self.instance.patient, 'participant': self.instance.patient.asapparticipant, 'params': self.params }) default_context.update(self.context) # also copy the prefix attribute (if it exists) into our machine's registered prefix if 'prefix' in top.attrib: self.prefix = self.dispatch.request_prefix(self.instance.patient, top.attrib['prefix']) # pre-step: determine if there are any elements that require a response # that may be siblings to a <message> element. we need to know this # so we know whether to tell them to "type prefix before their response" siblings = [node.tag for node in top] accepts_response = ("response" in siblings or "link" in siblings) # execute all top-level elements for node in top: # depending on the type of the thing, perform some action if node.tag == "message": # if there's a condition supplied, evaluate it using the template language # only proceed if the string evaluates to a non-empty string. # if there's no condition, just send it! if (not "condition" in node.attrib) or (self.templatize(node.attrib['condition'], default_context).strip()): # strip node text and collapse whitespace text = " ".join(node.text.strip().split()) # apply django's template engine to the text and send the resulting message self.send(self.templatize(text, default_context), accepts_response=accepts_response) elif node.tag == "response": # we first check to see if this can apply to us at all by evaluating the condition attribute # it works just like it does for message; if it evaluates to false, this node is skipped entirely if (not "condition" in node.attrib) or (self.templatize(node.attrib['condition'], default_context).strip()): if node.get('type',None) == "date_time": # it's a parsedatetime condition rather than a regex one self.conditions.append(ParseDateTimeCondition(self.tree.getpath(node))) else: # it's a regex condition (FIXME: should be a 'regex' type) # add the condition of the response to the action queue print "--> Adding a regex condition in %s" % (top) # angle brackets are mapped to {@ and @} to get around xml's restrictions pattern = node.attrib["pattern"].replace("{@","<").replace("@}",">") self.conditions.append(RegexCondition(self.tree.getpath(node), pattern)) elif node.tag == "timeout": # gather the duration and offset, if specified try: duration = node.attrib['delay'] offset = node.get('offset', None) # optional; None if not present triggerdate = utilities.parsedt(duration, offset) except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) # FIXME: allows temporary override of the timeout duration for testing try: if settings.FORCE_TIMEOUT_DELAY: triggerdate = utilities.parsedt(settings.FORCE_TIMEOUT_DELAY) except: # we don't need to do anything; we just assume the setting wasn't set pass # add the condition of the response to the action queue print "--> Adding a timeout condition in %s" % (top) self.conditions.append(TimeoutCondition(self.tree.getpath(node), triggerdate)) # and register us as requiring a timeout # only replace the timeout if the new one is more recent than the existing one # this is just a nicety to the task writer, since only one timeout # will ever trigger...thus, the first one will erase all subsequent ones anyway self.instance.timeout_date = triggerdate self.instance.save() elif node.tag == "schedule": # gather the duration and offset, if specified try: tasktemplatename = node.attrib['task'] date = self.templatize(node.attrib['date'], default_context) offset = node.get('offset', None) # optional; None if not present if offset is not None: offset = self.templatize(offset, default_context) schedule_date = utilities.parsedt(offset, utilities.parsedt(date)) else: schedule_date = utilities.parsedt(date) except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) # FIXME: allows temporary override of the timeout duration for testing try: if settings.FORCE_SCHEDULE_DELAY: schedule_date = utilities.parsedt(settings.FORCE_SCHEDULE_DELAY) except: # we don't need to do anything; we just assume the setting wasn't set pass # look up the task template that they specified... template = TaskTemplate.objects.get(name=tasktemplatename) # grab its default arguments to start out new_args = json.loads(template.arguments) # and collect any additional arguments (e.g. params) defined in children of this node in <param key="a">value</param> format for param in [n for n in node if n.tag == "param"]: # process the values and insert them into the new_args, too new_args[param.attrib['key']] = self.templatize(param.text, default_context) # this time we spawn another task rather than continuing execution here self.instance.spawn_task( template.task, schedule_date, name=template.name, update_params=new_args ) elif node.tag == "store": # gather the key and value try: key = node.attrib['key'] value = node.attrib['value'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) value = self.templatize(value, default_context) print "--> Storing '%s' to key '%s'" % (value, key) # store these to the persistent params collection and save it p = json.loads(self.instance.params) p[key] = value # and don't forget to update the context, too! default_context['params'] = p # finally, save it all back to the db self.instance.params = json.dumps(p) self.instance.save() elif node.tag == "unstore": # gather the key and value try: key = node.attrib['key'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) print "--> Unstoring key '%s'" % (key) # store these to the persistent params collection and save it try: p = json.loads(self.instance.params) del p[key] # and don't forget to update the context, too! default_context['params'] = p # finally, save it all back to the db self.instance.params = json.dumps(p) self.instance.save() except: # not sure what to do here self.dispatch.info("Unable to unstore key '%s' from parameters collection" % (key)) elif node.tag == "alert": # gather the key and value try: name = node.attrib['name'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) # collect any params defined in children of this node in <param key="a">value</param> format alert_args = {} for param in [n for n in node if n.tag == "param"]: # process the values and insert them into alert_args alert_args[param.attrib['key']] = self.templatize(param.text, default_context) alert_args['url'] = '/taskmanager/patients/%d/history/#session_%d' % (self.instance.patient.id, self.instance.id) # alert_args.update(default_context) Alert.objects.add_alert(name, arguments=alert_args, patient=self.instance.patient) elif node.tag == "abort": # gather the key and value try: scope = node.attrib['scope'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) if scope == "process": # remove all pending tasks belonging to the same process TaskInstance.objects.filter(process=self.instance.process, status="pending").delete() # and immediately conclude execution raise TaskCompleteException() elif scope == "others": # end all other tasks that belong to the same process TaskInstance.objects.filter(process=self.instance.process,status="running").exclude(pk=self.instance.id).update(status="completed") elif node.tag == "scope": # immediately expands when reached # if the condition is present and false,the node is ignored. if ("condition" not in node.attrib) or (self.templatize(node.attrib['condition'], default_context).strip()): self._exec_children(node, context) return # we have to break here as well so we don't die immediately elif node.tag == "link": # immediately expand the link with the id specified by target # but first we have to find it target = self.tree.xpath("//*[@id='" + node.attrib["target"] + "']") if len(target) <= 0: raise Exception("Target node for %s couldn't be found!" % ("//*[@id='" + node.attrib["target"] + "']")) # take the first element that matches the given id # (there should only be one, but we're not validating for that) target = target[0] # check for some obvious problem conditions if target == top or target == node: raise Exception("Aborting, link would lead to infinite recursion") print "--> Following link from %s to %s" % (top, target) # if everything's good, jump immediately to that node # we maintain the context to allow us to pass things that we detected in our present node self._exec_children(target, context) return # we have to break here, too... # if there's nothing left on the condition queue then, once again, we're done if not self.conditions: print "--> Dying in %s on account of having no conditions left" % (top) raise TaskCompleteException()
def _exec_children(self, top, context=None): print "--> Executing children of %s..." % (top) # store the node we're on as the last-expanded node # we'll use this to allow repeating the last taken action on a poke self.last_expansion = self.tree.getpath(top) self.last_expansion_context = context # first off, clear all pre-existing conditions self.conditions = [] self.instance.timeout_date = None self.instance.save() # construct a default context for this evaluation # and add any parent context info to the dict if context: self.context.update(context) default_context = Context({ 'patient': self.instance.patient, 'participant': self.instance.patient.asapparticipant, 'params': self.params }) default_context.update(self.context) # also copy the prefix attribute (if it exists) into our machine's registered prefix if 'prefix' in top.attrib: self.prefix = self.dispatch.request_prefix(self.instance.patient, top.attrib['prefix']) # pre-step: determine if there are any elements that require a response # that may be siblings to a <message> element. we need to know this # so we know whether to tell them to "type prefix before their response" siblings = [node.tag for node in top] accepts_response = ("response" in siblings or "link" in siblings) # execute all top-level elements for node in top: # depending on the type of the thing, perform some action if node.tag == "message": # if there's a condition supplied, evaluate it using the template language # only proceed if the string evaluates to a non-empty string. # if there's no condition, just send it! if (not "condition" in node.attrib) or (self.templatize( node.attrib['condition'], default_context).strip()): # strip node text and collapse whitespace text = " ".join(node.text.strip().split()) # apply django's template engine to the text and send the resulting message self.send(self.templatize(text, default_context), accepts_response=accepts_response) elif node.tag == "response": # we first check to see if this can apply to us at all by evaluating the condition attribute # it works just like it does for message; if it evaluates to false, this node is skipped entirely if (not "condition" in node.attrib) or (self.templatize( node.attrib['condition'], default_context).strip()): if node.get('type', None) == "date_time": # it's a parsedatetime condition rather than a regex one self.conditions.append( ParseDateTimeCondition(self.tree.getpath(node))) else: # it's a regex condition (FIXME: should be a 'regex' type) # add the condition of the response to the action queue print "--> Adding a regex condition in %s" % (top) # angle brackets are mapped to {@ and @} to get around xml's restrictions pattern = node.attrib["pattern"].replace("{@", "<").replace( "@}", ">") self.conditions.append( RegexCondition(self.tree.getpath(node), pattern)) elif node.tag == "timeout": # gather the duration and offset, if specified try: duration = node.attrib['delay'] offset = node.get('offset', None) # optional; None if not present triggerdate = utilities.parsedt(duration, offset) except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) # FIXME: allows temporary override of the timeout duration for testing try: if settings.FORCE_TIMEOUT_DELAY: triggerdate = utilities.parsedt( settings.FORCE_TIMEOUT_DELAY) except: # we don't need to do anything; we just assume the setting wasn't set pass # add the condition of the response to the action queue print "--> Adding a timeout condition in %s" % (top) self.conditions.append( TimeoutCondition(self.tree.getpath(node), triggerdate)) # and register us as requiring a timeout # only replace the timeout if the new one is more recent than the existing one # this is just a nicety to the task writer, since only one timeout # will ever trigger...thus, the first one will erase all subsequent ones anyway self.instance.timeout_date = triggerdate self.instance.save() elif node.tag == "schedule": # gather the duration and offset, if specified try: tasktemplatename = node.attrib['task'] date = self.templatize(node.attrib['date'], default_context) offset = node.get('offset', None) # optional; None if not present if offset is not None: offset = self.templatize(offset, default_context) schedule_date = utilities.parsedt( offset, utilities.parsedt(date)) else: schedule_date = utilities.parsedt(date) except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) # FIXME: allows temporary override of the timeout duration for testing try: if settings.FORCE_SCHEDULE_DELAY: schedule_date = utilities.parsedt( settings.FORCE_SCHEDULE_DELAY) except: # we don't need to do anything; we just assume the setting wasn't set pass # look up the task template that they specified... template = TaskTemplate.objects.get(name=tasktemplatename) # grab its default arguments to start out new_args = json.loads(template.arguments) # and collect any additional arguments (e.g. params) defined in children of this node in <param key="a">value</param> format for param in [n for n in node if n.tag == "param"]: # process the values and insert them into the new_args, too new_args[param.attrib['key']] = self.templatize( param.text, default_context) # this time we spawn another task rather than continuing execution here self.instance.spawn_task(template.task, schedule_date, name=template.name, update_params=new_args) elif node.tag == "store": # gather the key and value try: key = node.attrib['key'] value = node.attrib['value'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) value = self.templatize(value, default_context) print "--> Storing '%s' to key '%s'" % (value, key) # store these to the persistent params collection and save it p = json.loads(self.instance.params) p[key] = value # and don't forget to update the context, too! default_context['params'] = p # finally, save it all back to the db self.instance.params = json.dumps(p) self.instance.save() elif node.tag == "unstore": # gather the key and value try: key = node.attrib['key'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) print "--> Unstoring key '%s'" % (key) # store these to the persistent params collection and save it try: p = json.loads(self.instance.params) del p[key] # and don't forget to update the context, too! default_context['params'] = p # finally, save it all back to the db self.instance.params = json.dumps(p) self.instance.save() except: # not sure what to do here self.dispatch.info( "Unable to unstore key '%s' from parameters collection" % (key)) elif node.tag == "alert": # gather the key and value try: name = node.attrib['name'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) # collect any params defined in children of this node in <param key="a">value</param> format alert_args = {} for param in [n for n in node if n.tag == "param"]: # process the values and insert them into alert_args alert_args[param.attrib['key']] = self.templatize( param.text, default_context) alert_args[ 'url'] = '/taskmanager/patients/%d/history/#session_%d' % ( self.instance.patient.id, self.instance.id) # alert_args.update(default_context) Alert.objects.add_alert(name, arguments=alert_args, patient=self.instance.patient) elif node.tag == "abort": # gather the key and value try: scope = node.attrib['scope'] except KeyError as ex: raise XMLFormatException("%s node expects attribute '%s'" % (node.tag, ex.args[0])) if scope == "process": # remove all pending tasks belonging to the same process TaskInstance.objects.filter(process=self.instance.process, status="pending").delete() # and immediately conclude execution raise TaskCompleteException() elif scope == "others": # end all other tasks that belong to the same process TaskInstance.objects.filter( process=self.instance.process, status="running").exclude(pk=self.instance.id).update( status="completed") elif node.tag == "scope": # immediately expands when reached # if the condition is present and false,the node is ignored. if ("condition" not in node.attrib) or (self.templatize( node.attrib['condition'], default_context).strip()): self._exec_children(node, context) return # we have to break here as well so we don't die immediately elif node.tag == "link": # immediately expand the link with the id specified by target # but first we have to find it target = self.tree.xpath("//*[@id='" + node.attrib["target"] + "']") if len(target) <= 0: raise Exception( "Target node for %s couldn't be found!" % ("//*[@id='" + node.attrib["target"] + "']")) # take the first element that matches the given id # (there should only be one, but we're not validating for that) target = target[0] # check for some obvious problem conditions if target == top or target == node: raise Exception( "Aborting, link would lead to infinite recursion") print "--> Following link from %s to %s" % (top, target) # if everything's good, jump immediately to that node # we maintain the context to allow us to pass things that we detected in our present node self._exec_children(target, context) return # we have to break here, too... # if there's nothing left on the condition queue then, once again, we're done if not self.conditions: print "--> Dying in %s on account of having no conditions left" % ( top) raise TaskCompleteException()