def property_changed_in_action(domain, case_transaction, case_id, case_property_name): from casexml.apps.case.xform import get_case_updates PropertyChangedInfo = namedtuple("PropertyChangedInfo", 'transaction new_value modified_on') include_create_fields = case_property_name in ['owner_id', 'name', 'external_id'] if not should_use_sql_backend(domain): # couch domains return 2 transactions for case properties created in a create form if case_transaction.is_case_create and not include_create_fields: return False case_updates = get_case_updates(case_transaction.form) actions = [] for update in case_updates: if update.id == case_id: actions.append((update.modified_on_str, update.get_update_action(), case_transaction)) if include_create_fields and case_transaction.is_case_create: actions.append((update.modified_on_str, update.get_create_action(), case_transaction)) for (modified_on, action, case_transaction) in actions: if action: property_changed = action.dynamic_properties.get(case_property_name) if include_create_fields and not property_changed: property_changed = getattr(action, case_property_name, None) if property_changed is not None: return PropertyChangedInfo(case_transaction, property_changed, modified_on) return False
def create_case(case_id, files, patient_case_id=None): """ Handle case submission for the sonosite endpoint """ # we already parsed what we need from this, so can just remove it # without worrying we will need it later files.pop('PT_PPS.XML', '') xform = render_sonosite_xform(files, case_id, patient_case_id) file_dict = {} for f in files: file_dict[f] = UploadedFile(files[f], f) submit_form_locally( instance=xform, attachments=file_dict, domain=UTH_DOMAIN, ) # this is a bit of a hack / abstraction violation # would be nice if submit_form_locally returned info about cases updated case_ids = { case_update.id for case_update in get_case_updates(convert_xform_to_json(xform)) } return [CommCareCase.get(case_id) for case_id in case_ids]
def safe_hard_delete(case): """ Hard delete a case - by deleting the case itself as well as all forms associated with it permanently from the database. Will fail hard if the case has any reverse indices or if any of the forms associated with the case also touch other cases. This is used primarily for cleaning up system cases/actions (e.g. the location delegate case). """ if not settings.UNIT_TESTING: from corehq.apps.commtrack.const import USER_LOCATION_OWNER_MAP_TYPE if not (case.is_deleted or case.type == USER_LOCATION_OWNER_MAP_TYPE): raise CommCareCaseError("Attempt to hard delete a live case whose type isn't white listed") if case.reverse_indices: raise CommCareCaseError("You can't hard delete a case that has other dependencies ({})!".format(case.case_id)) interface = FormProcessorInterface(case.domain) forms = interface.get_case_forms(case.case_id) for form in forms: case_updates = get_case_updates(form) if any([c.id != case.case_id for c in case_updates]): raise CommCareCaseError("You can't hard delete a case that has shared forms with other cases!") interface.hard_delete_case_and_forms(case, forms)
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ touched_cases = {} if len(xforms) > 1: domain = xforms[0].domain affected_cases = set() deprecated_form = None for xform in xforms: if xform.is_deprecated: deprecated_form = xform affected_cases.update(case_update.id for case_update in get_case_updates(xform)) rebuild_detail = FormEditRebuild(deprecated_form_id=deprecated_form.form_id) for case_id in affected_cases: case = case_db.get(case_id) is_creation = False if not case: case = CommCareCaseSQL(domain=domain, case_id=case_id) is_creation = True case_db.set(case_id, case) previous_owner = case.owner_id case = FormProcessorSQL._rebuild_case_from_transactions(case, rebuild_detail, updated_xforms=xforms) if case: touched_cases[case.case_id] = CaseUpdateMetadata( case=case, is_creation=is_creation, previous_owner_id=previous_owner, )
def _fetch_case_ids_to_rebuild(self): case_ids_to_rebuild = set() for form in with_progress_bar(self.forms): form_case_ids = set(cu.id for cu in get_case_updates(form)) if form_case_ids: case_ids_to_rebuild.update(form_case_ids) return list(case_ids_to_rebuild)
def property_changed_in_action(case_transaction, case_id, case_property_name): from casexml.apps.case.xform import get_case_updates PropertyChangedInfo = namedtuple("PropertyChangedInfo", 'transaction new_value modified_on') include_create_fields = case_property_name in [ 'owner_id', 'name', 'external_id' ] case_updates = get_case_updates(case_transaction.form) actions = [] for update in case_updates: if update.id == case_id: actions.append((update.modified_on_str, update.get_update_action(), case_transaction)) if include_create_fields and case_transaction.is_case_create: actions.append((update.modified_on_str, update.get_create_action(), case_transaction)) for (modified_on, action, case_transaction) in actions: if action: property_changed = action.dynamic_properties.get( case_property_name) if include_create_fields: property_changed = getattr(action, case_property_name, None) if property_changed: return PropertyChangedInfo(case_transaction, property_changed, modified_on) return False
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ touched_cases = {} if len(xforms) > 1: domain = xforms[0].domain affected_cases = set() deprecated_form = None for xform in xforms: if xform.is_deprecated: deprecated_form = xform if not (xform.is_deprecated and xform.problem): # don't process deprecatd forms which have errors. # see http://manage.dimagi.com/default.asp?243382 for context. # note that we have to use .problem instead of .is_error because applying # the state=DEPRECATED overrides state=ERROR affected_cases.update(case_update.id for case_update in get_case_updates(xform)) rebuild_detail = FormEditRebuild(deprecated_form_id=deprecated_form.form_id) for case_id in affected_cases: case = case_db.get(case_id) is_creation = False if not case: case = CommCareCaseSQL(domain=domain, case_id=case_id) is_creation = True case_db.set(case_id, case) previous_owner = case.owner_id case = FormProcessorSQL._rebuild_case_from_transactions(case, rebuild_detail, updated_xforms=xforms) if case: touched_cases[case.case_id] = CaseUpdateMetadata( case=case, is_creation=is_creation, previous_owner_id=previous_owner, )
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ # have to apply the deprecations before the updates sorted_forms = sorted(xforms, key=lambda f: 0 if f.is_deprecated else 1) # don't process error forms which are being deprecated since they were never processed in the first place. # see http://manage.dimagi.com/default.asp?243382 filtered_sorted_forms = [ form for form in sorted_forms if not (form.is_deprecated and form.problem) ] touched_cases = {} for xform in filtered_sorted_forms: for case_update in get_case_updates(xform): case_update_meta = case_db.get_case_from_case_update( case_update, xform) if case_update_meta.case: touched_cases[ case_update_meta.case.case_id] = case_update_meta 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) return touched_cases
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ touched_cases = {} if len(xforms) > 1: domain = xforms[0].domain affected_cases = set() deprecated_form = None for xform in xforms: if xform.is_deprecated: deprecated_form = xform if not (xform.is_deprecated and xform.problem): # don't process deprecatd forms which have errors. # see http://manage.dimagi.com/default.asp?243382 for context. # note that we have to use .problem instead of .is_error because applying # the state=DEPRECATED overrides state=ERROR affected_cases.update(case_update.id for case_update in get_case_updates(xform)) rebuild_detail = FormEditRebuild(deprecated_form_id=deprecated_form.form_id) for case_id in affected_cases: case = case_db.get(case_id) is_creation = False if not case: case = CommCareCaseSQL(domain=domain, case_id=case_id) is_creation = True case_db.set(case_id, case) previous_owner = case.owner_id case, _ = FormProcessorSQL._rebuild_case_from_transactions( case, rebuild_detail, updated_xforms=xforms ) if case: touched_cases[case.case_id] = CaseUpdateMetadata( case=case, is_creation=is_creation, previous_owner_id=previous_owner, ) else: xform = xforms[0] for case_update in get_case_updates(xform): case_update_meta = case_db.get_case_from_case_update(case_update, xform) if case_update_meta.case: touched_cases[case_update_meta.case.case_id] = case_update_meta 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 ) return touched_cases
def rebuild_case(case_id): """ Given a case ID, rebuild the entire case state based on all existing forms referencing it. Useful when things go wrong or when you need to manually rebuild a case after archiving / deleting it """ try: case = CommCareCase.get(case_id) found = True except ResourceNotFound: case = CommCareCase() case._id = case_id found = False # clear actions, xform_ids, close state, and all dynamic properties dynamic_properties = set([k for action in case.actions for k in action.updated_unknown_properties.keys()]) for k in dynamic_properties: try: delattr(case, k) except KeyError: pass # already deleted means it was explicitly set to "deleted", # as opposed to getting set to that because it has no actions already_deleted = case.doc_type == 'CommCareCase-Deleted' and primary_actions(case) if not already_deleted: case.doc_type = 'CommCareCase' case.xform_ids = [] case.actions = [] case.closed = False case.closed_on = None case.closed_by = '' form_ids = get_case_xform_ids(case_id) forms = [fetch_and_wrap_form(id) for id in form_ids] filtered_forms = [f for f in forms if f.doc_type == "XFormInstance"] sorted_forms = sorted(filtered_forms, key=lambda f: f.received_on) for form in sorted_forms: if not found and case.domain is None: case.domain = form.domain assert form.domain == case.domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == case_id] for u in filtered_updates: case.update_from_case_update(u, form) case.xform_ids = [f._id for f in sorted_forms] if not case.xform_ids: if not found: return None # there were no more forms. 'delete' the case case.doc_type = 'CommCareCase-Deleted' # add a "rebuild" action case.actions.append(_rebuild_action()) case.save() return case
def handle(self, log_file, **options): to_archive = [] cases_affected = set() episode_case_ids = CaseAccessors(domain).get_case_ids_in_domain( "episode") if options.get('limit'): episode_case_ids = episode_case_ids[:options.get('limit')] for episode_case_id in with_progress_bar(episode_case_ids): nikshay_to_archive = [] dots_99_to_archvie = [] case_forms = FormProcessorInterface(domain).get_case_forms( episode_case_id) for form in case_forms: if form.user_id in ("system", "", None ) and form.metadata.username == "system": updates = get_case_updates(form) update_actions = [ update.get_update_action() for update in updates if update.id == episode_case_id ] for action in update_actions: if isinstance(action, CaseUpdateAction): if set(action.dynamic_properties.keys()) == { "nikshay_registered", "nikshay_error" }: nikshay_to_archive.append(form) cases_affected.add(episode_case_id) elif set(action.dynamic_properties.keys()) == { "dots_99_registered", "dots_99_error" }: cases_affected.add(episode_case_id) dots_99_to_archvie.append(form) # get_case_updates() returns the forms in the correct order, but sorting is probably a good idea in case # get_case_updates ever changes. nikshay_to_archive.sort(key=lambda f: f.received_on) dots_99_to_archvie.sort(key=lambda f: f.received_on) nikshay_to_archive = nikshay_to_archive[:-1] dots_99_to_archvie = dots_99_to_archvie[:-1] to_archive.extend(nikshay_to_archive) to_archive.extend(dots_99_to_archvie) print("Will archive {} forms".format(len(to_archive))) xform_archived.disconnect(rebuild_form_cases) with open(log_file, "w") as f: for form in with_progress_bar(to_archive): f.write(form.form_id + "\n") f.flush() if options['commit']: form.archive(user_id="remove_duplicate_forms_script") xform_archived.connect(rebuild_form_cases) print("Will rebuild {} cases".format(len(cases_affected))) for case_id in with_progress_bar(cases_affected): rebuild_case_from_forms( domain, case_id, UserRequestedRebuild(user_id="remove_duplicate_forms_script"))
def _property_changed_in_action(self, action, case_property): update_actions = [(update.modified_on_str, update.get_update_action()) for update in get_case_updates(action.form)] for (modified_on, action) in update_actions: property_changed = action.dynamic_properties.get(case_property) if property_changed: return PropertyChangedInfo(property_changed, modified_on) return False
def _apply_form_transaction(self, transaction): form = transaction.form if form: assert form.domain == self.case.domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == self.case.case_id] for case_update in filtered_updates: self._apply_case_update(case_update, form)
def _get_result_recorded_form(test): """get last form that set result_recorded to yes""" for action in reversed(test.actions): for update in get_case_updates(action.form): if (update.id == test.case_id and update.get_update_action() and update.get_update_action().dynamic_properties.get( 'result_recorded') == 'yes'): return action.form.form_data
def undo_form_edits(form_tuples, logger): cases_to_rebuild = defaultdict(set) ledgers_to_rebuild = defaultdict(set) operation_date = datetime.utcnow() for live_form, deprecated_form in form_tuples: # undo corehq.form_processor.parsers.form.apply_deprecation case_cache = CaseDbCacheSQL(live_form.domain, load_src="undo_form_edits") live_case_updates = get_case_updates(live_form) deprecated_case_updates = get_case_updates(deprecated_form) case_cache.populate( set(cu.id for cu in live_case_updates) | set(cu.id for cu in deprecated_case_updates)) deprecated_form.form_id = new_id_in_same_dbalias( deprecated_form.form_id) deprecated_form.state = XFormInstanceSQL.NORMAL deprecated_form.orig_id = None deprecated_form.edited_on = None live_form.deprecated_form_id = None live_form.received_on = live_form.edited_on live_form.edited_on = None affected_cases, affected_ledgers = update_case_transactions_for_form( case_cache, live_case_updates, deprecated_case_updates, live_form, deprecated_form) for form in (live_form, deprecated_form): form.track_create( XFormOperationSQL(user_id='system', operation=XFormOperationSQL.UUID_DATA_FIX, date=operation_date)) FormAccessorSQL.update_form(form) logger.log('Form edit undone: {}, {}({})'.format( live_form.form_id, deprecated_form.form_id, deprecated_form.original_form_id)) cases_to_rebuild[live_form.domain].update(affected_cases) ledgers_to_rebuild[live_form.domain].update(affected_ledgers) logger.log('Cases to rebuild: {}'.format(','.join(affected_cases))) logger.log('Ledgers to rebuild: {}'.format(','.join( [l.as_id() for l in affected_ledgers]))) return cases_to_rebuild, ledgers_to_rebuild
def _fetch_case_transaction_forms(self, transactions, updated_xforms=None): """ Fetch the forms for a list of transactions, caching them on each transaction :param transactions: list of ``CaseTransaction`` objects: :param updated_xforms: optional list of forms that have been changed. """ form_ids = {tx.form_id for tx in transactions if tx.form_id} updated_xforms_map = { xform.form_id: xform for xform in updated_xforms if not xform.is_deprecated } if updated_xforms else {} updated_xform_ids = set(updated_xforms_map) form_ids_to_fetch = list(form_ids - updated_xform_ids) form_load_counter("rebuild_case", self.case.domain)(len(form_ids_to_fetch)) xform_map = { form.form_id: form for form in XFormInstance.objects.get_forms_with_attachments_meta( form_ids_to_fetch) } forms_missing_transactions = list(updated_xform_ids - form_ids) for form_id in forms_missing_transactions: # Add in any transactions that aren't already present form = updated_xforms_map[form_id] case_updates = [ update for update in get_case_updates(form) if update.id == self.case.case_id ] types = [ CaseTransaction.type_from_action_type_slug(a.action_type_slug) for case_update in case_updates for a in case_update.actions ] modified_on = case_updates[0].guess_modified_on() new_transaction = CaseTransaction.form_transaction( self.case, form, modified_on, types) transactions.append(new_transaction) def get_form(form_id): if form_id in updated_xforms_map: return updated_xforms_map[form_id] try: return xform_map[form_id] except KeyError: raise XFormNotFound(form_id) for case_transaction in transactions: if case_transaction.form_id: try: case_transaction.cached_form = get_form( case_transaction.form_id) except XFormNotFound: logging.error('Form not found during rebuild: %s', case_transaction.form_id)
def _noauth_post(request, domain, app_id=None): """ This is explictly called for a submission that has secure submissions enabled, but is manually overriding the submit URL to not specify auth context. It appears to be used by demo mode. It mainly just checks that we are touching test data only in the right domain and submitting as demo_user. """ instance, _ = couchforms.get_instance_and_attachment(request) form_json = convert_xform_to_json(instance) case_updates = get_case_updates(form_json) def form_ok(form_json): try: # require new-style meta/userID (reject Meta/chw_id) if form_json['meta']['userID'] == 'demo_user': return True except (KeyError, ValueError): pass if is_device_report(form_json): return True return False def case_block_ok(case_updates): """ Check for all cases that we are submitting as demo_user and that the domain we are submitting against for any previously existing cases matches the submission domain. """ allowed_ids = ('demo_user', 'demo_user_group_id', None) case_ids = set() for case_update in case_updates: case_ids.add(case_update.id) create_action = case_update.get_create_action() update_action = case_update.get_update_action() index_action = case_update.get_index_action() if create_action: if create_action.user_id not in allowed_ids: return False if create_action.owner_id not in allowed_ids: return False if update_action: if update_action.owner_id not in allowed_ids: return False if index_action: for index in index_action.indices: case_ids.add(index.referenced_id) # todo: consider whether we want to remove this call, and/or pass the result # through to the next function so we don't have to get the cases again later cases = CommCareCase.bulk_get_lite(list(case_ids)) for case in cases: if case.domain != domain: return False if case.owner_id or case.user_id not in allowed_ids: return False
def undo_form_edits(form_tuples, logger): cases_to_rebuild = defaultdict(set) ledgers_to_rebuild = defaultdict(set) operation_date = datetime.utcnow() for live_form, deprecated_form in form_tuples: # undo corehq.form_processor.parsers.form.apply_deprecation case_cache = CaseDbCacheSQL(live_form.domain) live_case_updates = get_case_updates(live_form) deprecated_case_updates = get_case_updates(deprecated_form) case_cache.populate( set(cu.id for cu in live_case_updates) | set(cu.id for cu in deprecated_case_updates) ) deprecated_form.form_id = new_id_in_same_dbalias(deprecated_form.form_id) deprecated_form.state = XFormInstanceSQL.NORMAL deprecated_form.orig_id = None deprecated_form.edited_on = None live_form.deprecated_form_id = None live_form.received_on = live_form.edited_on live_form.edited_on = None affected_cases, affected_ledgers = update_case_transactions_for_form( case_cache, live_case_updates, deprecated_case_updates, live_form, deprecated_form ) for form in (live_form, deprecated_form): form.track_create(XFormOperationSQL( user_id='system', operation=XFormOperationSQL.UUID_DATA_FIX, date=operation_date) ) FormAccessorSQL.update_form(form) logger.log('Form edit undone: {}, {}({})'.format( live_form.form_id, deprecated_form.form_id, deprecated_form.original_form_id )) cases_to_rebuild[live_form.domain].update(affected_cases) ledgers_to_rebuild[live_form.domain].update(affected_ledgers) logger.log('Cases to rebuild: {}'.format(','.join(affected_cases))) logger.log('Ledgers to rebuild: {}'.format(','.join([l.as_id() for l in affected_ledgers]))) return cases_to_rebuild, ledgers_to_rebuild
def _cases_referenced_by_xform(esxform): """Get a list of cases referenced by ESXFormInstance Note: this does not load cases referenced in stock transactions because ESXFormInstance does not have access to form XML, which is needed to find stock transactions. """ assert esxform.domain, esxform.form_id case_ids = set(cu.id for cu in get_case_updates(esxform)) return CaseAccessors(esxform.domain).get_cases(list(case_ids))
def get_case_ids(form): """Get a set of case ids referenced in form Gracefully handles missing XML, but will omit case ids referenced in ledger updates if XML is missing. """ try: return get_case_ids_from_form(form) except MissingFormXml: return {update.id for update in get_case_updates(form)}
def _get_diagnosis_update(episode): """get first form that set diagnosis_test_type to a value """ for action in episode.actions: if action.form is not None: for update in get_case_updates(action.form): if (update.id == episode.case_id and update.get_update_action() and update.get_update_action().dynamic_properties.get( 'diagnosis_test_type', '')): return update
def person_hiv_status_changed(case): last_case_action = case.actions[-1] if last_case_action.is_case_create: return False last_update_actions = [update.get_update_action() for update in get_case_updates(last_case_action.form)] value_changed = any( action for action in last_update_actions if isinstance(action, CaseUpdateAction) and 'hiv_status' in action.dynamic_properties ) return value_changed
def property_changed_in_action(action, case_property_name): update_actions = [(update.modified_on_str, update.get_update_action()) for update in get_case_updates(action.form) if update.id == case.case_id] for (modified_on, action) in update_actions: if action: property_changed = action.dynamic_properties.get( case_property_name) if property_changed: return PropertyChangedInfo(property_changed, modified_on) return False
def _noauth_post(request, domain, app_id=None): instance, _ = couchforms.get_instance_and_attachment(request) form_json = convert_xform_to_json(instance) case_updates = get_case_updates(form_json) def form_ok(form_json): try: # require new-style meta/userID (reject Meta/chw_id) if form_json['meta']['userID'] == 'demo_user': return True except (KeyError, ValueError): pass if is_device_report(form_json): return True return False def case_block_ok(case_updates): case_ids = set() for case_update in case_updates: case_ids.add(case_update.id) create_action = case_update.get_create_action() update_action = case_update.get_update_action() index_action = case_update.get_index_action() if create_action: if create_action.user_id not in ('demo_user', None): return False if create_action.owner_id not in ('demo_user', None): return False if update_action: if update_action.owner_id not in ('demo_user', None): return False if index_action: for index in index_action.indices: case_ids.add(index.referenced_id) cases = CommCareCase.bulk_get_lite(list(case_ids)) for case in cases: if case.domain != domain: return False if case.owner_id or case.user_id != 'demo_user': return False return True if not (form_ok(form_json) and case_block_ok(case_updates)): return HttpResponseForbidden() return _process_form( request=request, domain=domain, app_id=app_id, user_id=None, authenticated=False, auth_cls=WaivedAuthContext, )
def phone_number_changed(case): last_case_action = case.actions[-1] if last_case_action.is_case_create: return False update_actions = [update.get_update_action() for update in get_case_updates(last_case_action.form)] phone_number_changed = any( action for action in update_actions if 'phone_number' in action.dynamic_properties or 'backup_number' in action.dynamic_properties ) return phone_number_changed
def get_actions_for_form(self, xform): from casexml.apps.case.xform import get_case_updates updates = [u for u in get_case_updates(xform) if u.id == self.case_id] actions = [a for update in updates for a in update.actions] normalized_actions = [ CaseAction( action_type=a.action_type_slug, updated_known_properties=a.get_known_properties(), indices=a.indices ) for a in actions ] return normalized_actions
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ touched_cases = {} if len(xforms) > 1: domain = xforms[0].domain affected_cases = set() deprecated_form = None for xform in xforms: if xform.is_deprecated: deprecated_form = xform affected_cases.update(case_update.id for case_update in get_case_updates(xform)) rebuild_detail = FormEditRebuild(deprecated_form_id=deprecated_form.form_id) for case_id in affected_cases: case = case_db.get(case_id) is_creation = False if not case: case = CommCareCaseSQL(domain=domain, case_id=case_id) is_creation = True case_db.set(case_id, case) previous_owner = case.owner_id case = FormProcessorSQL._rebuild_case_from_transactions(case, rebuild_detail, updated_xforms=xforms) if case: touched_cases[case.case_id] = CaseUpdateMetadata( case=case, is_creation=is_creation, previous_owner_id=previous_owner, ) else: xform = xforms[0] for case_update in get_case_updates(xform): case_update_meta = case_db.get_case_from_case_update(case_update, xform) if case_update_meta.case: touched_cases[case_update_meta.case.case_id] = case_update_meta 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 ) return touched_cases
def _noauth_post(request, domain, app_id=None): """ This is explicitly called for a submission that has secure submissions enabled, but is manually overriding the submit URL to not specify auth context. It appears to be used by demo mode. It mainly just checks that we are touching test data only in the right domain and submitting as demo_user. """ try: instance, _ = couchforms.get_instance_and_attachment(request) except BadSubmissionRequest as e: return HttpResponseBadRequest(e.message) form_json = convert_xform_to_json(instance) case_updates = get_case_updates(form_json) def form_ok(form_json): return (from_demo_user(form_json) or is_device_report(form_json)) def case_block_ok(case_updates): """ Check for all cases that we are submitting as demo_user and that the domain we are submitting against for any previously existing cases matches the submission domain. """ allowed_ids = ('demo_user', 'demo_user_group_id', None) case_ids = set() for case_update in case_updates: case_ids.add(case_update.id) create_action = case_update.get_create_action() update_action = case_update.get_update_action() index_action = case_update.get_index_action() if create_action: if create_action.user_id not in allowed_ids: return False if create_action.owner_id not in allowed_ids: return False if update_action: if update_action.owner_id not in allowed_ids: return False if index_action: for index in index_action.indices: case_ids.add(index.referenced_id) # todo: consider whether we want to remove this call, and/or pass the result # through to the next function so we don't have to get the cases again later cases = CommCareCase.objects.get_cases(list(case_ids), domain) for case in cases: if case.domain != domain: return False if case.owner_id or case.user_id not in allowed_ids: return False
def _get_actions_from_forms(sorted_forms, case_id): case_actions = [] domain = None for form in sorted_forms: if domain is None: domain = form.domain assert form.domain == domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == case_id] for u in filtered_updates: case_actions.extend(u.get_case_actions(form)) return case_actions, domain
def _apply_form_transaction(self, transaction): form = transaction.form if form: assert form.domain == self.case.domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == self.case.case_id] # TODO: stock # stock_actions = get_stock_actions(form) # case_actions.extend([intent.action # for intent in stock_actions.case_action_intents # if not intent.is_deprecation]) for case_update in filtered_updates: self._apply_case_update(case_update, form)
def _get_owner_id_from_transaction(self, transaction): case_updates = get_case_updates(transaction.form) update_actions = [(update.modified_on_str, update.get_update_action()) for update in case_updates] create_actions = [(update.modified_on_str, update.get_create_action()) for update in case_updates] all_actions = update_actions + create_actions # look through updates first, as these trump creates for modified_on, action in all_actions: if action: owner_id = action.get_known_properties().get('owner_id') if owner_id: return PropertyChangedInfo(owner_id, modified_on) return None
def process_change(self, change): domain = change.metadata.domain if not CASE_DEDUPE.enabled(domain): return if is_dedupe_xmlns(change.get_document().get('xmlns')): return rules = self._get_rules(domain) if not rules: return for case_update in get_case_updates(change.get_document()): self._process_case_update(domain, case_update)
def rebuild_case(case_id): """ Given a case ID, rebuild the entire case state based on all existing forms referencing it. Useful when things go wrong or when you need to manually rebuild a case after archiving / deleting it """ try: case = CommCareCase.get(case_id) found = True except ResourceNotFound: case = CommCareCase() case._id = case_id found = False reset_state(case) # in addition to resetting the state, also manually clear xform_ids and actions # since we're going to rebuild these from the forms case.xform_ids = [] case.actions = [] forms = get_case_forms(case_id) filtered_forms = [f for f in forms if f.doc_type == "XFormInstance"] sorted_forms = sorted(filtered_forms, key=lambda f: f.received_on) for form in sorted_forms: if not found and case.domain is None: case.domain = form.domain assert form.domain == case.domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == case_id] for u in filtered_updates: case.actions.extend(u.get_case_actions(form)) # call "rebuild" on the case, which should populate xform_ids # and re-sort actions if necessary case.rebuild(strict=False, xforms={f._id: f for f in sorted_forms}) case.xform_ids = case.xform_ids + [f._id for f in sorted_forms if f._id not in case.xform_ids] # todo: should this move to case.rebuild? if not case.xform_ids: if not found: return None # there were no more forms. 'delete' the case case.doc_type = 'CommCareCase-Deleted' # add a "rebuild" action case.actions.append(_rebuild_action()) case.save() return case
def raise_events(xform, cases): supply_points = [SupplyPointCase.wrap(c._doc) for c in cases if c.type == const.SUPPLY_POINT_CASE_TYPE] case_updates = get_case_updates(xform) for sp in supply_points: created = any(filter(lambda update: update.id == sp._id and update.creates_case(), case_updates)) supply_point_modified.send(sender=None, supply_point=sp, created=created) requisition_cases = [RequisitionCase.wrap(c._doc) for c in cases if c.type == const.REQUISITION_CASE_TYPE] if requisition_cases and requisition_cases[0].requisition_status == RequisitionStatus.APPROVED: requisition_approved.send(sender=None, requisitions=requisition_cases) if requisition_cases and requisition_cases[0].requisition_status == RequisitionStatus.RECEIVED: requisition_receipt.send(sender=None, requisitions=requisition_cases) if requisition_cases and requisition_cases[0].requisition_status == RequisitionStatus.REQUESTED: requisition_modified.send(sender=None, cases=requisition_cases)
def related_dates_changed(case): last_case_action = case.actions[-1] if last_case_action.is_case_create: return False last_update_actions = [ update.get_update_action() for update in get_case_updates(last_case_action.form) ] value_changed = any(action for action in last_update_actions if isinstance(action, CaseUpdateAction) and ( 'art_initiation_date' in action.dynamic_properties or 'cpt_1_date' in action.dynamic_properties)) return value_changed
def raise_supply_point_events(xform, cases): supply_points = [ SupplyPointCase.wrap(c._doc) for c in cases if c.type == SUPPLY_POINT_CASE_TYPE ] case_updates = get_case_updates(xform) for sp in supply_points: created = any( filter( lambda update: update.id == sp._id and update.creates_case(), case_updates)) supply_point_modified.send(sender=None, supply_point=sp, created=created)
def _get_actions_from_forms(domain, sorted_forms, case_id): from corehq.form_processor.parsers.ledgers import get_stock_actions case_actions = [] for form in sorted_forms: assert form.domain == domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == case_id] for u in filtered_updates: case_actions.extend(u.get_case_actions(form)) stock_actions = get_stock_actions(form) case_actions.extend([intent.get_couch_action() for intent in stock_actions.case_action_intents if not intent.is_deprecation]) return case_actions
def _get_case_stock_result(self, sql_form, couch_form): case_stock_result = None if sql_form.initial_processing_complete: case_stock_result = _get_case_and_ledger_updates( self.domain, sql_form) if case_stock_result.case_models: has_noop_update = any( len(update.actions) == 1 and isinstance(update.actions[0], CaseNoopAction) for update in get_case_updates(couch_form)) if has_noop_update: # record these for later use when filtering case diffs. # See ``_filter_forms_touch_case`` self.statedb.add_no_action_case_form(couch_form.form_id) return case_stock_result
def episode_pending_registration_changed(case): last_case_action = case.actions[-1] if last_case_action.is_case_create: return False last_update_actions = [ update.get_update_action() for update in get_case_updates(last_case_action.form) ] value_changed = any( action for action in last_update_actions if isinstance(action, CaseUpdateAction) and 'episode_pending_registration' in action.dynamic_properties and action.dynamic_properties['episode_pending_registration'] == 'no') return value_changed
def prepare_planning_db(domain): db_filepath = get_planning_db_filepath(domain) planning_db = PlanningDB.init(db_filepath) xform_ids = get_form_ids_by_type(domain, 'XFormInstance') xform_db = XFormInstance.get_db() for i, xform in enumerate(iter_docs(xform_db, xform_ids)): xform_id = xform['_id'] case_actions_by_case_id = collections.defaultdict(list) try: xml = _get_submission_xml(xform, xform_db) except ResourceNotFound: continue new_form_json = _get_new_form_json(xml, xform_id) case_updates = get_case_updates(new_form_json) xform_copy = deepcopy(xform) xform_copy['form'] = new_form_json xformdoc = XFormInstance.wrap(xform_copy) xformdoc_json = xformdoc.to_json() planning_db.add_form(xform_id, xformdoc_json) planning_db.add_diffs('form', xform_id, json_diff(xform, xformdoc_json)) case_actions = [ (case_update.id, action.xform_id, action.to_json()) for case_update in case_updates for action in case_update.get_case_actions(xformdoc) ] stock_report_helpers, stock_case_actions = get_stock_actions(xformdoc) case_actions.extend(stock_case_actions) for case_id, xform_id, case_action in case_actions: case_actions_by_case_id[case_id].append((xform_id, case_action)) for case_id, case_actions in case_actions_by_case_id.items(): planning_db.ensure_case(case_id) planning_db.add_case_actions(case_id, case_actions) planning_db.add_stock_report_helpers([ stock_report_helper.to_json() for stock_report_helper in stock_report_helpers ]) return prepare_case_json(planning_db)
def _get_actions_from_forms(sorted_forms, case_id): from corehq.apps.commtrack.processing import get_stock_actions case_actions = [] domain = None for form in sorted_forms: if domain is None: domain = form.domain assert form.domain == domain case_updates = get_case_updates(form) filtered_updates = [u for u in case_updates if u.id == case_id] for u in filtered_updates: case_actions.extend(u.get_case_actions(form)) stock_actions = get_stock_actions(form) case_actions.extend([intent.action for intent in stock_actions.case_action_intents if not intent.is_deprecation]) return case_actions, domain
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ # have to apply the deprecations before the updates sorted_forms = sorted(xforms, key=lambda f: 0 if f.is_deprecated else 1) touched_cases = {} for xform in sorted_forms: for case_update in get_case_updates(xform): case_update_meta = case_db.get_case_from_case_update(case_update, xform) if case_update_meta.case: touched_cases[case_update_meta.case.case_id] = case_update_meta 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 ) return touched_cases
def _reopen_or_create_supply_point(location): from .helpers import update_supply_point_from_location from .dbaccessors import get_supply_point_by_location_id supply_point = get_supply_point_by_location_id(location.domain, location.location_id) if supply_point: if supply_point and supply_point.closed: form_ids = CaseAccessors(supply_point.domain).get_case_xform_ids(supply_point.case_id) form_accessor = FormAccessors(supply_point.domain) for form_id in form_ids: form = form_accessor.get_form(form_id) closes_case = any(map( lambda case_update: case_update.closes_case(), get_case_updates(form), )) if closes_case: form.archive(user_id=const.COMMTRACK_USERNAME) update_supply_point_from_location(supply_point, location) return supply_point else: return SupplyInterface.create_from_location(location.domain, location)
def safe_hard_delete(case): """ Hard delete a case - by deleting the case itself as well as all forms associated with it permanently from the database. Will fail hard if the case has any reverse indices or if any of the forms associated with the case also touch other cases. This is used primarily for cleaning up system cases/actions (e.g. the location delegate case). """ if case.reverse_indices: raise CommCareCaseError("You can't hard delete a case that has other dependencies ({})!".format(case._id)) forms = get_case_forms(case._id) for form in forms: case_updates = get_case_updates(form) if any([c.id != case._id for c in case_updates]): raise CommCareCaseError("You can't hard delete a case that has shared forms with other cases!") docs = [case._doc] + [f._doc for f in forms] case.get_db().bulk_delete(docs)
def _migrate_form_and_associated_models(self, couch_form): sql_form = _migrate_form(self.domain, couch_form) _migrate_form_attachments(sql_form, couch_form) _migrate_form_operations(sql_form, couch_form) self._save_diffs(couch_form, sql_form) case_stock_result = None if sql_form.initial_processing_complete: case_stock_result = _get_case_and_ledger_updates(self.domain, sql_form) if len(case_stock_result.case_models): touch_updates = [ update for update in get_case_updates(couch_form) if len(update.actions) == 1 and isinstance(update.actions[0], CaseNoopAction) ] if len(touch_updates): # record these for later use when filtering case diffs. See ``_filter_forms_touch_case`` self.forms_that_touch_cases_without_actions.add(couch_form.form_id) _save_migrated_models(sql_form, case_stock_result)
def get_cases_from_forms(case_db, xforms): """Get all cases affected by the forms. Includes new cases, updated cases. """ # have to apply the deprecations before the updates sorted_forms = sorted(xforms, key=lambda f: 0 if f.is_deprecated else 1) # don't process error forms which are being deprecated since they were never processed in the first place. # see http://manage.dimagi.com/default.asp?243382 filtered_sorted_forms = [form for form in sorted_forms if not (form.is_deprecated and form.problem)] touched_cases = {} for xform in filtered_sorted_forms: for case_update in get_case_updates(xform): case_update_meta = case_db.get_case_from_case_update(case_update, xform) if case_update_meta.case: touched_cases[case_update_meta.case.case_id] = case_update_meta 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 ) return touched_cases
def _check_update_matches(self, case, expected_update): last_form = FormAccessors(TEST_DOMAIN).get_form(case.xform_ids[-1]) case_update = get_case_updates(last_form)[0] self.assertDictEqual(case_update.update_block, expected_update)
def _noauth_post(request, domain, app_id=None): """ This is explictly called for a submission that has secure submissions enabled, but is manually overriding the submit URL to not specify auth context. It appears to be used by demo mode. It mainly just checks that we are touching test data only in the right domain and submitting as demo_user. """ instance, _ = couchforms.get_instance_and_attachment(request) form_json = convert_xform_to_json(instance) case_updates = get_case_updates(form_json) def form_ok(form_json): try: # require new-style meta/userID (reject Meta/chw_id) if form_json['meta']['userID'] == 'demo_user': return True except (KeyError, ValueError): pass if is_device_report(form_json): return True return False def case_block_ok(case_updates): """ Check for all cases that we are submitting as demo_user and that the domain we are submitting against for any previously existing cases matches the submission domain. """ allowed_ids = ('demo_user', 'demo_user_group_id', None) case_ids = set() for case_update in case_updates: case_ids.add(case_update.id) create_action = case_update.get_create_action() update_action = case_update.get_update_action() index_action = case_update.get_index_action() if create_action: if create_action.user_id not in allowed_ids: return False if create_action.owner_id not in allowed_ids: return False if update_action: if update_action.owner_id not in allowed_ids: return False if index_action: for index in index_action.indices: case_ids.add(index.referenced_id) # todo: consider whether we want to remove this call, and/or pass the result # through to the next function so we don't have to get the cases again later cases = CommCareCase.bulk_get_lite(list(case_ids)) for case in cases: if case.domain != domain: return False if case.owner_id or case.user_id not in allowed_ids: return False return True if not (form_ok(form_json) and case_block_ok(case_updates)): return HttpResponseForbidden() return _process_form( request=request, domain=domain, app_id=app_id, user_id=None, authenticated=False, auth_cls=WaivedAuthContext, )
def raise_supply_point_events(xform, cases): supply_points = [SupplyPointCase.wrap(c._doc) for c in cases if c.type == const.SUPPLY_POINT_CASE_TYPE] case_updates = get_case_updates(xform) for sp in supply_points: created = any(filter(lambda update: update.id == sp._id and update.creates_case(), case_updates)) supply_point_modified.send(sender=None, supply_point=sp, created=created)
def _noauth_post(request, domain, app_id=None): """ This is explictly called for a submission that has secure submissions enabled, but is manually overriding the submit URL to not specify auth context. It appears to be used by demo mode. It mainly just checks that we are touching test data only in the right domain and submitting as demo_user. """ instance, _ = couchforms.get_instance_and_attachment(request) form_json = convert_xform_to_json(instance) case_updates = get_case_updates(form_json) def form_ok(form_json): return (from_demo_user(form_json) or is_device_report(form_json)) def case_block_ok(case_updates): """ Check for all cases that we are submitting as demo_user and that the domain we are submitting against for any previously existing cases matches the submission domain. """ allowed_ids = ('demo_user', 'demo_user_group_id', None) case_ids = set() for case_update in case_updates: case_ids.add(case_update.id) create_action = case_update.get_create_action() update_action = case_update.get_update_action() index_action = case_update.get_index_action() if create_action: if create_action.user_id not in allowed_ids: return False if create_action.owner_id not in allowed_ids: return False if update_action: if update_action.owner_id not in allowed_ids: return False if index_action: for index in index_action.indices: case_ids.add(index.referenced_id) # todo: consider whether we want to remove this call, and/or pass the result # through to the next function so we don't have to get the cases again later cases = CaseAccessors(domain).get_cases(list(case_ids)) for case in cases: if case.domain != domain: return False if case.owner_id or case.user_id not in allowed_ids: return False return True if not (form_ok(form_json) and case_block_ok(case_updates)): if request.GET.get('submit_mode') != DEMO_SUBMIT_MODE: # invalid submissions under demo mode submission can be processed return HttpResponseForbidden() return _process_form( request=request, domain=domain, app_id=app_id, user_id=None, authenticated=False, auth_cls=WaivedAuthContext, )