예제 #1
0
    def test_archie_modified_on(self):
        case_id = uuid.uuid4().hex
        now = datetime.utcnow().replace(microsecond=0)
        earlier = now - timedelta(hours=1)
        way_earlier = now - timedelta(days=1)
        # make sure we timestamp everything so they have the right order
        create_block = CaseBlock(case_id,
                                 create=True,
                                 date_modified=way_earlier)
        post_case_blocks([create_block.as_xml()],
                         form_extras={'received_on': way_earlier})
        update_block = CaseBlock(case_id,
                                 update={'foo': 'bar'},
                                 date_modified=earlier)
        post_case_blocks([update_block.as_xml()],
                         form_extras={'received_on': earlier})

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        self.assertEqual(earlier, case.modified_on)

        second_form = FormAccessors(REBUILD_TEST_DOMAIN).get_form(
            case.xform_ids[-1])
        second_form.archive()
        case = case_accessors.get_case(case_id)
        self.assertEqual(way_earlier, case.modified_on)
예제 #2
0
    def test_archiving_only_form(self):
        """
        Checks that archiving the only form associated with the case archives
        the case and unarchiving unarchives it.
        """
        case_id = _post_util(create=True, p1='p1-1', p2='p2-1')
        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)

        self.assertFalse(case.is_deleted)
        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(1, len(case.actions))
        else:
            self.assertEqual(2, len(case.actions))
        [form_id] = case.xform_ids
        form = FormAccessors(REBUILD_TEST_DOMAIN).get_form(form_id)

        form.archive()
        case = case_accessors.get_case(case_id)

        self.assertTrue(case.is_deleted)
        # should just have the 'rebuild' action
        self.assertEqual(1, len(case.actions))
        self.assertTrue(case.actions[0].is_case_rebuild)

        form.unarchive()
        case = case_accessors.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(3, len(case.actions))
        self.assertTrue(case.actions[-1].is_case_rebuild)
예제 #3
0
    def test_edit_submissions_simple(self):
        initial_quantity = 100
        form = submit_case_blocks(
            case_blocks=get_single_balance_block(quantity=initial_quantity, **self._stock_state_key),
            domain=self.domain,
        )[0]
        self._assert_stats(1, initial_quantity, initial_quantity)

        case_accessors = CaseAccessors(self.domain)
        case = case_accessors.get_case(self.case.case_id)
        try:
            self.assertTrue(any([action.is_ledger_transaction for action in case.actions]))
        except AttributeError:
            self.assertTrue('commtrack' in [action.action_type for action in case.actions])
        self.assertEqual([form.form_id], case.xform_ids[1:])

        # change the value to 50
        edit_quantity = 50
        submit_case_blocks(
            case_blocks=get_single_balance_block(quantity=edit_quantity, **self._stock_state_key),
            domain=self.domain,
            form_id=form.form_id,
        )
        case = case_accessors.get_case(self.case.case_id)

        try:
            # CaseTransaction
            self.assertTrue(any([action.is_ledger_transaction for action in case.actions]))
        except AttributeError:
            # CaseAction
            self.assertTrue('commtrack' in [action.action_type for action in case.actions])

        self._assert_stats(1, edit_quantity, edit_quantity)
        self.assertEqual([form.form_id], case.xform_ids[1:])
예제 #4
0
def get_cases(request, domain):
    request_params = request.GET

    if request.couch_user.is_commcare_user():
        user_id = request.couch_user.get_id
    else:
        user_id = request_params.get("user_id", "")

    if not user_id and not request.couch_user.is_web_user():
        return HttpResponseBadRequest("Must specify user_id!")

    ids_only = string_to_boolean(request_params.get("ids_only", "false"))
    case_id = request_params.get("case_id", "")
    footprint = string_to_boolean(request_params.get("footprint", "false"))
    accessor = CaseAccessors(domain)

    if toggles.HSPH_HACK.enabled(domain):
        hsph_case_id = request_params.get('hsph_hack', None)
        if hsph_case_id != 'None' and hsph_case_id and user_id:
            case = accessor.get_case(hsph_case_id)
            usercase_id = CommCareUser.get_by_user_id(
                user_id).get_usercase_id()
            usercase = accessor.get_case(usercase_id) if usercase_id else None
            return json_response(
                map(
                    lambda case: CaseAPIResult(
                        id=case['_id'], couch_doc=case, id_only=ids_only),
                    filter(None, [case, case.parent, usercase])))
예제 #5
0
 def handle(self, domain, case_id, **options):
     case_accessor = CaseAccessors(domain=domain)
     case = case_accessor.get_case(case_id)
     if not case.is_deleted and input('\n'.join([
         'Case {} is not already deleted. Are you sure you want to delete it? (y/N)'.format(case_id)
     ])).lower() != 'y':
         sys.exit(0)
예제 #6
0
    def test_submit_to_deleted_case(self):
        """submitting to a deleted case should succeed and affect the case"""
        case_id = uuid.uuid4().hex
        xform, [case] = post_case_blocks([
            CaseBlock(create=True,
                      case_id=case_id,
                      user_id='whatever',
                      update={
                          'foo': 'bar'
                      }).as_xml()
        ],
                                         domain="test-domain")
        cases = CaseAccessors("test-domain")
        cases.soft_delete_cases([case_id])

        case = cases.get_case(case_id)
        self.assertEqual('bar', case.dynamic_case_properties()['foo'])
        self.assertTrue(case.is_deleted)

        xform, [case] = post_case_blocks([
            CaseBlock(create=False,
                      case_id=case_id,
                      user_id='whatever',
                      update={
                          'foo': 'not_bar'
                      }).as_xml()
        ])
        self.assertEqual('not_bar', case.dynamic_case_properties()['foo'])
        self.assertTrue(case.is_deleted)
예제 #7
0
    def handle(self, domain, log_file_name, case_ids, **options):
        commit = options['commit']

        print("Starting {} migration on {} at {}".format(
            "real" if commit else "fake", domain, datetime.datetime.utcnow()
        ))

        accessor = CaseAccessors(domain)
        case_ids = case_ids or accessor.get_case_ids_in_domain(type=self.case_type)

        with open(log_file_name, "w") as log_file:
            writer = csv.writer(log_file)
            writer.writerow(
                ['case_id']
                + ['current_' + case_prop for case_prop in self.case_properties_to_update]
                + self.case_properties_to_update
                + [self.datamigration_case_property]
            )
            for case_id in with_progress_bar(case_ids):
                if self.is_valid_case(domain, case_id):
                    case = accessor.get_case(case_id)
                    updated_case_properties = self.get_case_property_updates(case, domain)
                    needs_update = bool(updated_case_properties)
                    updated_case_properties[self.datamigration_case_property] = 'yes' if needs_update else 'no'
                    writer.writerow(
                        [case.case_id]
                        + [case.get_case_property(case_prop) or '' for case_prop in self.case_properties_to_update]
                        + [updated_case_properties.get(case_prop, '') for case_prop in (
                            self.case_properties_to_update + [self.datamigration_case_property])]
                    )
                    if needs_update and commit:
                        self.commit_updates(domain, case.case_id, updated_case_properties)

            print("Finished at {}".format(datetime.datetime.utcnow()))
예제 #8
0
def get_cases(request, domain):
    request_params = request.GET

    if request.couch_user.is_commcare_user():
        user_id = request.couch_user.get_id
    else:
        user_id = request_params.get("user_id", "")

    if not user_id and not request.couch_user.is_web_user():
        return HttpResponseBadRequest("Must specify user_id!")

    ids_only = string_to_boolean(request_params.get("ids_only", "false"))
    case_id = request_params.get("case_id", "")
    footprint = string_to_boolean(request_params.get("footprint", "false"))
    accessor = CaseAccessors(domain)

    if case_id and not footprint:
        # short circuit everything else and just return the case
        # NOTE: this allows any user in the domain to access any case given
        # they know its ID, which is slightly different from the previous
        # behavior (can only access things you own + footprint). If we want to
        # change this contract we would need to update this to check the
        # owned case list + footprint
        case = accessor.get_case(case_id)
        assert case.domain == domain
        cases = [
            CaseAPIResult(domain=domain,
                          id=case_id,
                          couch_doc=case,
                          id_only=ids_only)
        ]
예제 #9
0
def _update_case(domain, case_id, server_modified_on, last_visit_date=None):
    accessors = CaseAccessors(domain)
    case = accessors.get_case(case_id)
    case.server_modified_on = server_modified_on
    if last_visit_date:
        set_case_property_directly(case, 'last_visit_date',
                                   last_visit_date.strftime('%Y-%m-%d'))
    _save_case(domain, case)
예제 #10
0
def get_clays() -> Iterable[CassiusMarcellus]:
    case_accessors = CaseAccessors(DOMAIN)
    for case_id in case_accessors.get_case_ids_in_domain(type=CASE_TYPE):
        case = case_accessors.get_case(case_id)
        if not case.external_id:
            # This case is not mapped to a facility in DHIS2.
            continue
        yield CassiusMarcellus(case)
 def test_update_case(self):
     update_properties = {'age': 99}
     self.factory.create_or_update_cases([self.person])
     case_accessors = CaseAccessors(self.domain)
     person_case = case_accessors.get_case(self.person_id)
     self.assertEqual(person_case.dynamic_case_properties().get('age', None), '20')
     update_case(self.domain, self.person_id, update_properties)
     person_case = case_accessors.get_case(self.person_id)
     self.assertEqual(person_case.dynamic_case_properties()['age'], '99')
예제 #12
0
    def test_form_archiving(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, p1='p1-1', p2='p2-1',
                            form_extras={'received_on': now})
        _post_util(case_id=case_id, p2='p2-2', p3='p3-2', p4='p4-2',
                  form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p4='p4-3', p5='p5-3', close=True,
                  form_extras={'received_on': now + timedelta(seconds=2)})

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        closed_by = case.closed_by
        closed_on = case.closed_on
        self.assertNotEqual('', closed_by)
        self.assertNotEqual(None, closed_on)

        def _check_initial_state(case):
            self.assertTrue(case.closed)
            self.assertEqual(closed_by, case.closed_by)
            self.assertEqual(closed_on, case.closed_on)
            self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
            self.assertEqual(case.get_case_property('p2'), 'p2-2')  # updated in second post
            self.assertEqual(case.get_case_property('p3'), 'p3-2')  # new in second post
            self.assertEqual(case.get_case_property('p4'), 'p4-3')  # updated in third post
            self.assertEqual(case.get_case_property('p5'), 'p5-3')  # new in third post
            if should_use_sql_backend(REBUILD_TEST_DOMAIN):
                # SQL stores one transaction per form
                self.assertEqual(3, len(primary_actions(case)))  # create + update + close
            else:
                self.assertEqual(5, len(primary_actions(case)))  # create + 3 updates + close

        _check_initial_state(case)

        # verify xform/action states
        [f1, f2, f3] = case.xform_ids
        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            [create, update, close] = case.actions
            self.assertEqual(f1, create.form_id)
            self.assertEqual(f2, update.form_id)
            self.assertEqual(f3, close.form_id)
        else:
            [create, u1, u2, u3, close] = case.actions
            self.assertEqual(f1, create.form_id)
            self.assertEqual(f1, u1.form_id)
            self.assertEqual(f2, u2.form_id)
            self.assertEqual(f3, u3.form_id)

        # todo: should this be the behavior for archiving the create form?
        form_acessors = FormAccessors(REBUILD_TEST_DOMAIN)
        f1_doc = form_acessors.get_form(f1)
        with capture_kafka_changes_context(topics.CASE_SQL) as change_context:
            f1_doc.archive()

        if should_use_sql_backend(case.domain):
            self.assertEqual([case.case_id], [change.id for change in change_context.changes])
예제 #13
0
    def _delete_child_index(self):
        ref = CaseStructure()
        ref.case_id = ""  # reset case_id to empty
        self.factory.create_or_update_case(
            CaseStructure(case_id=self.child.case_id,
                          indices=[CaseIndex(ref, related_type='parent')],
                          walk_related=False), )

        # re-fetch case to clear memoized properties
        accessors = CaseAccessors(self.parent.domain)
        self.parent = accessors.get_case(self.parent.case_id)
        self.child = accessors.get_case(self.child.case_id)
