Ejemplo n.º 1
0
def get_stock_actions(xform):
    if is_device_report(xform):
        return _empty_actions()

    stock_report_helpers = list(unpack_commtrack(xform))
    transaction_helpers = [
        transaction_helper
        for stock_report_helper in stock_report_helpers
        for transaction_helper in stock_report_helper.transactions
    ]
    if not transaction_helpers:
        return _empty_actions()

    # list of cases that had stock reports in the form
    case_ids = list(set(transaction_helper.case_id
                        for transaction_helper in transaction_helpers))

    user_id = xform.form['meta']['userID']
    submit_time = xform['received_on']
    case_action_intents = []

    for case_id in case_ids:
        if is_deprecation(xform):
            case_action_intents.append(CaseActionIntent(
                case_id=case_id, form_id=xform.orig_id, is_deprecation=True, action=None
            ))
        else:
            case_action = CommCareCaseAction.from_parsed_action(
                submit_time, user_id, xform, AbstractAction(CASE_ACTION_COMMTRACK)
            )
            case_action_intents.append(CaseActionIntent(
                case_id=case_id, form_id=xform._id, is_deprecation=False, action=case_action
            ))

    return StockFormActions(stock_report_helpers, case_action_intents)
Ejemplo n.º 2
0
 def make_from_form(cls, form, timestamp, tag, transactions):
     deprecated = is_deprecation(form)
     return cls(
         domain=form.domain,
         form_id=form.get_id if not deprecated else form.orig_id,
         timestamp=timestamp,
         tag=tag,
         transactions=transactions,
         server_date=form.received_on,
         deprecated=deprecated,
     )
Ejemplo n.º 3
0
def process_stock(xforms, case_db=None):
    """
    process the commtrack xml constructs in an incoming submission
    """
    case_db = case_db or CaseDbCache()
    assert isinstance(case_db, CaseDbCache)

    sorted_forms = sorted(xforms, key=lambda f: 0 if is_deprecation(f) else 1)
    stock_report_helpers = []
    case_action_intents = []
    for xform in sorted_forms:
        actions_for_form = get_stock_actions(xform)
        stock_report_helpers += actions_for_form.stock_report_helpers
        case_action_intents += actions_for_form.case_action_intents

    # omitted: normalize_transactions (used for bulk requisitions?)

    # validate the parsed transactions
    for stock_report_helper in stock_report_helpers:
        stock_report_helper.validate()

    relevant_cases = []
    # touch every case for proper ota restore logic syncing to be preserved
    for action_intent in case_action_intents:
        case_id = action_intent.case_id
        case = case_db.get(action_intent.case_id)
        relevant_cases.append(case)
        if case is None:
            raise IllegalCaseId(
                _('Ledger transaction references invalid Case ID "{}"')
                .format(case_id))

        if action_intent.is_deprecation:
            # just remove the old stock actions for the form from the case
            case.actions = [
                a for a in case.actions if not
                (a.xform_id == action_intent.form_id and a.action_type == CASE_ACTION_COMMTRACK)
            ]
        else:
            case_action = action_intent.action
            # hack: clear the sync log id so this modification always counts
            # since consumption data could change server-side
            case_action.sync_log_id = ''
            case.actions.append(case_action)

        case_db.mark_changed(case)

    return StockProcessingResult(
        xform=xform,
        relevant_cases=relevant_cases,
        stock_report_helpers=stock_report_helpers,
    )
Ejemplo n.º 4
0
 def from_case_update(cls, case_update, xformdoc):
     """
     Create a case object from a case update object.
     """
     assert not is_deprecation(xformdoc)  # you should never be able to create a case from a deleted update
     case = cls()
     case._id = case_update.id
     case.modified_on = parsing.string_to_utc_datetime(case_update.modified_on_str) \
                         if case_update.modified_on_str else datetime.utcnow()
     
     # apply initial updates, if present
     case.update_from_case_update(case_update, xformdoc)
     return case
