def __init__(self, xform_raw=None, xform_path=None, instance_raw=None, instance_path=None, preload_data={}, extensions=[], nav_mode='prompt'): self.lock = threading.Lock() self.nav_mode = nav_mode self.seq_id = 0 def content_or_path(content, path): if content != None: return content elif path != None: with codecs.open(path, encoding='utf-8') as f: return f.read() else: return None xform = content_or_path(xform_raw, xform_path) instance = content_or_path(instance_raw, instance_path) self.form = load_form(xform, instance, extensions, preload_data) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) self._parse_current_event() self.update_last_activity()
def __init__(self, xform_raw=None, xform_path=None, instance_raw=None, instance_path=None, init_lang=None, preload_data={}, extensions=[], nav_mode='prompt'): self.lock = threading.Lock() self.nav_mode = nav_mode self.seq_id = 0 def content_or_path(content, path): if content != None: return content elif path != None: with codecs.open(path, encoding='utf-8') as f: return f.read() else: return None xform = content_or_path(xform_raw, xform_path) instance = content_or_path(instance_raw, instance_path) self.form = load_form(xform, instance, extensions, preload_data) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if init_lang is not None: self.fec.setLanguage(init_lang) self._parse_current_event() self.update_last_activity()
def __init__(self, xform, instance=None, **params): self.uuid = params.get('uuid', uuid.uuid4().hex) self.lock = threading.Lock() self.nav_mode = params.get('nav_mode', 'prompt') self.seq_id = params.get('seq_id', 0) self.form = load_form(xform, instance, params.get('extensions', []), params.get('session_data', {}), params.get('api_auth')) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if params.get('init_lang'): try: self.fec.setLanguage(params.get('init_lang')) except UnregisteredLocaleException: pass # just use default language if params.get('cur_index'): self.fec.jumpToIndex(self.parse_ix(params.get('cur_index'))) self._parse_current_event() self.staleness_window = 3600. * params['staleness_window'] self.persist = params.get('persist', settings.PERSIST_SESSIONS) self.orig_params = { 'xform': xform, 'nav_mode': params.get('nav_mode'), 'session_data': params.get('session_data'), 'api_auth': params.get('api_auth'), 'staleness_window': params['staleness_window'], } self.update_last_activity()
def __init__(self, xform, instance=None, **params): self.uuid = params.get('uuid', uuid.uuid4().hex) self.nav_mode = params.get('nav_mode', 'prompt') self.seq_id = params.get('seq_id', 0) self.uses_sql_backend = params.get('uses_sql_backend') self.form = load_form( xform, instance, params.get('extensions', []), params.get('session_data', {}), params.get('api_auth'), params.get('form_context', None), self.uses_sql_backend, ) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if params.get('init_lang'): try: self.fec.setLanguage(params.get('init_lang')) except UnregisteredLocaleException: pass # just use default language if params.get('cur_index'): self.fec.jumpToIndex(self.parse_ix(params.get('cur_index'))) self._parse_current_event() self.staleness_window = 3600. * params['staleness_window'] self.persist = params.get('persist', settings.PERSIST_SESSIONS) self.orig_params = { 'xform': xform, 'nav_mode': params.get('nav_mode'), 'session_data': params.get('session_data'), 'api_auth': params.get('api_auth'), 'staleness_window': params['staleness_window'], } self.update_last_activity()
def __init__(self, xform, instance=None, init_lang=None, session_data={}, extensions=[], nav_mode='prompt', api_auth=None): self.lock = threading.Lock() self.nav_mode = nav_mode self.seq_id = 0 self.form = load_form(xform, instance, extensions, session_data, api_auth) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if init_lang is not None: self.fec.setLanguage(init_lang) self._parse_current_event() self.update_last_activity()
def __init__(self, xform, instance=None, **params): self.uuid = params.get("uuid", uuid.uuid4().hex) self.nav_mode = params.get("nav_mode", "prompt") self.seq_id = params.get("seq_id", 0) self.uses_sql_backend = params.get("uses_sql_backend") self.form = load_form( xform, instance, params.get("extensions", []), params.get("session_data", {}), params.get("api_auth"), params.get("form_context", None), self.uses_sql_backend, ) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if params.get("init_lang"): try: self.fec.setLanguage(params.get("init_lang")) except UnregisteredLocaleException: pass # just use default language if params.get("cur_index"): self.fec.jumpToIndex(self.parse_ix(params.get("cur_index"))) self._parse_current_event() self.staleness_window = 3600.0 * params["staleness_window"] self.persist = params.get("persist", settings.PERSIST_SESSIONS) self.orig_params = { "xform": xform, "nav_mode": params.get("nav_mode"), "session_data": params.get("session_data"), "api_auth": params.get("api_auth"), "staleness_window": params["staleness_window"], } self.update_last_activity()
class XFormSession: def __init__(self, xform_raw=None, xform_path=None, instance_raw=None, instance_path=None, preload_data={}, extensions=[], nav_mode='prompt'): self.lock = threading.Lock() self.nav_mode = nav_mode self.seq_id = 0 def content_or_path(content, path): if content != None: return content elif path != None: with codecs.open(path, encoding='utf-8') as f: return f.read() else: return None xform = content_or_path(xform_raw, xform_path) instance = content_or_path(instance_raw, instance_path) self.form = load_form(xform, instance, extensions, preload_data) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) self._parse_current_event() self.update_last_activity() def __enter__(self): if self.nav_mode == 'fao': self.lock.acquire() else: if not self.lock.acquire(False): raise SequencingException() self.seq_id += 1 self.update_last_activity() return self def __exit__(self, *_): self.lock.release() def update_last_activity(self): self.last_activity = time.time() def output(self): if self.cur_event['type'] != 'form-complete': #warn that not at end of form pass instance_bytes = FormSerializer().serializeInstance(self.form.getInstance()) return unicode(''.join(chr(b) for b in instance_bytes.tolist()), 'utf-8') def walk(self): form_ix = FormIndex.createBeginningOfFormIndex() tree = [] self._walk(form_ix, tree) return tree def _walk(self, parent_ix, siblings): def ix_in_scope(form_ix): if form_ix.isEndOfFormIndex(): return False elif parent_ix.isBeginningOfFormIndex(): return True else: return FormIndex.isSubElement(parent_ix, form_ix) form_ix = self.fem.incrementIndex(parent_ix, True) while ix_in_scope(form_ix): relevant = self.fem.isIndexRelevant(form_ix) if not relevant: form_ix = self.fem.incrementIndex(form_ix, False) continue evt = self.__parse_event(form_ix) evt['relevant'] = relevant if evt['type'] == 'sub-group': presentation_group = (evt['caption'] != None) if presentation_group: siblings.append(evt) evt['children'] = [] form_ix = self._walk(form_ix, evt['children'] if presentation_group else siblings) elif evt['type'] == 'repeat-juncture': siblings.append(evt) evt['children'] = [] for i in range(0, self.fem.getForm().getNumRepetitions(form_ix)): subevt = { 'type': 'sub-group', 'ix': self.fem.getForm().descendIntoRepeat(form_ix, i), 'caption': evt['repetitions'][i], 'repeatable': True, 'children': [], } # ghettoooo; if this pays off, this should be better incorporated into the form entry API subevt['uuid'] = self.form.getInstance().resolveReference(subevt['ix'].getReference()).uuid evt['children'].append(subevt) self._walk(subevt['ix'], subevt['children']) for key in ['repetitions', 'del-choice', 'del-header', 'done-choice']: del evt[key] form_ix = self.fem.incrementIndex(form_ix, True) # why True? else: siblings.append(evt) form_ix = self.fem.incrementIndex(form_ix, True) # why True? return form_ix def _parse_current_event(self): self.cur_event = self.__parse_event(self.fem.getFormIndex()) return self.cur_event def __parse_event (self, form_ix): event = {'ix': form_ix} status = self.fem.getEvent(form_ix) if status == self.fec.EVENT_BEGINNING_OF_FORM: event['type'] = 'form-start' elif status == self.fec.EVENT_END_OF_FORM: event['type'] = 'form-complete' self.fem.getForm().postProcessInstance() elif status == self.fec.EVENT_QUESTION: event['type'] = 'question' self._parse_question(event) elif status == self.fec.EVENT_REPEAT_JUNCTURE: event['type'] = 'repeat-juncture' self._parse_repeat_juncture(event) else: event['type'] = 'sub-group' event['caption'] = self.fem.getCaptionPrompt(form_ix).getLongText() if status == self.fec.EVENT_GROUP: event['repeatable'] = False elif status == self.fec.EVENT_REPEAT: event['repeatable'] = True event['exists'] = True elif status == self.fec.EVENT_PROMPT_NEW_REPEAT: #obsolete event['repeatable'] = True event['exists'] = False return event def _get_question_choices(self, q): return [choice(q, ch) for ch in q.getSelectChoices()] def _parse_style_info(self, rawstyle): info = {} if rawstyle != None: info['raw'] = rawstyle try: info.update([[p.strip() for p in f.split(':')][:2] for f in rawstyle.split(';') if f.strip()]) except ValueError: pass return info def _parse_question (self, event): q = self.fem.getQuestionPrompt(event['ix']) event['caption'] = q.getLongText() event['help'] = q.getHelpText() event['style'] = self._parse_style_info(q.getAppearanceHint()) if q.getControlType() == Constants.CONTROL_TRIGGER: event['datatype'] = 'info' else: try: event['datatype'] = { Constants.DATATYPE_NULL: 'str', Constants.DATATYPE_TEXT: 'str', Constants.DATATYPE_INTEGER: 'int', Constants.DATATYPE_DECIMAL: 'float', Constants.DATATYPE_DATE: 'date', Constants.DATATYPE_TIME: 'time', Constants.DATATYPE_CHOICE: 'select', Constants.DATATYPE_CHOICE_LIST: 'multiselect', # not supported yet Constants.DATATYPE_DATE_TIME: 'datetime', Constants.DATATYPE_GEOPOINT: 'geo', Constants.DATATYPE_BARCODE: 'barcode', Constants.DATATYPE_BINARY: 'binary', Constants.DATATYPE_LONG: 'longint', }[q.getDataType()] except KeyError: event['datatype'] = 'unrecognized' if event['datatype'] in ('select', 'multiselect'): event['choices'] = self._get_question_choices(q) event['required'] = q.isRequired() value = q.getAnswerValue() if value == None: event['answer'] = None elif event['datatype'] == 'int': event['answer'] = value.getValue() elif event['datatype'] == 'float': event['answer'] = value.getValue() elif event['datatype'] == 'str': event['answer'] = value.getValue() elif event['datatype'] == 'date': event['answer'] = to_pdate(value.getValue()) elif event['datatype'] == 'time': event['answer'] = to_ptime(value.getValue()) elif event['datatype'] == 'select': event['answer'] = value.getValue().index + 1 elif event['datatype'] == 'multiselect': event['answer'] = [sel.index + 1 for sel in value.getValue()] def _parse_repeat_juncture(self, event): r = self.fem.getCaptionPrompt(event['ix']) ro = r.getRepeatOptions() event['main-header'] = ro.header event['repetitions'] = list(r.getRepetitionsText()) event['add-choice'] = ro.add event['del-choice'] = ro.delete event['del-header'] = ro.delete_header event['done-choice'] = ro.done def next_event (self): self.fec.stepToNextEvent() return self._parse_current_event() def back_event (self): self.fec.stepToPreviousEvent() return self._parse_current_event() def answer_question (self, answer, _ix=None): ix = self.parse_ix(_ix) event = self.cur_event if ix is None else self.__parse_event(ix) if event['type'] != 'question': raise ValueError('not currently on a question') datatype = event['datatype'] if datatype == 'unrecognized': # don't commit answers to unrecognized questions, since we # couldn't parse what was originally there. whereas for # _unsupported_ questions, we're parsing and re-committing the # answer verbatim return {'status': 'success'} if answer == None or str(answer).strip() == '' or answer == [] or datatype == 'info': ans = None elif datatype == 'int': ans = IntegerData(int(answer)) elif datatype == 'float': ans = DecimalData(float(answer)) elif datatype == 'str': ans = StringData(str(answer)) elif datatype == 'date': ans = DateData(to_jdate(datetime.strptime(str(answer), '%Y-%m-%d').date())) elif datatype == 'time': ans = TimeData(to_jtime(datetime.strptime(str(answer), '%H:%M').time())) elif datatype == 'select': ans = SelectOneData(event['choices'][int(answer) - 1].to_sel()) elif datatype == 'multiselect': if hasattr(answer, '__iter__'): ans_list = answer else: ans_list = str(answer).split() ans = SelectMultiData(to_vect(event['choices'][int(k) - 1].to_sel() for k in ans_list)) result = self.fec.answerQuestion(*([ans] if ix is None else [ix, ans])) if result == self.fec.ANSWER_REQUIRED_BUT_EMPTY: return {'status': 'error', 'type': 'required'} elif result == self.fec.ANSWER_CONSTRAINT_VIOLATED: q = self.fem.getQuestionPrompt(*([] if ix is None else [ix])) return {'status': 'error', 'type': 'constraint', 'reason': q.getConstraintText()} elif result == self.fec.ANSWER_OK: return {'status': 'success'} def descend_repeat (self, rep_ix=None, _junc_ix=None): junc_ix = self.parse_ix(_junc_ix) if (junc_ix): self.fec.jumpToIndex(junc_ix) if rep_ix: self.fec.descendIntoRepeat(rep_ix - 1) else: self.fec.descendIntoNewRepeat() return self._parse_current_event() def delete_repeat (self, rep_ix, _junc_ix=None): junc_ix = self.parse_ix(_junc_ix) if (junc_ix): self.fec.jumpToIndex(junc_ix) self.fec.deleteRepeat(rep_ix - 1) return self._parse_current_event() #sequential (old-style) repeats only def new_repetition (self): #currently in the form api this always succeeds, but theoretically there could #be unsatisfied constraints that make it fail. how to handle them here? self.fec.newRepeat(self.fem.getFormIndex()) def parse_ix(self, s_ix): return index_from_str(s_ix, self.form) def form_title(self): return self.form.getTitle() def response(self, resp, ev_next=None, no_next=False): if no_next: navinfo = {} elif self.nav_mode == 'prompt': if ev_next is None: ev_next = next_event(self) navinfo = {'event': ev_next} elif self.nav_mode == 'fao': navinfo = {'tree': self.walk()} if DEBUG: print '=== walking ===' print_tree(self.walk()) print '===============' resp.update(navinfo) resp.update({'seq_id': self.seq_id}) return resp
class XFormSession: def __init__(self, xform_raw=None, xform_path=None, instance_raw=None, instance_path=None, preload_data={}, extensions=[], nav_mode='prompt'): self.lock = threading.Lock() self.nav_mode = nav_mode self.seq_id = 0 def content_or_path(content, path): if content != None: return content elif path != None: with codecs.open(path, encoding='utf-8') as f: return f.read() else: return None xform = content_or_path(xform_raw, xform_path) instance = content_or_path(instance_raw, instance_path) self.form = load_form(xform, instance, extensions, preload_data) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) self._parse_current_event() self.update_last_activity() def __enter__(self): if self.nav_mode == 'fao': self.lock.acquire() else: if not self.lock.acquire(False): raise SequencingException() self.seq_id += 1 self.update_last_activity() return self def __exit__(self, *_): self.lock.release() def update_last_activity(self): self.last_activity = time.time() def output(self): if self.cur_event['type'] != 'form-complete': #warn that not at end of form pass instance_bytes = FormSerializer().serializeInstance( self.form.getInstance()) return unicode(''.join(chr(b) for b in instance_bytes.tolist()), 'utf-8') def walk(self): form_ix = FormIndex.createBeginningOfFormIndex() tree = [] self._walk(form_ix, tree) return tree def _walk(self, parent_ix, siblings): def ix_in_scope(form_ix): if form_ix.isEndOfFormIndex(): return False elif parent_ix.isBeginningOfFormIndex(): return True else: return FormIndex.isSubElement(parent_ix, form_ix) form_ix = self.fem.incrementIndex(parent_ix, True) while ix_in_scope(form_ix): relevant = self.fem.isIndexRelevant(form_ix) if not relevant: form_ix = self.fem.incrementIndex(form_ix, False) continue evt = self.__parse_event(form_ix) evt['relevant'] = relevant if evt['type'] == 'sub-group': presentation_group = (evt['caption'] != None) if presentation_group: siblings.append(evt) evt['children'] = [] form_ix = self._walk( form_ix, evt['children'] if presentation_group else siblings) elif evt['type'] == 'repeat-juncture': siblings.append(evt) evt['children'] = [] for i in range(0, self.fem.getForm().getNumRepetitions(form_ix)): subevt = { 'type': 'sub-group', 'ix': self.fem.getForm().descendIntoRepeat(form_ix, i), 'caption': evt['repetitions'][i], 'repeatable': True, 'children': [], } # ghettoooo; if this pays off, this should be better incorporated into the form entry API subevt['uuid'] = self.form.getInstance().resolveReference( subevt['ix'].getReference()).uuid evt['children'].append(subevt) self._walk(subevt['ix'], subevt['children']) for key in [ 'repetitions', 'del-choice', 'del-header', 'done-choice' ]: del evt[key] form_ix = self.fem.incrementIndex(form_ix, True) # why True? else: siblings.append(evt) form_ix = self.fem.incrementIndex(form_ix, True) # why True? return form_ix def _parse_current_event(self): self.cur_event = self.__parse_event(self.fem.getFormIndex()) return self.cur_event def __parse_event(self, form_ix): event = {'ix': form_ix} status = self.fem.getEvent(form_ix) if status == self.fec.EVENT_BEGINNING_OF_FORM: event['type'] = 'form-start' elif status == self.fec.EVENT_END_OF_FORM: event['type'] = 'form-complete' self.fem.getForm().postProcessInstance() elif status == self.fec.EVENT_QUESTION: event['type'] = 'question' self._parse_question(event) elif status == self.fec.EVENT_REPEAT_JUNCTURE: event['type'] = 'repeat-juncture' self._parse_repeat_juncture(event) else: event['type'] = 'sub-group' event['caption'] = self.fem.getCaptionPrompt(form_ix).getLongText() if status == self.fec.EVENT_GROUP: event['repeatable'] = False elif status == self.fec.EVENT_REPEAT: event['repeatable'] = True event['exists'] = True elif status == self.fec.EVENT_PROMPT_NEW_REPEAT: #obsolete event['repeatable'] = True event['exists'] = False return event def _get_question_choices(self, q): return [choice(q, ch) for ch in q.getSelectChoices()] def _parse_style_info(self, rawstyle): info = {} if rawstyle != None: info['raw'] = rawstyle try: info.update([[p.strip() for p in f.split(':')][:2] for f in rawstyle.split(';') if f.strip()]) except ValueError: pass return info def _parse_question(self, event): q = self.fem.getQuestionPrompt(event['ix']) event['caption'] = q.getLongText() event['help'] = q.getHelpText() event['style'] = self._parse_style_info(q.getAppearanceHint()) if q.getControlType() == Constants.CONTROL_TRIGGER: event['datatype'] = 'info' else: try: event['datatype'] = { Constants.DATATYPE_NULL: 'str', Constants.DATATYPE_TEXT: 'str', Constants.DATATYPE_INTEGER: 'int', Constants.DATATYPE_DECIMAL: 'float', Constants.DATATYPE_DATE: 'date', Constants.DATATYPE_TIME: 'time', Constants.DATATYPE_CHOICE: 'select', Constants.DATATYPE_CHOICE_LIST: 'multiselect', # not supported yet Constants.DATATYPE_DATE_TIME: 'datetime', Constants.DATATYPE_GEOPOINT: 'geo', Constants.DATATYPE_BARCODE: 'barcode', Constants.DATATYPE_BINARY: 'binary', Constants.DATATYPE_LONG: 'longint', }[q.getDataType()] except KeyError: event['datatype'] = 'unrecognized' if event['datatype'] in ('select', 'multiselect'): event['choices'] = self._get_question_choices(q) event['required'] = q.isRequired() value = q.getAnswerValue() if value == None: event['answer'] = None elif event['datatype'] == 'int': event['answer'] = value.getValue() elif event['datatype'] == 'float': event['answer'] = value.getValue() elif event['datatype'] == 'str': event['answer'] = value.getValue() elif event['datatype'] == 'date': event['answer'] = to_pdate(value.getValue()) elif event['datatype'] == 'time': event['answer'] = to_ptime(value.getValue()) elif event['datatype'] == 'select': event['answer'] = value.getValue().index + 1 elif event['datatype'] == 'multiselect': event['answer'] = [sel.index + 1 for sel in value.getValue()] def _parse_repeat_juncture(self, event): r = self.fem.getCaptionPrompt(event['ix']) ro = r.getRepeatOptions() event['main-header'] = ro.header event['repetitions'] = list(r.getRepetitionsText()) event['add-choice'] = ro.add event['del-choice'] = ro.delete event['del-header'] = ro.delete_header event['done-choice'] = ro.done def next_event(self): self.fec.stepToNextEvent() return self._parse_current_event() def back_event(self): self.fec.stepToPreviousEvent() return self._parse_current_event() def answer_question(self, answer, _ix=None): ix = self.parse_ix(_ix) event = self.cur_event if ix is None else self.__parse_event(ix) if event['type'] != 'question': raise ValueError('not currently on a question') datatype = event['datatype'] if datatype == 'unrecognized': # don't commit answers to unrecognized questions, since we # couldn't parse what was originally there. whereas for # _unsupported_ questions, we're parsing and re-committing the # answer verbatim return {'status': 'success'} if answer == None or str( answer).strip() == '' or answer == [] or datatype == 'info': ans = None elif datatype == 'int': ans = IntegerData(int(answer)) elif datatype == 'float': ans = DecimalData(float(answer)) elif datatype == 'str': ans = StringData(str(answer)) elif datatype == 'date': ans = DateData( to_jdate(datetime.strptime(str(answer), '%Y-%m-%d').date())) elif datatype == 'time': ans = TimeData( to_jtime(datetime.strptime(str(answer), '%H:%M').time())) elif datatype == 'select': ans = SelectOneData(event['choices'][int(answer) - 1].to_sel()) elif datatype == 'multiselect': if hasattr(answer, '__iter__'): ans_list = answer else: ans_list = str(answer).split() ans = SelectMultiData( to_vect(event['choices'][int(k) - 1].to_sel() for k in ans_list)) result = self.fec.answerQuestion(*([ans] if ix is None else [ix, ans])) if result == self.fec.ANSWER_REQUIRED_BUT_EMPTY: return {'status': 'error', 'type': 'required'} elif result == self.fec.ANSWER_CONSTRAINT_VIOLATED: q = self.fem.getQuestionPrompt(*([] if ix is None else [ix])) return { 'status': 'error', 'type': 'constraint', 'reason': q.getConstraintText() } elif result == self.fec.ANSWER_OK: return {'status': 'success'} def descend_repeat(self, rep_ix=None, _junc_ix=None): junc_ix = self.parse_ix(_junc_ix) if (junc_ix): self.fec.jumpToIndex(junc_ix) if rep_ix: self.fec.descendIntoRepeat(rep_ix - 1) else: self.fec.descendIntoNewRepeat() return self._parse_current_event() def delete_repeat(self, rep_ix, _junc_ix=None): junc_ix = self.parse_ix(_junc_ix) if (junc_ix): self.fec.jumpToIndex(junc_ix) self.fec.deleteRepeat(rep_ix - 1) return self._parse_current_event() #sequential (old-style) repeats only def new_repetition(self): #currently in the form api this always succeeds, but theoretically there could #be unsatisfied constraints that make it fail. how to handle them here? self.fec.newRepeat(self.fem.getFormIndex()) def parse_ix(self, s_ix): return index_from_str(s_ix, self.form) def form_title(self): return self.form.getTitle() def response(self, resp, ev_next=None, no_next=False): if no_next: navinfo = {} elif self.nav_mode == 'prompt': if ev_next is None: ev_next = next_event(self) navinfo = {'event': ev_next} elif self.nav_mode == 'fao': navinfo = {'tree': self.walk()} if DEBUG: print '=== walking ===' print_tree(self.walk()) print '===============' resp.update(navinfo) resp.update({'seq_id': self.seq_id}) return resp
class XFormSession: def __init__(self, xform, instance=None, **params): self.uuid = params.get('uuid', uuid.uuid4().hex) self.lock = threading.Lock() self.nav_mode = params.get('nav_mode', 'prompt') self.seq_id = params.get('seq_id', 0) self.form = load_form(xform, instance, params.get('extensions', []), params.get('session_data', {}), params.get('api_auth')) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if params.get('init_lang'): try: self.fec.setLanguage(params.get('init_lang')) except UnregisteredLocaleException: pass # just use default language if params.get('cur_index'): self.fec.jumpToIndex(self.parse_ix(params.get('cur_index'))) self._parse_current_event() self.staleness_window = 3600. * params['staleness_window'] self.persist = params.get('persist', settings.PERSIST_SESSIONS) self.orig_params = { 'xform': xform, 'nav_mode': params.get('nav_mode'), 'session_data': params.get('session_data'), 'api_auth': params.get('api_auth'), 'staleness_window': params['staleness_window'], } self.update_last_activity() def __enter__(self): if self.nav_mode == 'fao': self.lock.acquire() else: if not self.lock.acquire(False): raise SequencingException() self.seq_id += 1 self.update_last_activity() return self def __exit__(self, *_): if self.persist: # TODO should this be done async? we must dump state before releasing the lock, however persistence.persist(self) self.lock.release() def update_last_activity(self): self.last_activity = time.time() def session_state(self): # workaround def get_lang(): if self.fem.getForm().getLocalizer() is not None: return self.fem.getLanguage() else: return None state = dict(self.orig_params) state.update({ 'instance': self.output(), 'init_lang': get_lang(), 'cur_index': str(self.fem.getFormIndex()) if self.nav_mode != 'fao' else None, 'seq_id': self.seq_id, }) # prune entries with null value, so that defaults will take effect when the session is re-created state = dict((k, v) for k, v in state.iteritems() if v is not None) return state def output(self): if self.cur_event['type'] != 'form-complete': #warn that not at end of form pass instance_bytes = FormSerializer().serializeInstance(self.form.getInstance()) return unicode(''.join(chr(b) for b in instance_bytes.tolist()), 'utf-8') def walk(self): form_ix = FormIndex.createBeginningOfFormIndex() tree = [] self._walk(form_ix, tree) return tree def _walk(self, parent_ix, siblings): def step(ix, descend): next_ix = self.fem.incrementIndex(ix, descend) self.fem.setQuestionIndex(next_ix) # needed to trigger events in form engine return next_ix def ix_in_scope(form_ix): if form_ix.isEndOfFormIndex(): return False elif parent_ix.isBeginningOfFormIndex(): return True else: return FormIndex.isSubElement(parent_ix, form_ix) form_ix = step(parent_ix, True) while ix_in_scope(form_ix): relevant = self.fem.isIndexRelevant(form_ix) if not relevant: form_ix = step(form_ix, False) continue evt = self.__parse_event(form_ix) evt['relevant'] = relevant if evt['type'] == 'sub-group': presentation_group = (evt['caption'] != None) if presentation_group: siblings.append(evt) evt['children'] = [] form_ix = self._walk(form_ix, evt['children'] if presentation_group else siblings) elif evt['type'] == 'repeat-juncture': siblings.append(evt) evt['children'] = [] for i in range(0, self.fem.getForm().getNumRepetitions(form_ix)): subevt = { 'type': 'sub-group', 'ix': self.fem.getForm().descendIntoRepeat(form_ix, i), 'caption': evt['repetitions'][i], 'repeatable': True, 'children': [], } # kinda ghetto; we need to be able to track distinct repeat instances, even if their position # within the list of repetitions changes (such as by deleting a rep in the middle) # would be nice to have proper FormEntryAPI support for this java_uid = self.form.getInstance().resolveReference(subevt['ix'].getReference()).hashCode() subevt['uuid'] = hashlib.sha1(str(java_uid)).hexdigest()[:12] evt['children'].append(subevt) self._walk(subevt['ix'], subevt['children']) for key in ['repetitions', 'del-choice', 'del-header', 'done-choice']: del evt[key] form_ix = step(form_ix, True) # why True? else: siblings.append(evt) form_ix = step(form_ix, True) # why True? return form_ix def _parse_current_event(self): self.cur_event = self.__parse_event(self.fem.getFormIndex()) return self.cur_event def __parse_event (self, form_ix): event = {'ix': form_ix} status = self.fem.getEvent(form_ix) if status == self.fec.EVENT_BEGINNING_OF_FORM: event['type'] = 'form-start' elif status == self.fec.EVENT_END_OF_FORM: event['type'] = 'form-complete' elif status == self.fec.EVENT_QUESTION: event['type'] = 'question' self._parse_question(event) elif status == self.fec.EVENT_REPEAT_JUNCTURE: event['type'] = 'repeat-juncture' self._parse_repeat_juncture(event) else: event['type'] = 'sub-group' event['caption'] = self.fem.getCaptionPrompt(form_ix).getLongText() if status == self.fec.EVENT_GROUP: event['repeatable'] = False elif status == self.fec.EVENT_REPEAT: event['repeatable'] = True event['exists'] = True elif status == self.fec.EVENT_PROMPT_NEW_REPEAT: #obsolete event['repeatable'] = True event['exists'] = False return event def _get_question_choices(self, q): return [choice(q, ch) for ch in q.getSelectChoices()] def _parse_style_info(self, rawstyle): info = {} if rawstyle != None: info['raw'] = rawstyle try: info.update([[p.strip() for p in f.split(':')][:2] for f in rawstyle.split(';') if f.strip()]) except ValueError: pass return info def _parse_question (self, event): q = self.fem.getQuestionPrompt(event['ix']) event['caption'] = q.getLongText() event['help'] = q.getHelpText() event['style'] = self._parse_style_info(q.getAppearanceHint()) event['binding'] = q.getQuestion().getBind().getReference().toString() if q.getControlType() == Constants.CONTROL_TRIGGER: event['datatype'] = 'info' else: try: event['datatype'] = { Constants.DATATYPE_NULL: 'str', Constants.DATATYPE_TEXT: 'str', Constants.DATATYPE_INTEGER: 'int', Constants.DATATYPE_LONG: 'longint', Constants.DATATYPE_DECIMAL: 'float', Constants.DATATYPE_DATE: 'date', Constants.DATATYPE_TIME: 'time', Constants.DATATYPE_CHOICE: 'select', Constants.DATATYPE_CHOICE_LIST: 'multiselect', Constants.DATATYPE_GEOPOINT: 'geo', # not supported yet Constants.DATATYPE_DATE_TIME: 'datetime', Constants.DATATYPE_BARCODE: 'barcode', Constants.DATATYPE_BINARY: 'binary', }[q.getDataType()] except KeyError: event['datatype'] = 'unrecognized' if event['datatype'] in ('select', 'multiselect'): event['choices'] = self._get_question_choices(q) event['required'] = q.isRequired() value = q.getAnswerValue() if value == None: event['answer'] = None elif event['datatype'] in ('int', 'float', 'str', 'longint'): event['answer'] = value.getValue() elif event['datatype'] == 'date': event['answer'] = to_pdate(value.getValue()) elif event['datatype'] == 'time': event['answer'] = to_ptime(value.getValue()) elif event['datatype'] == 'select': event['answer'] = choice(q, selection=value.getValue()).ordinal() elif event['datatype'] == 'multiselect': event['answer'] = [choice(q, selection=sel).ordinal() for sel in value.getValue()] elif event['datatype'] == 'geo': event['answer'] = list(value.getValue())[:2] def _parse_repeat_juncture(self, event): r = self.fem.getCaptionPrompt(event['ix']) ro = r.getRepeatOptions() event['main-header'] = ro.header event['repetitions'] = list(r.getRepetitionsText()) event['add-choice'] = ro.add event['del-choice'] = ro.delete event['del-header'] = ro.delete_header event['done-choice'] = ro.done def next_event (self): self.fec.stepToNextEvent() return self._parse_current_event() def back_event (self): self.fec.stepToPreviousEvent() return self._parse_current_event() def answer_question (self, answer, _ix=None): ix = self.parse_ix(_ix) event = self.cur_event if ix is None else self.__parse_event(ix) if event['type'] != 'question': raise ValueError('not currently on a question') datatype = event['datatype'] if datatype == 'unrecognized': # don't commit answers to unrecognized questions, since we # couldn't parse what was originally there. whereas for # _unsupported_ questions, we're parsing and re-committing the # answer verbatim return {'status': 'success'} def multians(a): if hasattr(a, '__iter__'): return a else: return str(a).split() if answer == None or str(answer).strip() == '' or answer == []: ans = None elif datatype == 'int': ans = IntegerData(int(answer)) elif datatype == 'longint': ans = LongData(int(answer)) elif datatype == 'float': ans = DecimalData(float(answer)) elif datatype == 'str' or datatype == 'info': ans = StringData(str(answer)) elif datatype == 'date': ans = DateData(to_jdate(datetime.strptime(str(answer), '%Y-%m-%d').date())) elif datatype == 'time': ans = TimeData(to_jtime(datetime.strptime(str(answer), '%H:%M').time())) elif datatype == 'select': ans = SelectOneData(event['choices'][int(answer) - 1].to_sel()) elif datatype == 'multiselect': ans = SelectMultiData(to_vect(event['choices'][int(k) - 1].to_sel() for k in multians(answer))) elif datatype == 'geo': ans = GeoPointData(to_arr((float(x) for x in multians(answer)), 'd')) result = self.fec.answerQuestion(*([ans] if ix is None else [ix, ans])) if result == self.fec.ANSWER_REQUIRED_BUT_EMPTY: return {'status': 'error', 'type': 'required'} elif result == self.fec.ANSWER_CONSTRAINT_VIOLATED: q = self.fem.getQuestionPrompt(*([] if ix is None else [ix])) return {'status': 'error', 'type': 'constraint', 'reason': q.getConstraintText()} elif result == self.fec.ANSWER_OK: return {'status': 'success'} def descend_repeat (self, rep_ix=None, _junc_ix=None): junc_ix = self.parse_ix(_junc_ix) if (junc_ix): self.fec.jumpToIndex(junc_ix) if rep_ix: self.fec.descendIntoRepeat(rep_ix - 1) else: self.fec.descendIntoNewRepeat() return self._parse_current_event() def delete_repeat (self, rep_ix, _junc_ix=None): junc_ix = self.parse_ix(_junc_ix) if (junc_ix): self.fec.jumpToIndex(junc_ix) self.fec.deleteRepeat(rep_ix - 1) return self._parse_current_event() #sequential (old-style) repeats only def new_repetition (self): #currently in the form api this always succeeds, but theoretically there could #be unsatisfied constraints that make it fail. how to handle them here? self.fec.newRepeat(self.fem.getFormIndex()) def set_locale(self, lang): self.fec.setLanguage(lang) return self._parse_current_event() def get_locales(self): return self.fem.getLanguages() or [] def finalize(self): self.fem.getForm().postProcessInstance() def parse_ix(self, s_ix): return index_from_str(s_ix, self.form) def form_title(self): return self.form.getTitle() def response(self, resp, ev_next=None, no_next=False): if no_next: navinfo = {} elif self.nav_mode == 'prompt': if ev_next is None: ev_next = next_event(self) navinfo = {'event': ev_next} elif self.nav_mode == 'fao': navinfo = {'tree': self.walk()} if DEBUG: print '=== walking ===' print_tree(self.walk()) print '===============' resp.update(navinfo) resp.update({'seq_id': self.seq_id}) return resp
class XFormSession(object): def __init__(self, xform, instance=None, **params): self.uuid = params.get("uuid", uuid.uuid4().hex) self.nav_mode = params.get("nav_mode", "prompt") self.seq_id = params.get("seq_id", 0) self.uses_sql_backend = params.get("uses_sql_backend") self.form = load_form( xform, instance, params.get("extensions", []), params.get("session_data", {}), params.get("api_auth"), params.get("form_context", None), self.uses_sql_backend, ) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if params.get("init_lang"): try: self.fec.setLanguage(params.get("init_lang")) except UnregisteredLocaleException: pass # just use default language if params.get("cur_index"): self.fec.jumpToIndex(self.parse_ix(params.get("cur_index"))) self._parse_current_event() self.staleness_window = 3600.0 * params["staleness_window"] self.persist = params.get("persist", settings.PERSIST_SESSIONS) self.orig_params = { "xform": xform, "nav_mode": params.get("nav_mode"), "session_data": params.get("session_data"), "api_auth": params.get("api_auth"), "staleness_window": params["staleness_window"], } self.update_last_activity() def _assert_locked(self): if not global_state.get_lock(self.uuid).locked(): # note that this isn't a perfect check that we have the lock # but is hopefully a good enough proxy. this is basically an # assertion. raise Exception("Tried to update XFormSession without the lock!") def __enter__(self): self._assert_locked() self.seq_id += 1 self.update_last_activity() return self def __exit__(self, exc_type, exc_val, exc_tb): self._assert_locked() if self.persist: # TODO should this be done async? we must dump state before releasing the lock, however persistence.persist(self) def update_last_activity(self): self.last_activity = time.time() def session_state(self): state = dict(self.orig_params) state.update( { "instance": self.output(), "init_lang": self.get_lang(), "cur_index": str(self.fem.getFormIndex()) if self.nav_mode != "fao" else None, "seq_id": self.seq_id, } ) # prune entries with null value, so that defaults will take effect when the session is re-created state = dict((k, v) for k, v in state.iteritems() if v is not None) return state def get_xmlns(self): """ :return: the xmlns header for the instance of the current session """ metadata = self.fem.getForm().getMainInstance().getMetaData() return metadata.get("XMLNS") def evaluate_xpath(self, xpath): """ :param xpath: the xpath expression to be evaluated EG "/data/question1" :return: the value stored in the referenced path at the moment only supports single values IE won't return nodesets We use the JavaRosa "Text" type as a factory to turn our String into and XPath """ evaluation_context = self.fem.getForm().exprEvalContext args = JHashtable() result = {} try: m_text = JRText.XPathText(xpath, args) result["status"] = "success" result["output"] = m_text.evaluate(evaluation_context) except XPathException, e: result["status"] = "failure" result["output"] = e.getMessage() return result
class XFormSession(object): def __init__(self, xform, instance=None, **params): self.uuid = params.get('uuid', uuid.uuid4().hex) self.nav_mode = params.get('nav_mode', 'prompt') self.seq_id = params.get('seq_id', 0) self.uses_sql_backend = params.get('uses_sql_backend') self.form = load_form( xform, instance, params.get('extensions', []), params.get('session_data', {}), params.get('api_auth'), params.get('form_context', None), self.uses_sql_backend, ) self.fem = FormEntryModel(self.form, FormEntryModel.REPEAT_STRUCTURE_NON_LINEAR) self.fec = FormEntryController(self.fem) if params.get('init_lang'): try: self.fec.setLanguage(params.get('init_lang')) except UnregisteredLocaleException: pass # just use default language if params.get('cur_index'): self.fec.jumpToIndex(self.parse_ix(params.get('cur_index'))) self._parse_current_event() self.staleness_window = 3600. * params['staleness_window'] self.persist = params.get('persist', settings.PERSIST_SESSIONS) self.orig_params = { 'xform': xform, 'nav_mode': params.get('nav_mode'), 'session_data': params.get('session_data'), 'api_auth': params.get('api_auth'), 'staleness_window': params['staleness_window'], } self.update_last_activity() def _assert_locked(self): if not global_state.get_lock(self.uuid).locked(): # note that this isn't a perfect check that we have the lock # but is hopefully a good enough proxy. this is basically an # assertion. raise Exception('Tried to update XFormSession without the lock!') def __enter__(self): self._assert_locked() self.seq_id += 1 self.update_last_activity() return self def __exit__(self, exc_type, exc_val, exc_tb): self._assert_locked() if self.persist: # TODO should this be done async? we must dump state before releasing the lock, however persistence.persist(self) def update_last_activity(self): self.last_activity = time.time() def session_state(self): state = dict(self.orig_params) state.update({ 'instance': self.output(), 'init_lang': self.get_lang(), 'cur_index': str(self.fem.getFormIndex()) if self.nav_mode != 'fao' else None, 'seq_id': self.seq_id, }) # prune entries with null value, so that defaults will take effect when the session is re-created state = dict((k, v) for k, v in state.iteritems() if v is not None) return state def get_xmlns(self): """ :return: the xmlns header for the instance of the current session """ metadata = self.fem.getForm().getMainInstance().getMetaData() return metadata.get("XMLNS") def evaluate_xpath(self, xpath): """ :param xpath: the xpath expression to be evaluated EG "/data/question1" :return: the value stored in the referenced path at the moment only supports single values IE won't return nodesets We use the JavaRosa "Text" type as a factory to turn our String into and XPath """ evaluation_context = self.fem.getForm().exprEvalContext args = JHashtable() result = {} try: m_text = JRText.XPathText(xpath, args) result['status'] = 'success' result['output'] = m_text.evaluate(evaluation_context) except XPathException, e: result['status'] = 'failure' result['output'] = e.getMessage() return result