예제 #14
0
def lookup_case(search_field, search_id, domain, case_type):
    """
    Attempt to find the case in CouchDB by the provided search_field and search_id.

    Returns a tuple with case (if found) and an
    error code (if there was an error in lookup).
    """
    found = False
    case_accessors = CaseAccessors(domain)
    if search_field == 'case_id':
        try:
            case = case_accessors.get_case(search_id)
            if case.domain == domain and case.type == case_type:
                found = True
def perform_resave_on_cases(domain):
    case_ids_missing_in_es, _ = compare_cases(domain, 'CommCareCase')
    print("%s Ids found for cases missing in ES." % len(case_ids_missing_in_es))
    print(case_ids_missing_in_es)
    ok = input("Type 'ok' to continue: ")
    if ok != "ok":
        print("No changes made")
        return
    case_accessor = CaseAccessors(domain)
    for case_id in with_progress_bar(case_ids_missing_in_es):
        case = case_accessor.get_case(case_id)
        if case:
            resave_case(domain, case, send_post_save_signal=False)
        else:
            print("case not found %s" % case_id)
    def test_soft_delete(self):
        _submit_case_block(True, 'c1', domain=DOMAIN)
        _submit_case_block(True, 'c2', domain=DOMAIN)
        _submit_case_block(True, 'c3', domain=DOMAIN)

        accessors = CaseAccessors(DOMAIN)

        # delete
        num = accessors.soft_delete_cases(['c1', 'c2'], deletion_id='123')
        self.assertEqual(num, 2)

        for case_id in ['c1', 'c2']:
            case = accessors.get_case(case_id)
            self.assertTrue(case.is_deleted)
            self.assertEqual(case.deletion_id, '123')
예제 #17
0
    def __call__(self, item, context=None):
        case_id = self._case_id_expression(item, context)

        if not case_id:
            return None

        # a village will only be on the case as the owner if
        # its the assignment case so we should verify that
        if item['owner_id'] != UNOWNED_EXTENSION_OWNER_ID:
            accessor = CaseAccessors(context.root_doc['domain'])
            case = accessor.get_case(case_id)
            if case.type == 'assignment':
                # should verify it is a village case (not AWC)
                # via some property to be determined later
                return item['owner_id']
예제 #18
0
    def test_archived_form_gets_removed_from_case_xform_ids(self):
        initial_amounts = [(p._id, float(100)) for p in self.products]
        instance_id = self.submit_xml_form(balance_submission(initial_amounts),
                                           timestamp=datetime.utcnow() +
                                           timedelta(-30))

        case_accessors = CaseAccessors(self.domain.name)
        case = case_accessors.get_case(self.sp.case_id)
        self.assertIn(instance_id, case.xform_ids)

        form = FormAccessors(self.domain.name).get_form(instance_id)
        form.archive()

        case = case_accessors.get_case(self.sp.case_id)
        self.assertNotIn(instance_id, case.xform_ids)
예제 #19
0
def get_case_blocks() -> Iterable[CaseBlock]:
    case_accessors = CaseAccessors(DOMAIN)
    for case_id in case_accessors.get_case_ids_in_domain(type=CASE_TYPE):
        case = case_accessors.get_case(case_id)
        if not case.external_id:
            # This case is not mapped to a facility in DHIS2.
            continue
        case_block = CaseBlock(
            case_id=case.case_id,
            external_id=case.external_id,
            case_type=CASE_TYPE,
            case_name=case.name,
            update={},
        )
        yield case_block
예제 #20
0
파일: util.py 프로젝트: dimagi/commcare-hq
def lookup_case(search_field, search_id, domain, case_type):
    """
    Attempt to find the case in CouchDB by the provided search_field and search_id.

    Returns a tuple with case (if found) and an
    error code (if there was an error in lookup).
    """
    found = False
    case_accessors = CaseAccessors(domain)
    if search_field == 'case_id':
        try:
            case = case_accessors.get_case(search_id)
            if case.domain == domain and case.type == case_type:
                found = True
        except CaseNotFound:
            pass
    elif search_field == EXTERNAL_ID:
        cases_by_type = case_accessors.get_cases_by_external_id(search_id, case_type=case_type)
        if not cases_by_type:
            return (None, LookupErrors.NotFound)
        elif len(cases_by_type) > 1:
            return (None, LookupErrors.MultipleResults)
        else:
            case = cases_by_type[0]
            found = True

    if found:
        return (case, None)
    else:
        return (None, LookupErrors.NotFound)
예제 #21
0
def get_parent_of_case(domain, case_id, parent_case_type):
    case_accessor = CaseAccessors(domain)
    try:
        if not isinstance(case_id, basestring):
            case_id = case_id.case_id

        child_case = case_accessor.get_case(case_id)
    except CaseNotFound:
        raise ENikshayCaseNotFound(
            "Couldn't find case: {}".format(case_id)
        )

    parent_case_ids = [
        indexed_case.referenced_id for indexed_case in child_case.indices
        if indexed_case.referenced_type == parent_case_type
    ]
    parent_cases = case_accessor.get_cases(parent_case_ids)
    open_parent_cases = [
        occurrence_case for occurrence_case in parent_cases
        if not occurrence_case.closed
    ]

    if not open_parent_cases:
        raise ENikshayCaseNotFound(
            "Couldn't find any open {} cases for id: {}".format(parent_case_type, case_id)
        )

    return open_parent_cases[0]
예제 #22
0
 def parent_cases(self):
     from corehq.apps.api.util import case_to_es_case
     accessor = CaseAccessors(self.domain)
     return {
         index['identifier']: case_to_es_case(accessor.get_case(index['referenced_id']))
         for index in self.indices
     }
예제 #23
0
    def test_archived_form_gets_removed_from_case_xform_ids(self):
        initial_amounts = [(p._id, float(100)) for p in self.products]
        instance_id = self.submit_xml_form(
            balance_submission(initial_amounts),
            timestamp=datetime.utcnow() + timedelta(-30)
        )

        case_accessors = CaseAccessors(self.domain.name)
        case = case_accessors.get_case(self.sp.case_id)
        self.assertIn(instance_id, case.xform_ids)

        form = FormAccessors(self.domain.name).get_form(instance_id)
        form.archive()

        case = case_accessors.get_case(self.sp.case_id)
        self.assertNotIn(instance_id, case.xform_ids)
예제 #24
0
class PopulateMissingMotherNameDocProcessor(DataManagementDocProcessor):
    def __init__(self, domain):
        super().__init__(domain)
        date_today = date.today()
        self.cut_off_dob = str(date_today.replace(year=date_today.year - CUT_OFF_AGE_IN_YEARS))
        self.test_location_ids = find_test_awc_location_ids(self.domain)
        self.case_accessor = CaseAccessors(self.domain)

    def process_bulk_docs(self, docs):
        updates = {}
        for doc in docs:
            case_id = doc['_id']
            mother_case_ids = [i.referenced_id for i in CaseAccessorSQL.get_indices(self.domain, case_id)
                               if i.identifier == MOTHER_INDEX_IDENTIFIER]
            if len(mother_case_ids) == 1:
                try:
                    mother_case = self.case_accessor.get_case(mother_case_ids[0])
                except CaseNotFound:
                    pass
                else:
                    updates[case_id] = mother_case.name
        if updates:
            submit_case_blocks(self._create_case_blocks(updates, MOTHER_NAME_PROPERTY),
                               self.domain, user_id=SYSTEM_USER_ID)
        return True

    def should_process(self, doc):
        owner_id = doc.get('owner_id')
        if owner_id and owner_id in self.test_location_ids:
            return False
        dob = doc.get(DOB_PROPERTY)
        if dob and dob >= self.cut_off_dob and not doc.get(MOTHER_NAME_PROPERTY):
            return True
        return False
예제 #25
0
    def handle(self, file_path, *args, **options):
        print("Loading episode ids or person_ids")
        self._load_case_ids(file_path,
                            options.get('parse_as_person_enikshay_ids'))
        print("Okay! Loaded {number} episode ids".format(
            number=len(self.episode_ids)))
        case_accessor = CaseAccessors(DOMAIN)
        for episode_id in with_progress_bar(self.episode_ids):
            should_be_forwarded = "Could not be determined"
            try:
                episode_case = case_accessor.get_case(episode_id)
                episode_case_properties = episode_case.dynamic_case_properties(
                )
                should_be_forwarded = self._should_be_forwarded(
                    episode_case, episode_case_properties)
                self._add_row(episode_id, episode_case,
                              episode_case_properties, should_be_forwarded)
            except CaseNotFound:
                self._add_row(episode_id,
                              None,
                              None,
                              should_be_forwarded,
                              error_message="Could not find episode case")

        file_name = self._save_file()
        print("Report saved in file:{filename}".format(filename=file_name))
예제 #26
0
class Command(BaseCommand):

    def add_arguments(self, parser):
        parser.add_argument('infile')
        parser.add_argument('outfile')

    def handle(self, infile, outfile, *args, **options):
        self.case_accessor = CaseAccessors('icds-cas')
        with open(infile, 'r', encoding='utf-8') as old, open(outfile, 'w', encoding='utf-8') as new:
            reader = csv.reader(old)
            writer = csv.writer(new)
            headers = next(reader)
            writer.writerow(headers)
            for row in reader:
                case_id = row[4]
                hh_id = row[10]
                if hh_id:
                    person, hh = self.case_accessor.get_cases([case_id, hh_id], ordered=True)
                else:
                    person = self.case_accessor.get_case(case_id)
                    hh = None
                if hh:
                    row[18] = hh.get_case_property('name')
                    row[19] = hh.get_case_property('hh_num')
                row[20] = person.get_case_property('name')
                writer.writerow(row)
예제 #27
0
def _update_case(domain, case_id, server_modified_on, last_visit_date=None):
    accessors = CaseAccessors(domain)
    case = accessors.get_case(case_id)
    case.server_modified_on = server_modified_on
    if last_visit_date:
        set_case_property_directly(case, 'last_visit_date', last_visit_date.strftime('%Y-%m-%d'))
    _save_case(domain, case)
예제 #28
0
class Command(BaseCommand):
    def add_arguments(self, parser):
        parser.add_argument('infile')
        parser.add_argument('outfile')

    def handle(self, infile, outfile, *args, **options):
        self.case_accessor = CaseAccessors('icds-cas')
        with open(infile, 'r') as old, open(outfile, 'w') as new:
            reader = csv.reader(old)
            writer = csv.writer(new)
            headers = next(reader)
            writer.writerow(headers)
            for row in reader:
                owner = row[2]
                case_id = row[3]
                hh_id = row[4]
                if hh_id:
                    person, hh = self.case_accessor.get_cases([case_id, hh_id],
                                                              ordered=True)
                else:
                    person = self.case_accessor.get_case(case_id)
                    hh = None
                row[15] = SQLLocation.objects.get(location_id=owner).name
                if hh:
                    row[16] = hh.get_case_property('name')
                    row[17] = hh.get_case_property('hh_num')
                row[18] = person.get_case_property('name')
                row[19] = person.get_case_property('dob')
                row[20] = person.get_case_property('sex')
                writer.writerow(row)
예제 #29
0
def _get_case_ids_related_to_person(domain, person_case_id):
    case_accessor = CaseAccessors(domain)

    person_case = case_accessor.get_case(person_case_id)
    assert person_case.type == 'person'
    assert person_case.dynamic_case_properties().get('migration_created_case') == 'true'

    all_occurrence_cases = _get_children_cases_by_type(domain, person_case.case_id, CASE_TYPE_OCCURRENCE)
    for occurrence_case in all_occurrence_cases:
        assert occurrence_case.dynamic_case_properties().get('migration_created_case') == 'true'

    all_episode_cases = _concatenate_list_of_lists([
        _get_children_cases_by_type(domain, occurrence_child_case.case_id, CASE_TYPE_EPISODE)
        for occurrence_child_case in all_occurrence_cases
    ])
    for episode_case in all_episode_cases:
        assert episode_case.dynamic_case_properties().get('migration_created_case') == 'true'

    all_drtb_hiv_referral_cases = _concatenate_list_of_lists([
        _get_children_cases_by_type(domain, episode_child_case.case_id, CASE_TYPE_DRTB_HIV_REFERRAL)
        for episode_child_case in all_episode_cases
    ])
    for drtb_hiv_referral_case in all_drtb_hiv_referral_cases:
        assert drtb_hiv_referral_case.dynamic_case_properties().get('migration_created_case') == 'true'

    return _concatenate_list_of_lists([
        [person_case.case_id],
        [occurrence_child_case.case_id for occurrence_child_case in all_occurrence_cases],
        [episode_child_case.case_id for episode_child_case in all_episode_cases],
        [drtb_hiv_referral_case.case_id for drtb_hiv_referral_case in all_drtb_hiv_referral_cases],
    ])