Ejemplo n.º 5
0
def _get_or_update_cases(xforms, case_db):
    """
    Given an xform document, update any case blocks found within it,
    returning a dictionary mapping the case ids affected to the
    couch case document objects
    """
    # have to apply the deprecations before the updates
    sorted_forms = sorted(xforms, key=lambda f: 0 if is_deprecation(f) else 1)
    touched_cases = {}
    for xform in sorted_forms:
        for case_update in get_case_updates(xform):
            case_doc = _get_or_update_model(case_update, xform, case_db)
            touched_cases[case_doc['_id']] = case_doc
            if case_doc:
                # todo: legacy behavior, should remove after new case processing
                # is fully enabled.
                if xform._id not in case_doc.xform_ids:
                    case_doc.xform_ids.append(xform.get_id)
            else:
                logging.error(
                    "XForm %s had a case block that wasn't able to create a case! "
                    "This usually means it had a missing ID" % xform.get_id)

    # once we've gotten through everything, validate all indices
    # and check for new dirtiness flags
    def _validate_indices(case):
        dirtiness_flags = []
        is_dirty = False
        if case.indices:
            for index in case.indices:
                # call get and not doc_exists to force domain checking
                # see CaseDbCache.validate_doc
                referenced_case = case_db.get(index.referenced_id)
                if not referenced_case:
                    # just log, don't raise an error or modify the index
                    logging.error(
                        "Case '%s' references non-existent case '%s'",
                        case.get_id,
                        index.referenced_id,
                    )
                else:
                    if referenced_case.owner_id != case.owner_id:
                        is_dirty = True
        if is_dirty:
            dirtiness_flags.append(DirtinessFlag(case._id, case.owner_id))
        return dirtiness_flags

    def _get_dirtiness_flags_for_child_cases(domain, cases):
        child_cases = get_reverse_indexed_cases(domain,
                                                [c['_id'] for c in cases])
        case_owner_map = dict((case._id, case.owner_id) for case in cases)
Ejemplo n.º 6
0
    def from_case_update(cls, case_update, xformdoc):
        """
        Create a case object from a case update object.
        """
        assert not is_deprecation(
            xformdoc
        )  # you should never be able to create a case from a deleted update
        case = cls()
        case._id = case_update.id
        case.modified_on = parsing.string_to_utc_datetime(case_update.modified_on_str) \
                            if case_update.modified_on_str else datetime.utcnow()

        # apply initial updates, if present
        case.update_from_case_update(case_update, xformdoc)
        return case
Ejemplo n.º 7
0
    def update_from_case_update(self, case_update, xformdoc, other_forms=None):
        if case_update.has_referrals():
            logging.error(
                'Form {} touching case {} in domain {} is still using referrals'
                .format(xformdoc._id, case_update.id,
                        getattr(xformdoc, 'domain', None)))
            raise UsesReferrals(_('Sorry, referrals are no longer supported!'))

        if is_deprecation(xformdoc):
            # Mark all of the form actions as deprecated. These will get removed on rebuild.
            # This assumes that there is a second update coming that will actually
            # reapply the equivalent actions from the form that caused the current
            # one to be deprecated (which is what happens in form processing).
            for a in self.actions:
                if a.xform_id == xformdoc.orig_id:
                    a.deprecated = True

            # short circuit the rest of this since we don't actually want to
            # do any case processing
            return
        elif is_override(xformdoc):
            # This form is overriding a deprecated form.
            # Apply the actions just after the last action with this form type.
            # This puts the overriding actions in the right order relative to the others.
            prior_actions = [
                a for a in self.actions if a.xform_id == xformdoc._id
            ]
            if prior_actions:
                action_insert_pos = self.actions.index(prior_actions[-1]) + 1
                # slice insertion
                # http://stackoverflow.com/questions/7376019/python-list-extend-to-index/7376026#7376026
                self.actions[action_insert_pos:
                             action_insert_pos] = case_update.get_case_actions(
                                 xformdoc)
            else:
                self.actions.extend(case_update.get_case_actions(xformdoc))
        else:
            # normal form - just get actions and apply them on the end
            self.actions.extend(case_update.get_case_actions(xformdoc))

        # rebuild the case
        local_forms = {xformdoc._id: xformdoc}
        local_forms.update(other_forms or {})
        self.rebuild(strict=False, xforms=local_forms)

        if case_update.version:
            self.version = case_update.version
Ejemplo n.º 8
0
def _get_or_update_cases(xforms, case_db):
    """
    Given an xform document, update any case blocks found within it,
    returning a dictionary mapping the case ids affected to the
    couch case document objects
    """
    # have to apply the deprecations before the updates
    sorted_forms = sorted(xforms, key=lambda f: 0 if is_deprecation(f) else 1)
    for xform in sorted_forms:
        for case_update in get_case_updates(xform):
            case_doc = _get_or_update_model(case_update, xform, case_db)
            if case_doc:
                # todo: legacy behavior, should remove after new case processing
                # is fully enabled.
                if xform._id not in case_doc.xform_ids:
                    case_doc.xform_ids.append(xform.get_id)
                case_db.set(case_doc.case_id, case_doc)
            else:
                logging.error(
                    "XForm %s had a case block that wasn't able to create a case! "
                    "This usually means it had a missing ID" % xform.get_id
                )

    # at this point we know which cases we want to update so copy this away
    # this prevents indices that end up in the cache from being added to the return value
    touched_cases = copy.copy(case_db.cache)

    # once we've gotten through everything, validate all indices
    def _validate_indices(case):
        if case.indices:
            for index in case.indices:
                # call get and not doc_exists to force domain checking
                # see CaseDbCache.validate_doc
                referenced_case = case_db.get(index.referenced_id)

                if not referenced_case:
                    # just log, don't raise an error or modify the index
                    logging.error(
                        "Case '%s' references non-existent case '%s'",
                        case.get_id,
                        index.referenced_id,
                    )

    [_validate_indices(case) for case in case_db.cache.values()]

    return touched_cases
