Пример #1
0
    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()
Пример #2
0
    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()
Пример #3
0
    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()
Пример #4
0
    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()
Пример #5
0
    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()
Пример #6
0
    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()
Пример #7
0
    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()
Пример #8
0
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
Пример #9
0
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
Пример #10
0
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
Пример #11
0
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
Пример #12
0
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