예제 #30
0
class Command(BaseCommand):

    def add_arguments(self, parser):
        parser.add_argument('infile')
        parser.add_argument('outfile')

    def handle(self, infile, outfile, *args, **options):
        self.case_accessor = CaseAccessors('icds-cas')
        with open(infile, 'r', encoding='utf-8') as old, open(outfile, 'w', encoding='utf-8') as new:
            reader = csv.reader(old)
            writer = csv.writer(new)
            headers = next(reader)
            writer.writerow(headers)
            for row in reader:
                case_id = row[4]
                hh_id = row[10]
                if hh_id:
                    person, hh = self.case_accessor.get_cases([case_id, hh_id], ordered=True)
                else:
                    person = self.case_accessor.get_case(case_id)
                    hh = None
                if hh:
                    row[18] = hh.get_case_property('name')
                    row[19] = hh.get_case_property('hh_num')
                row[20] = person.get_case_property('name')
                writer.writerow(row)
예제 #31
0
def get_associated_episode_case_for_test(test_case, occurrence_case_id):
    """
    get associated episode case set on the test case for new structure
    if has a new_episode_id (for diagnostic -> confirmed_tb or dstb -> drtb episode transition)
        return that episode which should be a confirmed_tb/confirmed_drtb case
    elif has a episode_case_id (for follow up test cases)
        return the associated episode case only
        which should be a confirmed_tb/confirmed_drtb case
    else
        fallback to finding the open confirmed tb episode case
    """
    test_case_properties = test_case.dynamic_case_properties()
    test_case_episode_id = (test_case_properties.get('new_episode_case_id')
                            or test_case_properties.get('episode_case_id'))
    if test_case_episode_id:
        accessor = CaseAccessors(test_case.domain)
        try:
            return accessor.get_case(test_case_episode_id)
        except CaseNotFound:
            raise ENikshayCaseNotFound(
                "Could not find episode case %s associated with test %s" %
                (test_case_episode_id, test_case.get_id))

    return get_open_episode_case_from_occurrence(test_case.domain,
                                                 occurrence_case_id)
예제 #32
0
 def child_cases(self):
     from corehq.apps.api.util import case_to_es_case
     accessor = CaseAccessors(self.domain)
     return {
         index.case_id: case_to_es_case(accessor.get_case(index.case_id))
         for index in self._reverse_indices
     }
예제 #33
0
 def child_cases(self):
     from corehq.apps.api.util import case_to_es_case
     accessor = CaseAccessors(self.domain)
     return {
         index.case_id: case_to_es_case(accessor.get_case(index.case_id))
         for index in self._reverse_indices
     }
예제 #34
0
def get_case_by_identifier(domain, identifier):
    # circular import
    from corehq.apps.api.es import CaseES
    case_es = CaseES(domain)
    case_accessors = CaseAccessors(domain)

    def _query_by_type(i_type):
        q = case_es.base_query(
            terms={
                i_type: identifier,
            },
            fields=['_id', i_type],
            size=1
        )
        response = case_es.run_query(q)
        raw_docs = response['hits']['hits']
        if raw_docs:
            return case_accessors.get_case(raw_docs[0]['_id'])

    # Try by any of the allowed identifiers
    for identifier_type in ALLOWED_CASE_IDENTIFIER_TYPES:
        case = _query_by_type(identifier_type)
        if case is not None:
            return case

    # Try by case id
    try:
        case_by_id = case_accessors.get_case(identifier)
        if case_by_id.domain == domain:
            return case_by_id
    except (CaseNotFound, KeyError):
        pass

    return None
예제 #35
0
 def parent_cases(self):
     from corehq.apps.api.util import case_to_es_case
     accessor = CaseAccessors(self.domain)
     return {
         index['identifier']: case_to_es_case(accessor.get_case(index['referenced_id']))
         for index in self.indices
     }
예제 #36
0
    def handle(self, case_type, *args, **options):
        self.dry_run = options.get('dry_run')
        accessor = CaseAccessors(DOMAIN)
        result_file_path = "{case_type}_cases_update_report_{timestamp}.csv".format(
            case_type=case_type, timestamp=datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
        )
        if case_type == "all":
            case_types = ['person', 'episode', 'test']
        else:
            case_types = [case_type]
        with open(result_file_path, 'w') as output_buffer:
            writer = csv.DictWriter(output_buffer, fieldnames=self._get_headers(case_types))
            writer.writeheader()
            for person_case_id in self._get_person_case_ids_to_process():
                person_case = accessor.get_case(person_case_id)
                if person_case.dynamic_case_properties().get('case_version') == PERSON_CASE_2B_VERSION:
                    # reset all collections for next iteration
                    occurrence_cases = []

                    if "person" in case_types:
                        case_status = self.get_case_status(person_case)
                        case_status = self.update_case(person_case.get_id, case_status)
                        writer.writerow(case_status)

                    if "episode" in case_types:
                        occurrence_cases = [case for case in accessor.get_reverse_indexed_cases([person_case_id])
                                            if case.type == CASE_TYPE_OCCURRENCE]
                        for occurrence_case in occurrence_cases:
                            episode_cases = [case for case in
                                             accessor.get_reverse_indexed_cases([occurrence_case.get_id])
                                             if case.type == CASE_TYPE_EPISODE]
                            for episode_case in episode_cases:
                                case_status = self.get_case_status(episode_case, episode_cases=episode_cases)
                                case_status = self.update_case(episode_case.get_id, case_status)
                                writer.writerow(case_status)
예제 #37
0
def get_parent_of_case(domain, case_id, parent_case_type):
    case_accessor = CaseAccessors(domain)
    try:
        if not isinstance(case_id, basestring):
            case_id = case_id.case_id

        child_case = case_accessor.get_case(case_id)
    except CaseNotFound:
        raise ENikshayCaseNotFound("Couldn't find case: {}".format(case_id))

    parent_case_ids = [
        indexed_case.referenced_id for indexed_case in child_case.indices
        if indexed_case.referenced_type == parent_case_type
    ]
    parent_cases = case_accessor.get_cases(parent_case_ids)
    open_parent_cases = [
        occurrence_case for occurrence_case in parent_cases
        if not occurrence_case.closed
    ]

    if not open_parent_cases:
        raise ENikshayCaseNotFound(
            "Couldn't find any open {} cases for id: {}".format(
                parent_case_type, case_id))

    return open_parent_cases[0]
예제 #38
0
    def _iter_supercase_info(self, info: CaseTriggerInfo):
        def filter_index(idx):
            return ((not self.identifier or idx.identifier == self.identifier)
                    and (not self.referenced_type
                         or idx.referenced_type == self.referenced_type)
                    and (not self.relationship
                         or idx.relationship == self.relationship))

        case_accessor = CaseAccessors(info.domain)
        case = case_accessor.get_case(info.case_id)
        for index in case.live_indices:
            if filter_index(index):
                supercase = case_accessor.get_case(index.referenced_id)
                yield get_case_trigger_info_for_case(
                    supercase,
                    [self.supercase_value_source],
                )
예제 #39
0
def get_cases(request, domain):
    request_params = request.GET

    if request.couch_user.is_commcare_user():
        user_id = request.couch_user.get_id
    else:
        user_id = request_params.get("user_id", "")

    if not user_id and not request.couch_user.is_web_user():
        return HttpResponseBadRequest("Must specify user_id!")

    ids_only = string_to_boolean(request_params.get("ids_only", "false"))
    case_id = request_params.get("case_id", "")
    footprint = string_to_boolean(request_params.get("footprint", "false"))
    accessor = CaseAccessors(domain)

    if toggles.HSPH_HACK.enabled(domain):
        hsph_case_id = request_params.get('hsph_hack', None)
        if hsph_case_id != 'None' and hsph_case_id and user_id:
            case = accessor.get_case(hsph_case_id)
            usercase_id = CommCareUser.get_by_user_id(user_id).get_usercase_id()
            usercase = accessor.get_case(usercase_id) if usercase_id else None
            return json_response(map(
                lambda case: CaseAPIResult(domain=domain, id=case['_id'], couch_doc=case, id_only=ids_only),
                filter(None, [case, case.parent, usercase])
            ))

    if case_id and not footprint:
        # short circuit everything else and just return the case
        # NOTE: this allows any user in the domain to access any case given
        # they know its ID, which is slightly different from the previous
        # behavior (can only access things you own + footprint). If we want to
        # change this contract we would need to update this to check the
        # owned case list + footprint
        case = accessor.get_case(case_id)
        assert case.domain == domain
        cases = [CaseAPIResult(domain=domain, id=case_id, couch_doc=case, id_only=ids_only)]
    else:
        filters = get_filters_from_request_params(request_params)
        status = api_closed_to_status(request_params.get('closed', 'false'))
        case_type = filters.get('properties/case_type', None)
        cases = get_filtered_cases(domain, status=status, case_type=case_type,
                                   user_id=user_id, filters=filters,
                                   footprint=footprint, ids_only=ids_only,
                                   strip_history=True)
    return json_response(cases)
예제 #40
0
class ReadonlyCaseDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.case_accessors = CaseAccessors(domain=domain)

    def get_document(self, doc_id):
        return self.case_accessors.get_case(doc_id).to_json()
예제 #41
0
class TestHardDelete(TestCase):

    def setUp(self):
        self.casedb = CaseAccessors(TEST_DOMAIN_NAME)
        self.formdb = FormAccessors(TEST_DOMAIN_NAME)

    @run_with_all_backends
    def test_simple_delete(self):
        factory = CaseFactory()
        case = factory.create_case()
        [case] = factory.create_or_update_case(
            CaseStructure(case_id=case.case_id, attrs={'update': {'foo': 'bar'}})
        )
        self.assertIsNotNone(self.casedb.get_case(case.case_id))
        self.assertEqual(2, len(case.xform_ids))
        for form_id in case.xform_ids:
            self.assertIsNotNone(self.formdb.get_form(form_id))
        safe_hard_delete(case)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case.case_id)

        for form_id in case.xform_ids:
            with self.assertRaises(XFormNotFound):
                self.formdb.get_form(form_id)

    @run_with_all_backends
    def test_delete_with_related(self):
        factory = CaseFactory()
        parent = factory.create_case()
        [child] = factory.create_or_update_case(
            CaseStructure(attrs={'create': True}, walk_related=False, indices=[
                CaseIndex(CaseStructure(case_id=parent.case_id))
            ]),
        )
        # deleting the parent should not be allowed because the child still references it
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(parent)

        # deleting the child is ok
        safe_hard_delete(child)
        self.assertIsNotNone(self.casedb.get_case(parent.case_id))
        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(child.case_id)

    @run_with_all_backends
    def test_delete_sharing_form(self):
        factory = CaseFactory()
        c1, c2 = factory.create_or_update_cases([
            CaseStructure(attrs={'create': True}),
            CaseStructure(attrs={'create': True}),
        ])
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c1)

        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c2)

        self.assertIsNotNone(self.casedb.get_case(c1.case_id))
        self.assertIsNotNone(self.casedb.get_case(c2.case_id))
예제 #42
0
    def test_soft_delete(self):
        _submit_case_block(True, 'c1', domain=DOMAIN)
        _submit_case_block(True, 'c2', domain=DOMAIN)
        _submit_case_block(True, 'c3', domain=DOMAIN)

        accessors = CaseAccessors(DOMAIN)

        # delete
        num = accessors.soft_delete_cases(['c1', 'c2'], deletion_id='123')
        self.assertEqual(num, 2)

        for case_id in ['c1', 'c2']:
            case = accessors.get_case(case_id)
            self.assertTrue(case.is_deleted)
            self.assertEqual(case.deletion_id, '123')

        case = accessors.get_case('c3')
        self.assertFalse(case.is_deleted)