Ejemplo n.º 9
0
    def update_from_case_update(self, case_update, xformdoc, other_forms=None):
        if case_update.has_referrals():
            logging.error('Form {} touching case {} in domain {} is still using referrals'.format(
                xformdoc._id, case_update.id, getattr(xformdoc, 'domain', None))
            )
            raise UsesReferrals(_('Sorry, referrals are no longer supported!'))

        if is_deprecation(xformdoc):
            # Mark all of the form actions as deprecated. These will get removed on rebuild.
            # This assumes that there is a second update coming that will actually
            # reapply the equivalent actions from the form that caused the current
            # one to be deprecated (which is what happens in form processing).
            for a in self.actions:
                if a.xform_id == xformdoc.orig_id:
                    a.deprecated = True

            # short circuit the rest of this since we don't actually want to
            # do any case processing
            return
        elif is_override(xformdoc):
            # This form is overriding a deprecated form.
            # Apply the actions just after the last action with this form type.
            # This puts the overriding actions in the right order relative to the others.
            prior_actions = [a for a in self.actions if a.xform_id == xformdoc._id]
            if prior_actions:
                action_insert_pos = self.actions.index(prior_actions[-1]) + 1
                # slice insertion
                # http://stackoverflow.com/questions/7376019/python-list-extend-to-index/7376026#7376026
                self.actions[action_insert_pos:action_insert_pos] = case_update.get_case_actions(xformdoc)
            else:
                self.actions.extend(case_update.get_case_actions(xformdoc))
        else:
            # normal form - just get actions and apply them on the end
            self.actions.extend(case_update.get_case_actions(xformdoc))

        # rebuild the case
        local_forms = {xformdoc._id: xformdoc}
        local_forms.update(other_forms or {})
        self.rebuild(strict=False, xforms=local_forms)

        if case_update.version:
            self.version = case_update.version
Ejemplo n.º 10
0
def _get_or_update_cases(xforms, case_db):
    """
    Given an xform document, update any case blocks found within it,
    returning a dictionary mapping the case ids affected to the
    couch case document objects
    """
    # have to apply the deprecations before the updates
    sorted_forms = sorted(xforms, key=lambda f: 0 if is_deprecation(f) else 1)
    touched_cases = {}
    for xform in sorted_forms:
        for case_update in get_case_updates(xform):
            case_doc = _get_or_update_model(case_update, xform, case_db)
            touched_cases[case_doc['_id']] = case_doc
            if case_doc:
                # todo: legacy behavior, should remove after new case processing
                # is fully enabled.
                if xform._id not in case_doc.xform_ids:
                    case_doc.xform_ids.append(xform.get_id)
            else:
                logging.error(
                    "XForm %s had a case block that wasn't able to create a case! "
                    "This usually means it had a missing ID" % xform.get_id
                )

    # once we've gotten through everything, validate all indices
    # and check for new dirtiness flags
    def _validate_indices(case):
        dirtiness_flags = []
        is_dirty = False
        if case.indices:
            for index in case.indices:
                # call get and not doc_exists to force domain checking
                # see CaseDbCache.validate_doc
                referenced_case = case_db.get(index.referenced_id)
                if not referenced_case:
                    # just log, don't raise an error or modify the index
                    logging.error(
                        "Case '%s' references non-existent case '%s'",
                        case.get_id,
                        index.referenced_id,
                    )
                else:
                    if referenced_case.owner_id != case.owner_id:
                        is_dirty = True
        if is_dirty:
            dirtiness_flags.append(DirtinessFlag(case._id, case.owner_id))
        return dirtiness_flags

    def _get_dirtiness_flags_for_child_cases(domain, cases):
        child_cases = get_reverse_indexed_cases(domain, [c['_id'] for c in cases])
        case_owner_map = dict((case._id, case.owner_id) for case in cases)
        for child_case in child_cases:
            for index in child_case.indices:
                if (index.referenced_id in case_owner_map
                        and child_case.owner_id != case_owner_map[index.referenced_id]):
                    yield DirtinessFlag(child_case._id, child_case.owner_id)

    dirtiness_flags = [flag for case in touched_cases.values() for flag in _validate_indices(case)]
    domain = getattr(case_db, 'domain', None)
    track_cleanliness = should_track_cleanliness(domain)
    if track_cleanliness:
        # only do this extra step if the toggle is enabled since we know we aren't going to
        # care about the dirtiness flags otherwise.
        dirtiness_flags += list(_get_dirtiness_flags_for_child_cases(domain, touched_cases.values()))

    return CaseProcessingResult(domain, touched_cases.values(), dirtiness_flags, track_cleanliness)