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)
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, )
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, )
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
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)
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
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
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
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
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)