예제 #43
0
    def test_archive_against_deleted_case(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, p1='p1', form_extras={'received_on': now})
        _post_util(case_id=case_id, p2='p2',
                  form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p3='p3',
                  form_extras={'received_on': now + timedelta(seconds=2)})

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        case_accessors.soft_delete_cases([case_id])

        [f1, f2, f3] = case.xform_ids
        f2_doc = FormAccessors(REBUILD_TEST_DOMAIN).get_form(f2)
        f2_doc.archive()
        case = case_accessors.get_case(case_id)
        self.assertTrue(case.is_deleted)
예제 #44
0
    def test_archive_against_deleted_case(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, p1='p1', form_extras={'received_on': now})
        _post_util(case_id=case_id, p2='p2',
                  form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p3='p3',
                  form_extras={'received_on': now + timedelta(seconds=2)})

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        case_accessors.soft_delete_cases([case_id])

        [f1, f2, f3] = case.xform_ids
        f2_doc = FormAccessors(REBUILD_TEST_DOMAIN).get_form(f2)
        f2_doc.archive()
        case = case_accessors.get_case(case_id)
        self.assertTrue(case.is_deleted)
예제 #45
0
 def handle(self, domain, case_type, *args, **options):
     perform_update = True
     query = (CaseES(es_instance_alias=ES_EXPORT_INSTANCE).domain(
         domain).case_type(case_type).is_closed(False).term(
             'name.exact', ''))
     cases_count = query.count()
     print("Number of cases to be updated approximately: %s" % cases_count)
     if not input("Do you wish to update cases (y/n)") == 'y':
         perform_update = False
         if not input("Do you wish to just log updates (y/n)") == 'y':
             exit(0)
     case_ids = query.get_ids()
     print("Begin iterating %s cases" % len(case_ids))
     case_accessor = CaseAccessors(domain)
     case_updates = []
     filename = "case_updates_%s_%s_%s.csv" % (domain, case_type,
                                               datetime.utcnow())
     with open(filename, 'w') as f:
         writer = csv.DictWriter(f, ['case_id', 'new_value'])
         writer.writeheader()
         for case_id in with_progress_bar(case_ids):
             case = case_accessor.get_case(case_id)
             if case.name:
                 continue
             update_to_name = get_last_non_blank_value(case, 'name')
             if update_to_name:
                 writer.writerow({
                     'case_id': case_id,
                     'new_value': update_to_name
                 })
                 if perform_update:
                     case_updates.append((case_id, {
                         'name': update_to_name
                     }, False))
             # update batch when we have the threshold
             if len(case_updates) == CASE_UPDATE_BATCH:
                 bulk_update_cases(domain, case_updates, DEVICE_ID)
                 case_updates = []
         # submit left over case updates
         if case_updates:
             print("Performing last batch of updates")
             bulk_update_cases(domain, case_updates, DEVICE_ID)
         print("Finished. Update details in %s" % filename)
예제 #46
0
class ReadonlyCaseDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.case_accessors = CaseAccessors(domain=domain)

    def get_document(self, doc_id):
        try:
            return self.case_accessors.get_case(doc_id).to_json()
        except CaseNotFound as e:
            raise DocumentNotFoundError(e)
def _update_case(domain, case_id, server_modified_on, last_visit_date=None):
    accessors = CaseAccessors(domain)
    case = accessors.get_case(case_id)
    case.server_modified_on = server_modified_on
    if last_visit_date:
        set_case_property_directly(case, 'last_visit_date', last_visit_date.strftime('%Y-%m-%d'))
    if should_use_sql_backend(domain):
        CaseAccessorSQL.save_case(case)
    else:
        # can't call case.save() since it overrides the server_modified_on property
        CommCareCase.get_db().save_doc(case.to_json())
예제 #48
0
    def test_archive_removes_index(self):
        parent_case_id = uuid.uuid4().hex
        post_case_blocks([
            CaseBlock(parent_case_id, create=True).as_xml()
        ])
        child_case_id = uuid.uuid4().hex
        post_case_blocks([
            CaseBlock(child_case_id, create=True).as_xml()
        ])
        xform, _ = post_case_blocks([
            CaseBlock(child_case_id, index={'mom': ('mother', parent_case_id)}).as_xml()
        ])

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(child_case_id)
        self.assertEqual(1, len(case.indices))

        xform.archive()

        case = case_accessors.get_case(child_case_id)
        self.assertEqual(0, len(case.indices))
예제 #49
0
    def test_archie_modified_on(self):
        case_id = uuid.uuid4().hex
        now = datetime.utcnow().replace(microsecond=0)
        earlier = now - timedelta(hours=1)
        way_earlier = now - timedelta(days=1)
        # make sure we timestamp everything so they have the right order
        create_block = CaseBlock(case_id, create=True, date_modified=way_earlier)
        post_case_blocks(
            [create_block.as_xml()], form_extras={'received_on': way_earlier}
        )
        update_block = CaseBlock(case_id, update={'foo': 'bar'}, date_modified=earlier)
        post_case_blocks(
            [update_block.as_xml()], form_extras={'received_on': earlier}
        )

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        self.assertEqual(earlier, case.modified_on)

        second_form = FormAccessors(REBUILD_TEST_DOMAIN).get_form(case.xform_ids[-1])
        second_form.archive()
        case = case_accessors.get_case(case_id)
        self.assertEqual(way_earlier, case.modified_on)
예제 #50
0
def hint_still_valid(domain, hint):
    """
    For a given domain/owner/cleanliness hint check if it's still valid
    """
    casedb = CaseAccessors(domain)
    try:
        hint_case = casedb.get_case(hint)
        hint_owner = hint_case.owner_id
    except CaseNotFound:
        # hint was deleted
        return False
    dependent_case_ids = set(get_dependent_case_info(domain, [hint]).all_ids)
    return any([c.owner_id != hint_owner and c.owner_id != UNOWNED_EXTENSION_OWNER_ID
                for c in casedb.get_cases(list(dependent_case_ids))])
def _with_case(domain, case_type, last_modified):
    with drop_connected_signals(case_post_save):
        case = CaseFactory(domain).create_case(case_type=case_type)

    _update_case(domain, case.case_id, last_modified)
    accessors = CaseAccessors(domain)
    case = accessors.get_case(case.case_id)
    try:
        yield case
    finally:
        if should_use_sql_backend(domain):
            CaseAccessorSQL.hard_delete_cases(domain, [case.case_id])
        else:
            case.delete()
예제 #52
0
    def handle(self, *args, **options):
        domain = options["domain"]
        case_id = options["case_id"]
        case_accessor = CaseAccessors(domain=domain)
        case = case_accessor.get_case(case_id)
        if (
            not case.is_deleted
            and raw_input(
                "\n".join(["Case {} is not already deleted. Are you sure you want to delete it? (y/N)".format(case_id)])
            ).lower()
            != "y"
        ):
            sys.exit(0)
        dependent_case_ids = get_entire_case_network(domain, [case_id])

        cases_to_delete = filter(lambda case: not case.is_deleted, case_accessor.get_cases(dependent_case_ids))
        if cases_to_delete:
            with open(options["filename"], "w") as csvfile:
                writer = csv.writer(csvfile)
                headers = ["case id", "case type", "owner", "opened by", "app version"]
                writer.writerow(headers)
                print headers

                for case in cases_to_delete:
                    form = FormAccessors(domain=domain).get_form(case.xform_ids[0])
                    app_version_info = get_app_version_info(
                        domain, form.build_id, form.form_data["@version"], form.metadata
                    )
                    row = [
                        case.case_id,
                        case.type,
                        cached_owner_id_to_display(case.owner_id) or case.owner_id,
                        cached_owner_id_to_display(case.opened_by),
                        app_version_info.build_version,
                    ]
                    writer.writerow(row)
                    print row

        if (
            cases_to_delete
            and raw_input("\n".join(["Delete these {} cases? (y/N)".format(len(cases_to_delete))])).lower() == "y"
        ):
            case_accessor.soft_delete_cases([c.case_id for c in cases_to_delete])
            print "deleted {} cases".format(len(cases_to_delete))

        if cases_to_delete:
            print "details here: {}".format(options["filename"])
        else:
            print "didn't find any cases to delete"
예제 #53
0
class ReadonlyCaseDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.case_accessors = CaseAccessors(domain=domain)

    def get_document(self, doc_id):
        try:
            return self.case_accessors.get_case(doc_id).to_json()
        except CaseNotFound as e:
            raise DocumentNotFoundError(e)

    def iter_document_ids(self, last_id=None):
        # todo: support last_id
        return iter(self.case_accessors.get_case_ids_in_domain())

    def iter_documents(self, ids):
        for wrapped_case in self.case_accessors.iter_cases(ids):
            yield wrapped_case.to_json()
