예제 #1
0
        def _further_deduplicate(action_list):
            def actions_match(a1, a2):
                # if everything but the server_date match, the actions match.
                # this will allow for multiple case blocks to be submitted
                # against the same case in the same form so long as they
                # are different
                a1doc = copy.copy(a1._doc)
                a2doc = copy.copy(a2._doc)
                a2doc['server_date'] = a1doc['server_date']
                a2doc['date'] = a1doc['date']
                return a1doc == a2doc

            ret = []
            for a in action_list:
                found_actions = [
                    other for other in ret if actions_match(a, other)
                ]
                if found_actions:
                    if len(found_actions) != 1:
                        error = (u"Case {0} action conflicts "
                                 u"with multiple other actions: {1}")
                        raise ReconciliationError(error.format(self.get_id, a))
                    match = found_actions[0]
                    # when they disagree, choose the _earlier_ one as this is
                    # the one that is likely timestamped with the form's date
                    # (and therefore being processed later in absolute time)
                    ret[ret.index(
                        match
                    )] = a if a.server_date < match.server_date else match
                else:
                    ret.append(a)
            return ret
예제 #2
0
 def _check_preconditions():
     error = None
     for a in self.actions:
         if a.server_date is None:
             error = u"Case {0} action server_date is None: {1}"
         elif a.xform_id is None:
             error = u"Case {0} action xform_id is None: {1}"
         if error:
             raise ReconciliationError(error.format(self.get_id, a))
예제 #3
0
    def rebuild(self, strict=True):
        """
        Rebuilds the case state from its actions.

        If strict is True, this will enforce that the first action must be a create.

        TODO: this implementation has a number of flaws:
          - it starts from whatever the cases current state is,
            not a clean slate
          - it simply ignores all case create blocks,
            except to report whether they're in order;
            it does not apply their changes.

        """
        # try to re-sort actions if necessary
        try:
            self.actions = sorted(self.actions,
                                  key=_action_sort_key_function(self))
        except MissingServerDate:
            # only worry date reconciliation if in strict mode
            if strict:
                raise

        actions = copy.deepcopy(list(self.actions))
        if strict:
            if actions[0].action_type != const.CASE_ACTION_CREATE:
                error = u"Case {0} first action not create action: {1}"
                raise ReconciliationError(
                    error.format(self.get_id, self.actions[0]))
            actions.pop(0)
        else:
            actions = [
                a for a in actions if a.action_type != const.CASE_ACTION_CREATE
            ]

        for a in actions:
            self._apply_action(a)

        self.xform_ids = []
        for a in self.actions:
            if a.xform_id not in self.xform_ids:
                self.xform_ids.append(a.xform_id)
예제 #4
0
    def rebuild(self, strict=True, xforms=None):
        """
        Rebuilds the case state in place from its actions.

        If strict is True, this will enforce that the first action must be a create.
        """
        from casexml.apps.case.cleanup import reset_state

        xforms = xforms or {}
        reset_state(self)
        # try to re-sort actions if necessary
        try:
            self.actions = sorted(self.actions,
                                  key=_action_sort_key_function(self))
        except MissingServerDate:
            # only worry date reconciliation if in strict mode
            if strict:
                raise

        # remove all deprecated actions during rebuild.
        self.actions = [a for a in self.actions if not a.deprecated]
        actions = copy.deepcopy(list(self.actions))

        if strict:
            if actions[0].action_type != const.CASE_ACTION_CREATE:
                error = u"Case {0} first action not create action: {1}"
                raise ReconciliationError(
                    error.format(self.get_id, self.actions[0]))

        for a in actions:
            self._apply_action(a, xforms.get(a.xform_id))

        self.xform_ids = []
        for a in self.actions:
            if a.xform_id and a.xform_id not in self.xform_ids:
                self.xform_ids.append(a.xform_id)
예제 #5
0
    def reconcile_actions(self, rebuild=False, xforms=None):
        """
        Runs through the action list and tries to reconcile things that seem
        off (for example, out-of-order submissions, duplicate actions, etc.).

        This method raises a ReconciliationError if anything goes wrong.
        """
        def _check_preconditions():
            error = None
            for a in self.actions:
                if a.server_date is None:
                    error = u"Case {0} action server_date is None: {1}"
                elif a.xform_id is None:
                    error = u"Case {0} action xform_id is None: {1}"
                if error:
                    raise ReconciliationError(error.format(self.get_id, a))

        _check_preconditions()

        # this would normally work except we only recently started using the
        # form timestamp as the modification date so we have to do something
        # fancier to deal with old data
        deduplicated_actions = list(set(self.actions))

        def _further_deduplicate(action_list):
            def actions_match(a1, a2):
                # if everything but the server_date match, the actions match.
                # this will allow for multiple case blocks to be submitted
                # against the same case in the same form so long as they
                # are different
                a1doc = copy.copy(a1._doc)
                a2doc = copy.copy(a2._doc)
                a2doc['server_date'] = a1doc['server_date']
                a2doc['date'] = a1doc['date']
                return a1doc == a2doc

            ret = []
            for a in action_list:
                found_actions = [
                    other for other in ret if actions_match(a, other)
                ]
                if found_actions:
                    if len(found_actions) != 1:
                        error = (u"Case {0} action conflicts "
                                 u"with multiple other actions: {1}")
                        raise ReconciliationError(error.format(self.get_id, a))
                    match = found_actions[0]
                    # when they disagree, choose the _earlier_ one as this is
                    # the one that is likely timestamped with the form's date
                    # (and therefore being processed later in absolute time)
                    ret[ret.index(
                        match
                    )] = a if a.server_date < match.server_date else match
                else:
                    ret.append(a)
            return ret

        deduplicated_actions = _further_deduplicate(deduplicated_actions)
        sorted_actions = sorted(deduplicated_actions,
                                key=_action_sort_key_function(self))
        if sorted_actions:
            if sorted_actions[0].action_type != const.CASE_ACTION_CREATE:
                error = u"Case {0} first action not create action: {1}"
                raise ReconciliationError(
                    error.format(self.get_id, sorted_actions[0]))
        self.actions = sorted_actions
        if rebuild:
            # it's pretty important not to block new case changes
            # just because previous case changes have been bad
            self.rebuild(strict=False, xforms=xforms)