예제 #54
0
class FundamentalCaseTests(TestCase):

    @classmethod
    def setUpClass(cls):
        super(FundamentalCaseTests, cls).setUpClass()
        FormProcessorTestUtils.delete_all_cases(DOMAIN)
        FormProcessorTestUtils.delete_all_xforms(DOMAIN)

    @classmethod
    def tearDownClass(cls):
        FormProcessorTestUtils.delete_all_cases(DOMAIN)
        FormProcessorTestUtils.delete_all_xforms(DOMAIN)
        super(FundamentalCaseTests, cls).tearDownClass()

    def setUp(self):
        super(FundamentalCaseTests, self).setUp()
        self.interface = FormProcessorInterface()
        self.casedb = CaseAccessors()

    @run_with_all_backends
    def test_create_case(self):
        case_id = uuid.uuid4().hex
        modified_on = datetime.utcnow()
        _submit_case_block(
            True, case_id, user_id='user1', owner_id='owner1', case_type='demo',
            case_name='create_case', date_modified=modified_on, date_opened=modified_on, update={
                'dynamic': '123'
            }
        )

        case = self.casedb.get_case(case_id)
        self.assertIsNotNone(case)
        self.assertEqual(case.case_id, case_id)
        self.assertEqual(case.owner_id, 'owner1')
        self.assertEqual(case.type, 'demo')
        self.assertEqual(case.name, 'create_case')
        self.assertEqual(case.opened_on, modified_on)
        self.assertEqual(case.opened_by, 'user1')
        self.assertEqual(case.modified_on, modified_on)
        self.assertEqual(case.modified_by, 'user1')
        self.assertTrue(case.server_modified_on > modified_on)
        self.assertFalse(case.closed)
        self.assertIsNone(case.closed_on)

        if settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertIsNone(case.closed_by)
        else:
            self.assertEqual(case.closed_by, '')

        self.assertEqual(case.dynamic_case_properties()['dynamic'], '123')

    @run_with_all_backends
    def test_create_case_unicode_name(self):
        """
        Submit case blocks with unicode names
        """
        # This was failing hard:
        # http://manage.dimagi.com/default.asp?226582#1145687

        case_id = uuid.uuid4().hex
        modified_on = datetime.utcnow()
        case_name = u'प्रसव'
        _submit_case_block(
            True, case_id, user_id='user1', owner_id='owner1', case_type='demo',
            case_name=case_name, date_modified=modified_on, update={
                'dynamic': '123'
            }
        )
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.name, case_name)

    @run_with_all_backends
    def test_update_case(self):
        case_id = uuid.uuid4().hex
        opened_on = datetime.utcnow()
        _submit_case_block(
            True, case_id, user_id='user1', owner_id='owner1', case_type='demo',
            case_name='create_case', date_modified=opened_on, update={
                'dynamic': '123'
            }
        )

        modified_on = datetime.utcnow()
        _submit_case_block(
            False, case_id, user_id='user2', owner_id='owner2',
            case_name='update_case', date_modified=modified_on, date_opened=opened_on, update={
                'dynamic': '1234'
            }
        )

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.owner_id, 'owner2')
        self.assertEqual(case.name, 'update_case')
        self.assertEqual(coerce_to_datetime(case.opened_on), coerce_to_datetime(opened_on))
        self.assertEqual(case.opened_by, 'user1')
        self.assertEqual(case.modified_on, modified_on)
        self.assertEqual(case.modified_by, 'user2')
        self.assertTrue(case.server_modified_on > modified_on)
        self.assertFalse(case.closed)
        self.assertIsNone(case.closed_on)
        self.assertEqual(case.dynamic_case_properties()['dynamic'], '1234')

    @run_with_all_backends
    def test_close_case(self):
        # same as update, closed, closed on, closed by
        case_id = uuid.uuid4().hex
        opened_on = datetime.utcnow()
        _submit_case_block(
            True, case_id, user_id='user1', owner_id='owner1', case_type='demo',
            case_name='create_case', date_modified=opened_on
        )

        modified_on = datetime.utcnow()
        _submit_case_block(
            False, case_id, user_id='user2', date_modified=modified_on, close=True
        )

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.owner_id, 'owner1')
        self.assertEqual(case.modified_on, modified_on)
        self.assertEqual(case.modified_by, 'user2')
        self.assertTrue(case.closed)
        self.assertEqual(case.closed_on, modified_on)
        self.assertEqual(case.closed_by, 'user2')
        self.assertTrue(case.server_modified_on > modified_on)

    @run_with_all_backends
    def test_empty_update(self):
        case_id = uuid.uuid4().hex
        opened_on = datetime.utcnow()
        _submit_case_block(
            True, case_id, user_id='user1', owner_id='owner1', case_type='demo',
            case_name='create_case', date_modified=opened_on, update={
                'dynamic': '123'
            }
        )

        modified_on = datetime.utcnow()
        _submit_case_block(
            False, case_id, user_id='user2', date_modified=modified_on, update={}
        )

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties(), {'dynamic': '123'})

    @run_with_all_backends
    def test_case_with_index(self):
        # same as update, indexes
        mother_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother',
            case_name='mother', date_modified=datetime.utcnow()
        )

        child_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, child_case_id, user_id='user1', owner_id='owner1', case_type='child',
            case_name='child', date_modified=datetime.utcnow(), index={
                'mom': ('mother', mother_case_id)
            }
        )

        case = self.casedb.get_case(child_case_id)
        self.assertEqual(len(case.indices), 1)
        index = case.indices[0]
        self.assertEqual(index.identifier, 'mom')
        self.assertEqual(index.referenced_id, mother_case_id)
        self.assertEqual(index.referenced_type, 'mother')
        self.assertEqual(index.relationship, 'child')

    @run_with_all_backends
    def test_update_index(self):
        mother_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother',
            case_name='mother', date_modified=datetime.utcnow()
        )

        child_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, child_case_id, user_id='user1', owner_id='owner1', case_type='child',
            case_name='child', date_modified=datetime.utcnow(), index={
                'mom': ('mother', mother_case_id)
            }
        )

        case = self.casedb.get_case(child_case_id)
        self.assertEqual(case.indices[0].identifier, 'mom')

        _submit_case_block(
            False, child_case_id, user_id='user1', date_modified=datetime.utcnow(), index={
                'mom': ('other_mother', mother_case_id)
            }
        )
        case = self.casedb.get_case(child_case_id)
        self.assertEqual(case.indices[0].referenced_type, 'other_mother')

    @run_with_all_backends
    def test_delete_index(self):
        mother_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother',
            case_name='mother', date_modified=datetime.utcnow()
        )

        child_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, child_case_id, user_id='user1', owner_id='owner1', case_type='child',
            case_name='child', date_modified=datetime.utcnow(), index={
                'mom': ('mother', mother_case_id)
            }
        )

        case = self.casedb.get_case(child_case_id)
        self.assertEqual(len(case.indices), 1)

        _submit_case_block(
            False, child_case_id, user_id='user1', date_modified=datetime.utcnow(), index={
                'mom': ('mother', '')
            }
        )
        case = self.casedb.get_case(child_case_id)
        self.assertEqual(len(case.indices), 0)

    @run_with_all_backends
    def test_invalid_index(self):
        invalid_case_id = uuid.uuid4().hex
        child_case_id = uuid.uuid4().hex
        form, cases = _submit_case_block(
            True, child_case_id, user_id='user1', owner_id='owner1', case_type='child',
            case_name='child', date_modified=datetime.utcnow(), index={
                'mom': ('mother', invalid_case_id)
            }
        )
        self.assertEqual(0, len(cases))
        self.assertTrue(form.is_error)
        self.assertTrue('InvalidCaseIndex' in form.problem)

    @run_with_all_backends
    def test_invalid_index_cross_domain(self):
        mother_case_id = uuid.uuid4().hex
        _submit_case_block(
            True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother',
            case_name='mother', date_modified=datetime.utcnow(),
            domain='domain-1',
        )

        child_case_id = uuid.uuid4().hex
        form, cases = _submit_case_block(
            True, child_case_id, user_id='user1', owner_id='owner1', case_type='child',
            case_name='child', date_modified=datetime.utcnow(), index={
                'mom': ('mother', mother_case_id)
            },
            domain='domain-2',
        )
        self.assertEqual(0, len(cases))
        self.assertTrue(form.is_error)
        self.assertTrue('InvalidCaseIndex' in form.problem)

    def test_case_with_attachment(self):
        # same as update, attachments
        pass

    @run_with_all_backends
    def test_date_opened_coercion(self):
        delete_all_users()
        self.project = Domain(name='some-domain')
        self.project.save()
        user = create_restore_user(self.project.name)
        case_id = uuid.uuid4().hex
        modified_on = datetime.utcnow()
        case = CaseBlock(
            create=True,
            case_id=case_id,
            user_id=user.user_id, owner_id=user.user_id, case_type='demo',
            case_name='create_case', date_modified=modified_on, date_opened=modified_on, update={
                'dynamic': '123'
            }
        )

        post_case_blocks([case.as_xml()], domain='some-domain')
        # update the date_opened to date type to check for value on restore
        case.date_opened = case.date_opened.date()
        check_user_has_case(self, user, case.as_xml())

    @run_with_all_backends
    def test_restore_caches_cleared(self):
        cache = get_redis_default_cache()
        cache_key = restore_cache_key(RESTORE_CACHE_KEY_PREFIX, 'user_id', version="2.0")
        cache.set(cache_key, 'test-thing')
        self.assertEqual(cache.get(cache_key), 'test-thing')
        form = """
            <data xmlns="http://openrosa.org/formdesigner/blah">
                <meta>
                    <userID>{user_id}</userID>
                </meta>
            </data>
        """
        submit_form_locally(form.format(user_id='user_id'), DOMAIN)
        self.assertIsNone(cache.get(cache_key))
예제 #55
0
class TestHardDelete(TestCase):

    def setUp(self):
        self.casedb = CaseAccessors(TEST_DOMAIN_NAME)
        self.formdb = FormAccessors(TEST_DOMAIN_NAME)

    @run_with_all_backends
    def test_simple_delete(self):
        factory = CaseFactory()
        case = factory.create_case()
        [case] = factory.create_or_update_case(
            CaseStructure(case_id=case.case_id, attrs={'update': {'foo': 'bar'}})
        )
        self.assertIsNotNone(self.casedb.get_case(case.case_id))
        self.assertEqual(2, len(case.xform_ids))
        for form_id in case.xform_ids:
            self.assertIsNotNone(self.formdb.get_form(form_id))

        with capture_kafka_changes_context(topics.FORM_SQL, topics.CASE_SQL) as change_context:
            safe_hard_delete(case)

        if should_use_sql_backend(case.domain):
            self.assertEqual(3, len(change_context.changes))
            expected_ids = {case.case_id} | set(case.xform_ids)
            self.assertEqual(expected_ids, {change.id for change in change_context.changes})
            for change in change_context.changes:
                self.assertTrue(change.deleted)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case.case_id)

        for form_id in case.xform_ids:
            with self.assertRaises(XFormNotFound):
                self.formdb.get_form(form_id)

    @run_with_all_backends
    def test_delete_with_related(self):
        factory = CaseFactory()
        parent = factory.create_case()
        [child] = factory.create_or_update_case(
            CaseStructure(attrs={'create': True}, walk_related=False, indices=[
                CaseIndex(CaseStructure(case_id=parent.case_id))
            ]),
        )
        # deleting the parent should not be allowed because the child still references it
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(parent)

        # deleting the child is ok
        safe_hard_delete(child)
        self.assertIsNotNone(self.casedb.get_case(parent.case_id))
        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(child.case_id)

    @run_with_all_backends
    def test_delete_sharing_form(self):
        factory = CaseFactory()
        c1, c2 = factory.create_or_update_cases([
            CaseStructure(attrs={'create': True}),
            CaseStructure(attrs={'create': True}),
        ])
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c1)

        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c2)

        self.assertIsNotNone(self.casedb.get_case(c1.case_id))
        self.assertIsNotNone(self.casedb.get_case(c2.case_id))
예제 #56
0
    def test_form_archiving(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, p1='p1-1', p2='p2-1',
                            form_extras={'received_on': now})
        _post_util(case_id=case_id, p2='p2-2', p3='p3-2', p4='p4-2',
                  form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p4='p4-3', p5='p5-3', close=True,
                  form_extras={'received_on': now + timedelta(seconds=2)})

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        closed_by = case.closed_by
        closed_on = case.closed_on
        self.assertNotEqual('', closed_by)
        self.assertNotEqual(None, closed_on)

        def _check_initial_state(case):
            self.assertTrue(case.closed)
            self.assertEqual(closed_by, case.closed_by)
            self.assertEqual(closed_on, case.closed_on)
            self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
            self.assertEqual(case.get_case_property('p2'), 'p2-2')  # updated in second post
            self.assertEqual(case.get_case_property('p3'), 'p3-2')  # new in second post
            self.assertEqual(case.get_case_property('p4'), 'p4-3')  # updated in third post
            self.assertEqual(case.get_case_property('p5'), 'p5-3')  # new in third post
            if should_use_sql_backend(REBUILD_TEST_DOMAIN):
                # SQL stores one transaction per form
                self.assertEqual(3, len(primary_actions(case)))  # create + update + close
            else:
                self.assertEqual(5, len(primary_actions(case)))  # create + 3 updates + close

        _check_initial_state(case)

        # verify xform/action states
        [f1, f2, f3] = case.xform_ids
        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            [create, update, close] = case.actions
            self.assertEqual(f1, create.form_id)
            self.assertEqual(f2, update.form_id)
            self.assertEqual(f3, close.form_id)
        else:
            [create, u1, u2, u3, close] = case.actions
            self.assertEqual(f1, create.form_id)
            self.assertEqual(f1, u1.form_id)
            self.assertEqual(f2, u2.form_id)
            self.assertEqual(f3, u3.form_id)

        # todo: should this be the behavior for archiving the create form?
        form_acessors = FormAccessors(REBUILD_TEST_DOMAIN)
        f1_doc = form_acessors.get_form(f1)
        with capture_kafka_changes_context(topics.CASE_SQL) as change_context:
            f1_doc.archive()

        if should_use_sql_backend(case.domain):
            self.assertEqual([case.case_id], [change.id for change in change_context.changes])

        case = case_accessors.get_case(case_id)

        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(2, len(primary_actions(case)))
        else:
            self.assertEqual(3, len(primary_actions(case)))

        [u2, u3] = case.xform_ids
        self.assertEqual(f2, u2)
        self.assertEqual(f3, u3)

        self.assertTrue(case.closed)  # no change
        self.assertFalse('p1' in case.dynamic_case_properties())  # should disappear entirely
        self.assertEqual(case.get_case_property('p2'), 'p2-2')  # no change
        self.assertEqual(case.get_case_property('p3'), 'p3-2')  # no change
        self.assertEqual(case.get_case_property('p4'), 'p4-3')  # no change
        self.assertEqual(case.get_case_property('p5'), 'p5-3')  # no change

        def _reset(form_id):
            form_doc = form_acessors.get_form(form_id)
            form_doc.unarchive()
            case = case_accessors.get_case(case_id)
            _check_initial_state(case)

        _reset(f1)

        f2_doc = form_acessors.get_form(f2)
        f2_doc.archive()
        case = case_accessors.get_case(case_id)

        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(2, len(primary_actions(case)))
        else:
            self.assertEqual(4, len(primary_actions(case)))

        [u1, u3] = case.xform_ids
        self.assertEqual(f1, u1)
        self.assertEqual(f3, u3)

        self.assertTrue(case.closed)  # no change
        self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
        self.assertEqual(case.get_case_property('p2'), 'p2-1')  # loses second form update
        self.assertFalse('p3' in case.dynamic_case_properties())  # should disappear entirely
        self.assertEqual(case.get_case_property('p4'), 'p4-3')  # no change
        self.assertEqual(case.get_case_property('p5'), 'p5-3')  # no change

        _reset(f2)

        f3_doc = form_acessors.get_form(f3)
        f3_doc.archive()
        case = case_accessors.get_case(case_id)

        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(2, len(primary_actions(case)))
        else:
            self.assertEqual(3, len(primary_actions(case)))

        [u1, u2] = case.xform_ids
        self.assertEqual(f1, u1)
        self.assertEqual(f2, u2)

        self.assertFalse(case.closed)  # reopened!
        self.assertEqual('', case.closed_by)
        self.assertEqual(None, case.closed_on)
        self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
        self.assertEqual(case.get_case_property('p2'), 'p2-2')  # original
        self.assertEqual(case.get_case_property('p3'), 'p3-2')  # new in second post
        self.assertEqual(case.get_case_property('p4'), 'p4-2')  # loses third form update
        self.assertFalse('p5' in case.dynamic_case_properties())  # should disappear entirely
        _reset(f3)
class AutomaticCaseUpdateTest(TestCase):
    def setUp(self):
        self.domain = 'auto-update-test'
        self.case_db = CaseAccessors(self.domain)
        self.factory = CaseFactory(self.domain)
        self.rule = AutomaticUpdateRule(
            domain=self.domain,
            name='test-rule',
            case_type='test-case-type',
            active=True,
            server_modified_boundary=30,
        )
        self.rule.save()
        self.rule.automaticupdaterulecriteria_set = [
            AutomaticUpdateRuleCriteria(
                property_name='last_visit_date',
                property_value='30',
                match_type=AutomaticUpdateRuleCriteria.MATCH_DAYS_SINCE,
            ),
        ]
        self.rule.automaticupdateaction_set = [
            AutomaticUpdateAction(
                action=AutomaticUpdateAction.ACTION_UPDATE,
                property_name='update_flag',
                property_value='Y',
            ),
            AutomaticUpdateAction(
                action=AutomaticUpdateAction.ACTION_CLOSE,
            ),
        ]

        self.rule2 = AutomaticUpdateRule(
            domain=self.domain,
            name='test-rule-2',
            case_type='test-case-type-2',
            active=True,
            server_modified_boundary=30,
        )
        self.rule2.save()
        self.rule2.automaticupdateaction_set = [
            AutomaticUpdateAction(
                action=AutomaticUpdateAction.ACTION_CLOSE,
            ),
        ]

        self.rule3 = AutomaticUpdateRule(
            domain=self.domain,
            name='test-rule-3',
            case_type='test-case-type-2',
            active=True,
            server_modified_boundary=50,
        )
        self.rule3.save()
        self.rule3.automaticupdateaction_set = [
            AutomaticUpdateAction(
                action=AutomaticUpdateAction.ACTION_CLOSE,
            ),
        ]

        with drop_connected_signals(case_post_save):
            case = self.factory.create_case(case_type='test-case-type')
        self.case_id = case.case_id

    def tearDown(self):
        AutomaticUpdateRuleCriteria.objects.all().delete()
        AutomaticUpdateAction.objects.all().delete()
        AutomaticUpdateRule.objects.all().delete()
        FormProcessorTestUtils.delete_all_cases(self.domain)

    def _get_case_ids(self, *args, **kwargs):
        return [self.case_id]

    def _get_case(self):
        return self.case_db.get_case(self.case_id)

    def _assert_case_revision(self, rev_number, last_modified, expect_modified=False):
        if should_use_sql_backend(self.domain):
            self.assertEqual(
                expect_modified,
                CaseAccessorSQL.case_modified_since(self.case_id, last_modified)
            )
        else:
            doc = self._get_case()
            self.assertTrue(doc['_rev'].startswith('%s-' % rev_number))

    @run_with_all_backends
    def test_rule(self):
        now = datetime(2015, 10, 22, 0, 0)
        with patch('corehq.apps.data_interfaces.models.AutomaticUpdateRule.get_case_ids', new=self._get_case_ids):
            # No update: both dates are 27 days away
            last_modified = datetime(2015, 9, 25, 12, 0)
            _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 25))
            self._assert_case_revision(2, last_modified)
            run_case_update_rules_for_domain(self.domain, now=now)
            self._assert_case_revision(2, last_modified)

            # No update: server_modified_on is 32 days away but last_visit_date is 27 days away
            last_modified = datetime(2015, 9, 20, 12, 0)
            _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 25))
            self._assert_case_revision(3, last_modified)
            run_case_update_rules_for_domain(self.domain, now=now)
            self._assert_case_revision(3, last_modified)

            # No update: last_visit_date is 32 days away but server_modified_on is 27 days away
            last_modified = datetime(2015, 9, 25, 12, 0)
            _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 20))
            self._assert_case_revision(4, last_modified)
            run_case_update_rules_for_domain(self.domain, now=now)
            self._assert_case_revision(4, last_modified)

            # Perform update: both dates are 32 days away
            last_modified = datetime(2015, 9, 20, 12, 0)
            _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 20))
            self._assert_case_revision(5, last_modified)
            with drop_connected_signals(case_post_save):
                run_case_update_rules_for_domain(self.domain, now=now)
            self._assert_case_revision(6, last_modified, True)

            case = self._get_case()
            self.assertEqual(case.get_case_property('update_flag'), 'Y')
            self.assertEqual(case.closed, True)

    @run_with_all_backends
    def test_match_days_since(self):
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:
            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='last_visit_date',
                    property_value='30',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_DAYS_SINCE,
                ),
            ]
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'last_visit_date', '2015-12-30')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'last_visit_date', '2015-12-03')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'last_visit_date', '2015-12-02')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'last_visit_date', '2015-11-01')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    @run_with_all_backends
    def test_match_equal(self):
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:
            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='property1',
                    property_value='value1',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_EQUAL,
                ),
            ]
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property1', 'x')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property1', 'value1')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    @run_with_all_backends
    def test_match_not_equal(self):
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:
            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='property2',
                    property_value='value2',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_NOT_EQUAL,
                ),
            ]
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property2', 'value2')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property2', 'x')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    @run_with_all_backends
    def test_date_case_properties_for_equality(self):
        """
        Date case properties are automatically converted from string to date
        when fetching from the db, so here we want to make sure this doesn't
        interfere with our ability to compare dates for equality.
        """
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:
            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='property1',
                    property_value='2016-02-24',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_EQUAL,
                ),
            ]

            set_case_property_directly(case, 'property1', '2016-02-24')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property1', '2016-02-25')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    @run_with_all_backends
    def test_date_case_properties_for_inequality(self):
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:
            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='property1',
                    property_value='2016-02-24',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_NOT_EQUAL,
                ),
            ]

            set_case_property_directly(case, 'property1', '2016-02-24')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property1', '2016-02-25')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    @run_with_all_backends
    def test_match_has_value(self):
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:
            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='property3',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_HAS_VALUE,
                ),
            ]
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property3', 'x')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property3', '')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    @run_with_all_backends
    def test_and_criteria(self):
        with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case:

            self.rule2.automaticupdaterulecriteria_set = [
                AutomaticUpdateRuleCriteria(
                    property_name='last_visit_date',
                    property_value='30',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_DAYS_SINCE,
                ),
                AutomaticUpdateRuleCriteria(
                    property_name='property1',
                    property_value='value1',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_EQUAL,
                ),
                AutomaticUpdateRuleCriteria(
                    property_name='property2',
                    property_value='value2',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_NOT_EQUAL,
                ),
                AutomaticUpdateRuleCriteria(
                    property_name='property3',
                    match_type=AutomaticUpdateRuleCriteria.MATCH_HAS_VALUE,
                ),
            ]

            set_case_property_directly(case, 'last_visit_date', '2015-11-01')
            set_case_property_directly(case, 'property1', 'value1')
            set_case_property_directly(case, 'property2', 'x')
            set_case_property_directly(case, 'property3', 'x')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'last_visit_date', '2015-12-30')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'last_visit_date', '2015-11-01')
            set_case_property_directly(case, 'property1', 'x')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property1', 'value1')
            set_case_property_directly(case, 'property2', 'value2')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property2', 'x')
            set_case_property_directly(case, 'property3', '')
            self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

            set_case_property_directly(case, 'property3', 'x')
            self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1)))

    def test_get_rules_from_domain(self):
        rules = AutomaticUpdateRule.by_domain(self.domain)
        rules_by_case_type = AutomaticUpdateRule.organize_rules_by_case_type(rules)

        expected_case_types = ['test-case-type', 'test-case-type-2']
        actual_case_types = rules_by_case_type.keys()
        self.assertEqual(set(expected_case_types), set(actual_case_types))

        expected_rule_ids = [self.rule.pk]
        actual_rule_ids = [rule.pk for rule in rules_by_case_type['test-case-type']]
        self.assertEqual(set(expected_rule_ids), set(actual_rule_ids))

        expected_rule_ids = [self.rule2.pk, self.rule3.pk]
        actual_rule_ids = [rule.pk for rule in rules_by_case_type['test-case-type-2']]
        self.assertEqual(set(expected_rule_ids), set(actual_rule_ids))

    def test_boundary_date(self):
        rules = AutomaticUpdateRule.by_domain(self.domain)
        rules_by_case_type = AutomaticUpdateRule.organize_rules_by_case_type(rules)

        boundary_date = AutomaticUpdateRule.get_boundary_date(
            rules_by_case_type['test-case-type'], datetime(2016, 1, 1))
        self.assertEqual(boundary_date, datetime(2015, 12, 2))

        boundary_date = AutomaticUpdateRule.get_boundary_date(
            rules_by_case_type['test-case-type-2'], datetime(2016, 1, 1))
        self.assertEqual(boundary_date, datetime(2015, 12, 2))
예제 #58
0
class ImporterTest(TestCase):

    def setUp(self):
        super(ImporterTest, self).setUp()
        self.domain_obj = create_domain("importer-test")
        self.domain = self.domain_obj.name
        self.default_case_type = 'importer-test-casetype'

        self.couch_user = WebUser.create(None, "test", "foobar")
        self.couch_user.add_domain_membership(self.domain, is_admin=True)
        self.couch_user.save()

        self.accessor = CaseAccessors(self.domain)

        self.factory = CaseFactory(domain=self.domain, case_defaults={
            'case_type': self.default_case_type,
        })
        delete_all_cases()

    def tearDown(self):
        self.couch_user.delete()
        self.domain_obj.delete()
        super(ImporterTest, self).tearDown()

    def _config(self, col_names, search_column=None, case_type=None,
                search_field='case_id', create_new_cases=True):
        return ImporterConfig(
            couch_user_id=self.couch_user._id,
            case_type=case_type or self.default_case_type,
            excel_fields=col_names,
            case_fields=[''] * len(col_names),
            custom_fields=col_names,
            search_column=search_column or col_names[0],
            search_field=search_field,
            create_new_cases=create_new_cases,
        )

    @run_with_all_backends
    @patch('corehq.apps.case_importer.tasks.bulk_import_async.update_state')
    def testImportNone(self, update_state):
        res = bulk_import_async.delay(self._config(['anything']), self.domain, None)
        self.assertIsInstance(res.result, Ignore)
        update_state.assert_called_with(
            state=states.FAILURE,
            meta={'errors': 'Sorry, your session has expired. Please start over and try again.'})
        self.assertEqual(0, len(get_case_ids_in_domain(self.domain)))

    @run_with_all_backends
    def testImportBasic(self):
        config = self._config(['case_id', 'age', 'sex', 'location'])
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            ['case_id-0', 'age-0', 'sex-0', 'location-0'],
            ['case_id-1', 'age-1', 'sex-1', 'location-1'],
            ['case_id-2', 'age-2', 'sex-2', 'location-2'],
            ['case_id-3', 'age-3', 'sex-3', 'location-3'],
            ['case_id-4', 'age-4', 'sex-4', 'location-4'],
        )
        res = do_import(file, config, self.domain)
        self.assertEqual(5, res['created_count'])
        self.assertEqual(0, res['match_count'])
        self.assertFalse(res['errors'])
        self.assertEqual(1, res['num_chunks'])
        case_ids = self.accessor.get_case_ids_in_domain()
        cases = list(self.accessor.get_cases(case_ids))
        self.assertEqual(5, len(cases))
        properties_seen = set()
        for case in cases:
            self.assertEqual(self.couch_user._id, case.user_id)
            self.assertEqual(self.couch_user._id, case.owner_id)
            self.assertEqual(self.default_case_type, case.type)
            for prop in ['age', 'sex', 'location']:
                self.assertTrue(prop in case.get_case_property(prop))
                self.assertFalse(case.get_case_property(prop) in properties_seen)
                properties_seen.add(case.get_case_property(prop))

    @run_with_all_backends
    def testImportNamedColumns(self):
        config = self._config(['case_id', 'age', 'sex', 'location'])
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            ['case_id-0', 'age-0', 'sex-0', 'location-0'],
            ['case_id-1', 'age-1', 'sex-1', 'location-1'],
            ['case_id-2', 'age-2', 'sex-2', 'location-2'],
            ['case_id-3', 'age-3', 'sex-3', 'location-3'],
        )
        res = do_import(file, config, self.domain)

        self.assertEqual(4, res['created_count'])
        self.assertEqual(4, len(self.accessor.get_case_ids_in_domain()))

    @run_with_all_backends
    def testImportTrailingWhitespace(self):
        cols = ['case_id', 'age', 'sex\xa0', 'location']
        config = self._config(cols)
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex\xa0', 'location'],
            ['case_id-0', 'age-0', 'sex\xa0-0', 'location-0'],
        )
        res = do_import(file, config, self.domain)

        self.assertEqual(1, res['created_count'])
        case_ids = self.accessor.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        case = self.accessor.get_case(case_ids[0])
        self.assertTrue(bool(case.get_case_property('sex')))  # make sure the value also got properly set

    @run_with_all_backends
    def testCaseIdMatching(self):
        # bootstrap a stub case
        [case] = self.factory.create_or_update_case(CaseStructure(attrs={
            'create': True,
            'update': {'importer_test_prop': 'foo'},
        }))
        self.assertEqual(1, len(self.accessor.get_case_ids_in_domain()))

        config = self._config(['case_id', 'age', 'sex', 'location'])
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            [case.case_id, 'age-0', 'sex-0', 'location-0'],
            [case.case_id, 'age-1', 'sex-1', 'location-1'],
            [case.case_id, 'age-2', 'sex-2', 'location-2'],
        )
        res = do_import(file, config, self.domain)
        self.assertEqual(0, res['created_count'])
        self.assertEqual(3, res['match_count'])
        self.assertFalse(res['errors'])

        # shouldn't create any more cases, just the one
        case_ids = self.accessor.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        [case] = self.accessor.get_cases(case_ids)
        for prop in ['age', 'sex', 'location']:
            self.assertTrue(prop in case.get_case_property(prop))

        # shouldn't touch existing properties
        self.assertEqual('foo', case.get_case_property('importer_test_prop'))

    @run_with_all_backends
    def testCaseLookupTypeCheck(self):
        [case] = self.factory.create_or_update_case(CaseStructure(attrs={
            'create': True,
            'case_type': 'nonmatch-type',
        }))
        self.assertEqual(1, len(self.accessor.get_case_ids_in_domain()))
        config = self._config(['case_id', 'age', 'sex', 'location'])
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            [case.case_id, 'age-0', 'sex-0', 'location-0'],
            [case.case_id, 'age-1', 'sex-1', 'location-1'],
            [case.case_id, 'age-2', 'sex-2', 'location-2'],
        )
        res = do_import(file, config, self.domain)
        # because the type is wrong these shouldn't match
        self.assertEqual(3, res['created_count'])
        self.assertEqual(0, res['match_count'])
        self.assertEqual(4, len(self.accessor.get_case_ids_in_domain()))

    @run_with_all_backends
    def testCaseLookupDomainCheck(self):
        self.factory.domain = 'wrong-domain'
        [case] = self.factory.create_or_update_case(CaseStructure(attrs={
            'create': True,
        }))
        self.assertEqual(0, len(self.accessor.get_case_ids_in_domain()))
        config = self._config(['case_id', 'age', 'sex', 'location'])
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            [case.case_id, 'age-0', 'sex-0', 'location-0'],
            [case.case_id, 'age-1', 'sex-1', 'location-1'],
            [case.case_id, 'age-2', 'sex-2', 'location-2'],
        )
        res = do_import(file, config, self.domain)

        # because the domain is wrong these shouldn't match
        self.assertEqual(3, res['created_count'])
        self.assertEqual(0, res['match_count'])
        self.assertEqual(3, len(self.accessor.get_case_ids_in_domain()))

    @run_with_all_backends
    def testExternalIdMatching(self):
        # bootstrap a stub case
        external_id = 'importer-test-external-id'
        [case] = self.factory.create_or_update_case(CaseStructure(
            attrs={
                'create': True,
                'external_id': external_id,
            }
        ))
        self.assertEqual(1, len(self.accessor.get_case_ids_in_domain()))

        headers = ['external_id', 'age', 'sex', 'location']
        config = self._config(headers, search_field='external_id')
        file = make_worksheet_wrapper(
            ['external_id', 'age', 'sex', 'location'],
            ['importer-test-external-id', 'age-0', 'sex-0', 'location-0'],
            ['importer-test-external-id', 'age-1', 'sex-1', 'location-1'],
            ['importer-test-external-id', 'age-2', 'sex-2', 'location-2'],
        )
        res = do_import(file, config, self.domain)
        self.assertEqual(0, res['created_count'])
        self.assertEqual(3, res['match_count'])
        self.assertFalse(res['errors'])

        # shouldn't create any more cases, just the one
        self.assertEqual(1, len(self.accessor.get_case_ids_in_domain()))

    @run_with_all_backends
    def test_external_id_matching_on_create_with_custom_column_name(self):
        headers = ['id_column', 'age', 'sex', 'location']
        external_id = 'external-id-test'
        config = self._config(headers[1:], search_column='id_column', search_field='external_id')
        file = make_worksheet_wrapper(
            ['id_column', 'age', 'sex', 'location'],
            ['external-id-test', 'age-0', 'sex-0', 'location-0'],
            ['external-id-test', 'age-1', 'sex-1', 'location-1'],
        )

        res = do_import(file, config, self.domain)
        self.assertFalse(res['errors'])
        self.assertEqual(1, res['created_count'])
        self.assertEqual(1, res['match_count'])
        case_ids = self.accessor.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        case = self.accessor.get_case(case_ids[0])
        self.assertEqual(external_id, case.external_id)

    def testNoCreateNew(self):
        config = self._config(['case_id', 'age', 'sex', 'location'], create_new_cases=False)
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            ['case_id-0', 'age-0', 'sex-0', 'location-0'],
            ['case_id-1', 'age-1', 'sex-1', 'location-1'],
            ['case_id-2', 'age-2', 'sex-2', 'location-2'],
            ['case_id-3', 'age-3', 'sex-3', 'location-3'],
            ['case_id-4', 'age-4', 'sex-4', 'location-4'],
        )
        res = do_import(file, config, self.domain)

        # no matching and no create new set - should do nothing
        self.assertEqual(0, res['created_count'])
        self.assertEqual(0, res['match_count'])
        self.assertEqual(0, len(get_case_ids_in_domain(self.domain)))

    def testBlankRows(self):
        # don't create new cases for rows left blank
        config = self._config(['case_id', 'age', 'sex', 'location'], create_new_cases=True)
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            [None, None, None, None],
            ['', '', '', ''],
        )
        res = do_import(file, config, self.domain)

        # no matching and no create new set - should do nothing
        self.assertEqual(0, res['created_count'])
        self.assertEqual(0, res['match_count'])
        self.assertEqual(0, len(get_case_ids_in_domain(self.domain)))

    def testBasicChunking(self):
        config = self._config(['case_id', 'age', 'sex', 'location'])
        file = make_worksheet_wrapper(
            ['case_id', 'age', 'sex', 'location'],
            ['case_id-0', 'age-0', 'sex-0', 'location-0'],
            ['case_id-1', 'age-1', 'sex-1', 'location-1'],
            ['case_id-2', 'age-2', 'sex-2', 'location-2'],
            ['case_id-3', 'age-3', 'sex-3', 'location-3'],
            ['case_id-4', 'age-4', 'sex-4', 'location-4'],
        )
        res = do_import(file, config, self.domain, chunksize=2)
        # 5 cases in chunks of 2 = 3 chunks
        self.assertEqual(3, res['num_chunks'])
        self.assertEqual(5, res['created_count'])
        self.assertEqual(5, len(get_case_ids_in_domain(self.domain)))

    @run_with_all_backends
    def testExternalIdChunking(self):
        # bootstrap a stub case
        external_id = 'importer-test-external-id'

        headers = ['external_id', 'age', 'sex', 'location']
        config = self._config(headers, search_field='external_id')
        file = make_worksheet_wrapper(
            ['external_id', 'age', 'sex', 'location'],
            ['importer-test-external-id', 'age-0', 'sex-0', 'location-0'],
            ['importer-test-external-id', 'age-1', 'sex-1', 'location-1'],
            ['importer-test-external-id', 'age-2', 'sex-2', 'location-2'],
        )

        # the first one should create the case, and the remaining two should update it
        res = do_import(file, config, self.domain)
        self.assertEqual(1, res['created_count'])
        self.assertEqual(2, res['match_count'])
        self.assertFalse(res['errors'])
        self.assertEqual(2, res['num_chunks']) # the lookup causes an extra chunk

        # should just create the one case
        case_ids = self.accessor.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        [case] = self.accessor.get_cases(case_ids)
        self.assertEqual(external_id, case.external_id)
        for prop in ['age', 'sex', 'location']:
            self.assertTrue(prop in case.get_case_property(prop))

    @run_with_all_backends
    def testParentCase(self):
        headers = ['parent_id', 'name', 'case_id']
        config = self._config(headers, create_new_cases=True, search_column='case_id')
        rows = 3
        [parent_case] = self.factory.create_or_update_case(CaseStructure(attrs={'create': True}))
        self.assertEqual(1, len(self.accessor.get_case_ids_in_domain()))

        file = make_worksheet_wrapper(
            ['parent_id', 'name', 'case_id'],
            [parent_case.case_id, 'name-0', 'case_id-0'],
            [parent_case.case_id, 'name-1', 'case_id-1'],
            [parent_case.case_id, 'name-2', 'case_id-2'],
        )
        file_missing = make_worksheet_wrapper(
            ['parent_id', 'name', 'case_id'],
            ['parent_id-0', 'name-0', 'case_id-0'],
            ['parent_id-1', 'name-1', 'case_id-1'],
            ['parent_id-2', 'name-2', 'case_id-2'],
        )

        # Should successfully match on `rows` cases
        res = do_import(file, config, self.domain)
        self.assertEqual(rows, res['created_count'])

        # Should be unable to find parent case on `rows` cases
        res = do_import(file_missing, config, self.domain)
        error_column_name = 'parent_id'
        self.assertEqual(rows,
                         len(res['errors'][ImportErrors.InvalidParentId][error_column_name]['rows']),
                         "All cases should have missing parent")

    def import_mock_file(self, rows):
        config = self._config(rows[0])
        xls_file = make_worksheet_wrapper(*rows)
        return do_import(xls_file, config, self.domain)

    @run_with_all_backends
    def testLocationOwner(self):
        # This is actually testing several different things, but I figure it's
        # worth it, as each of these tests takes a non-trivial amount of time.
        non_case_sharing = LocationType.objects.create(
            domain=self.domain, name='lt1', shares_cases=False
        )
        case_sharing = LocationType.objects.create(
            domain=self.domain, name='lt2', shares_cases=True
        )
        location = make_loc('loc-1', 'Loc 1', self.domain, case_sharing.code)
        make_loc('loc-2', 'Loc 2', self.domain, case_sharing.code)
        duplicate_loc = make_loc('loc-3', 'Loc 2', self.domain, case_sharing.code)
        improper_loc = make_loc('loc-4', 'Loc 4', self.domain, non_case_sharing.code)

        res = self.import_mock_file([
            ['case_id', 'name', 'owner_id', 'owner_name'],
            ['', 'location-owner-id', location.group_id, ''],
            ['', 'location-owner-code', '', location.site_code],
            ['', 'location-owner-name', '', location.name],
            ['', 'duplicate-location-name', '', duplicate_loc.name],
            ['', 'non-case-owning-name', '', improper_loc.name],
        ])
        case_ids = self.accessor.get_case_ids_in_domain()
        cases = {c.name: c for c in list(self.accessor.get_cases(case_ids))}

        self.assertEqual(cases['location-owner-id'].owner_id, location.group_id)
        self.assertEqual(cases['location-owner-code'].owner_id, location.group_id)
        self.assertEqual(cases['location-owner-name'].owner_id, location.group_id)

        error_message = ImportErrors.DuplicateLocationName
        error_column_name = None
        self.assertIn(error_message, res['errors'])
        self.assertEqual(res['errors'][error_message][error_column_name]['rows'], [5])

        error_message = ImportErrors.InvalidOwnerId
        self.assertIn(error_message, res['errors'])
        error_column_name = 'owner_id'
        self.assertEqual(res['errors'][error_message][error_column_name]['rows'], [6])

    @run_with_all_backends
    def test_opened_on(self):
        case = self.factory.create_case()
        new_date = '2015-04-30T14:41:53.000000Z'
        with flag_enabled('BULK_UPLOAD_DATE_OPENED'):
            self.import_mock_file([
                ['case_id', 'date_opened'],
                [case.case_id, new_date]
            ])
        case = CaseAccessors(self.domain).get_case(case.case_id)
        self.assertEqual(case.opened_on, PhoneTime(parse_datetime(new_date)).done())

    @run_with_all_backends
    def test_columns_and_rows_align(self):
        case_owner = CommCareUser.create(self.domain, 'username', 'pw')
        res = self.import_mock_file([
            ['case_id', 'name', '', 'favorite_color', 'owner_id'],
            ['', 'Jeff', '', 'blue', case_owner._id],
            ['', 'Caroline', '', 'yellow', case_owner._id],
        ])
        self.assertEqual(res['errors'], {})

        case_ids = self.accessor.get_case_ids_in_domain()
        cases = {c.name: c for c in list(self.accessor.get_cases(case_ids))}
        self.assertEqual(cases['Jeff'].owner_id, case_owner._id)
        self.assertEqual(cases['Jeff'].get_case_property('favorite_color'), 'blue')
        self.assertEqual(cases['Caroline'].owner_id, case_owner._id)
        self.assertEqual(cases['Caroline'].get_case_property('favorite_color'), 'yellow')
예제 #59
0
class EditFormTest(TestCase, TestFileMixin):
    ID = '7H46J37FGH3'
    domain = 'test-form-edits'

    file_path = ('data', 'deprecation')
    root = os.path.dirname(__file__)

    def setUp(self):
        self.interface = FormProcessorInterface(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.formdb = FormAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms(self.domain)
        FormProcessorTestUtils.delete_all_cases(self.domain)
        UnfinishedSubmissionStub.objects.all().delete()

    @run_with_all_backends
    def test_basic_edit(self):
        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        xform = post_xform(original_xml, domain=self.domain)

        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("", xform.form_data['vitals']['height'])
        self.assertEqual("other", xform.form_data['assessment']['categories'])

        xform = post_xform(edit_xml, domain=self.domain)
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("100", xform.form_data['vitals']['height'])
        self.assertEqual("Edited Baby!", xform.form_data['assessment']['categories'])

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)

        self.assertEqual(self.ID, deprecated_xform.orig_id)
        self.assertNotEqual(self.ID, deprecated_xform.form_id)
        self.assertTrue(deprecated_xform.is_deprecated)
        self.assertEqual("", deprecated_xform.form_data['vitals']['height'])
        self.assertEqual("other", deprecated_xform.form_data['assessment']['categories'])

        self.assertEqual(xform.received_on, deprecated_xform.received_on)
        self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id)
        self.assertTrue(xform.edited_on > deprecated_xform.received_on)

        self.assertEqual(
            deprecated_xform.get_xml(),
            original_xml
        )
        self.assertEqual(xform.get_xml(), edit_xml)

    @run_with_all_backends
    def test_broken_save(self):
        """
        Test that if the second form submission terminates unexpectedly
        and the main form isn't saved, then there are no side effects
        such as the original having been marked as deprecated.
        """

        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        _, xform, _ = submit_form_locally(original_xml, self.domain)
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual(self.domain, xform.domain)

        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            0
        )

        with patch.object(self.interface.processor, 'save_processed_models', side_effect=RequestFailed):
            with self.assertRaises(RequestFailed):
                submit_form_locally(edit_xml, self.domain)

        # it didn't go through, so make sure there are no edits still
        self.assertIsNone(getattr(xform, 'deprecated_form_id', None))

        xform = self.formdb.get_form(self.ID)
        self.assertIsNotNone(xform)
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID,
                                                    saved=False).count(),
            1
        )
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            1
        )

    @run_with_all_backends
    def test_case_management(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
            owner_id=owner_id,
            update={
                'property': 'original value'
            }
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # validate some assumptions
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'person')
        self.assertEqual(case.dynamic_case_properties()['property'], 'original value')
        self.assertEqual([form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

        # submit a new form with a different case update
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='newtype',
            owner_id=owner_id,
            update={
                'property': 'edited value'
            }
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'newtype')
        self.assertEqual(case.dynamic_case_properties()['property'], 'edited value')
        self.assertEqual([form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

    @run_with_all_backends
    def test_second_edit_fails(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # submit an edit form with a bad case update (for example a bad ID)
        case_block = CaseBlock(
            create=True,
            case_id='',
            case_type='person',
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        xform = self.formdb.get_form(form_id)
        self.assertTrue(xform.is_error)

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)
        self.assertTrue(deprecated_xform.is_deprecated)

    @run_with_all_backends
    def test_case_management_ordering(self):
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex

        # create a case
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
            owner_id=owner_id,
        ).as_string()
        create_form_id = submit_case_blocks(case_block, domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual([create_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual([create_form_id], [a.xform_id for a in case.actions])
            for a in case.actions:
                self.assertEqual(create_form_id, a.xform_id)

        edit_date = datetime.utcnow()
        # set some property value
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            date_modified=edit_date,
            update={
                'property': 'first value',
            }
        ).as_string()
        edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'], 'first value')
        self.assertEqual([create_form_id, edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual([create_form_id, edit_form_id], [a.xform_id for a in case.actions])

        # submit a second (new) form updating the value
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            update={
                'property': 'final value',
            }
        ).as_string()
        second_edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'], 'final value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(
                [create_form_id, edit_form_id, second_edit_form_id],
                [a.xform_id for a in case.actions]
            )

        # deprecate the middle edit
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            date_modified=edit_date,  # need to use the previous edit date for action sort comparisons
            update={
                'property': 'edited value',
                'added_property': 'added value',
            }
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=edit_form_id)

        # ensure that the middle edit stays in the right place and is applied
        # before the final one
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'], 'final value')
        self.assertEqual(case.dynamic_case_properties()['added_property'], 'added value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(
                [create_form_id, edit_form_id, second_edit_form_id],
                [a.xform_id for a in case.actions]
            )
예제 #60
0
class TestFormArchiving(TestCase, TestFileMixin):
    file_path = ('data', 'sample_xforms')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(TestFormArchiving, self).setUp()
        self.casedb = CaseAccessors('test-domain')
        self.formdb = FormAccessors('test-domain')

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms()
        FormProcessorTestUtils.delete_all_cases()
        super(TestFormArchiving, self).tearDown()

    def testArchive(self):
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.archive(user_id='mr. librarian')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_archived)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        self.assertEqual(case.xform_ids, [])

        [archival] = xform.history
        self.assertTrue(lower_bound <= archival.date <= upper_bound)
        self.assertEqual('archive', archival.operation)
        self.assertEqual('mr. librarian', archival.user)

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.unarchive(user_id='mr. researcher')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_normal)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(case.xform_ids, [xform.form_id])

        [archival, restoration] = xform.history
        self.assertTrue(lower_bound <= restoration.date <= upper_bound)
        self.assertEqual('unarchive', restoration.operation)
        self.assertEqual('mr. researcher', restoration.user)

    def testUnfinishedArchiveStub(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Mock the archive function throwing an error
        with mock.patch('couchforms.signals.xform_archived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.archive(user_id='librarian')
            except Exception:
                pass

        # Get the form with the updated history, it should be archived
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case associated with the form should still exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # There should be a stub for the unfinished archive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The case and stub should both be deleted now
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnfinishedUnarchiveStub(self):
        # Test running the celery task reprocess_archive_stubs on an existing unarchive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the unarchive function throwing an error
        with mock.patch('couchforms.signals.xform_unarchived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.unarchive(user_id='librarian')
            except Exception:
                pass

        # Make sure the history only has an archive and an unarchive
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(2, len(xform.history))
        self.assertFalse(xform.is_archived)
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('librarian', xform.history[0].user)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('librarian', xform.history[1].user)

        # The case should not exist because the unarchived form was not rebuilt
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # There should be a stub for the unfinished unarchive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The case should be back, and the stub should be deleted now
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnarchivingWithArchiveStub(self):
        # Test a user-initiated unarchive with an existing archive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))
        # Mock the archive function throwing an error
        with mock.patch('couchforms.signals.xform_archived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.archive(user_id='librarian')
            except Exception:
                pass

        # There should be a stub for the unfinished archive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Call an unarchive
        xform.unarchive(user_id='librarian')

        # The unfinished archive stub should be deleted
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 0)

        # The case should exist because the case close was unarchived
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the case still exists (to double check that the archive stub was deleted)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

    def testArchivingWithUnarchiveStub(self):
        # Test a user-initiated archive with an existing unarchive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the unarchive function throwing an error
        with mock.patch('couchforms.signals.xform_unarchived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.unarchive(user_id='librarian')
            except Exception:
                pass

        # There should be a stub for the unfinished unarchive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Call an archive
        xform.archive(user_id='librarian')

        # The unfinished archive stub should be deleted
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 0)

        # The case should not exist because the case close was archived
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The history should not have been added to, make sure that it still only has one entry

        # Make sure the case still does not exist (to double check that the unarchive stub was deleted)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

    def testUnfinishedArchiveStubErrorAddingHistory(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive
        # initially failed on updating the history
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Mock the couch and sql archive function throwing an error (so that this test works for both)
        with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.'
                        'archive_form') \
                as mock_operation_sql:
            with mock.patch('couchforms.models.XFormOperation') as mock_operation_couch:
                try:
                    mock_operation_sql.side_effect = Exception
                    mock_operation_couch.side_effect = Exception
                    xform.archive(user_id='librarian')
                except Exception:
                    pass

        # Get the form with the updated history, make sure it has not been archived yet
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(0, len(xform.history))
        self.assertFalse(xform.is_archived)

        # The case associated with the form should still exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # There should be a stub for the unfinished archive, and the history should not be updated yet
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, False)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the history shows an archive now
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case and stub should both be deleted now
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnfinishedUnarchiveStubErrorAddingHistory(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive
        # initially failed on updating the history
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the couch and sql archive function throwing an error (so that this test works for both)
        with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.'
                        'unarchive_form') \
                as mock_operation_sql:
            with mock.patch('couchforms.models.XFormOperation') as mock_operation_couch:
                try:
                    mock_operation_sql.side_effect = Exception
                    mock_operation_couch.side_effect = Exception
                    xform.unarchive(user_id='librarian')
                except Exception:
                    pass

        # Get the form with the updated history, make sure it only has one entry (the archive)
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case associated with the form should not exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # There should be a stub for the unfinished archive, and the history should not be updated yet
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, False)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the history shows an archive and an unarchive now
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(2, len(xform.history))
        self.assertFalse(xform.is_archived)
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('librarian', xform.history[0].user)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('librarian', xform.history[1].user)

        # The case should be back, and the stub should be deleted now
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testSignal(self):
        global archive_counter, restore_counter
        archive_counter = 0
        restore_counter = 0

        def count_archive(**kwargs):
            global archive_counter
            archive_counter += 1

        def count_unarchive(**kwargs):
            global restore_counter
            restore_counter += 1

        xform_archived.connect(count_archive)
        xform_unarchived.connect(count_unarchive)

        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertEqual(0, archive_counter)
        self.assertEqual(0, restore_counter)

        result.xform.archive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(0, restore_counter)

        xform = self.formdb.get_form(result.xform.form_id)
        xform.unarchive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(1, restore_counter)