示例#1
0
def _migrate_case_actions(couch_case, sql_case):
    from casexml.apps.case import const
    transactions = {}
    for action in couch_case.actions:
        if action.xform_id and action.xform_id in transactions:
            transaction = transactions[action.xform_id]
        else:
            transaction = CaseTransaction(
                case=sql_case,
                form_id=action.xform_id,
                sync_log_id=action.sync_log_id,
                type=CaseTransaction.TYPE_FORM if action.xform_id else 0,
                server_date=action.server_date,
            )
            if action.xform_id:
                transactions[action.xform_id] = transaction
            else:
                sql_case.track_create(transaction)
        if action.action_type == const.CASE_ACTION_CREATE:
            transaction.type |= CaseTransaction.TYPE_CASE_CREATE
        if action.action_type == const.CASE_ACTION_CLOSE:
            transaction.type |= CaseTransaction.TYPE_CASE_CLOSE
        if action.action_type == const.CASE_ACTION_INDEX:
            transaction.type |= CaseTransaction.TYPE_CASE_INDEX
        if action.action_type == const.CASE_ACTION_ATTACHMENT:
            transaction.type |= CaseTransaction.TYPE_CASE_ATTACHMENT
        if action.action_type == const.CASE_ACTION_REBUILD:
            transaction.type = CaseTransaction.TYPE_REBUILD_WITH_REASON
            transaction.details = RebuildWithReason(reason="Unknown")

    for transaction in transactions.values():
        sql_case.track_create(transaction)
示例#2
0
 def add_transaction_for_form(case, case_update, form):
     types = [CaseTransaction.type_from_action_type_slug(a.action_type_slug) for a in case_update.actions]
     transaction = CaseTransaction.form_transaction(case, form, case_update.guess_modified_on(), types)
     for trans in case.get_tracked_models_to_create(CaseTransaction):
         if transaction == trans:
             trans.type |= transaction.type
             break
     else:
         case.track_create(transaction)
class TestCaseESAccessorsSQL(TestCaseESAccessors):
    def _send_case_to_es(self,
                         domain=None,
                         owner_id=None,
                         user_id=None,
                         case_type=None,
                         opened_on=None,
                         closed_on=None):

        case = CommCareCaseSQL(case_id=uuid.uuid4().hex,
                               domain=domain or self.domain,
                               owner_id=owner_id or self.owner_id,
                               modified_by=user_id or self.user_id,
                               type=case_type or self.case_type,
                               opened_on=opened_on or datetime.now(),
                               opened_by=user_id or self.user_id,
                               closed_on=closed_on,
                               modified_on=datetime.now(),
                               closed_by=user_id or self.user_id,
                               server_modified_on=datetime.utcnow(),
                               closed=bool(closed_on))

        case.track_create(
            CaseTransaction(
                type=CaseTransaction.TYPE_FORM,
                form_id=uuid.uuid4().hex,
                case=case,
                server_date=opened_on,
            ))
示例#4
0
 def apply_action_intent(self, case_action_intent):
     if not case_action_intent.is_deprecation:
         # for now we only allow commtrack actions to be processed this way so just assert that's the case
         assert case_action_intent.action_type == CASE_ACTION_COMMTRACK
         transaction = CaseTransaction.ledger_transaction(self.case, case_action_intent.form)
         if transaction not in self.case.get_tracked_models_to_create(CaseTransaction):
             self.case.track_create(transaction)
示例#5
0
class SqlCaseUpdateStrategy(UpdateStrategy):
    case_implementation_class = CommCareCaseSQL

    def apply_action_intents(self, primary_intent, deprecation_intent=None):
        # for now we only allow commtrack actions to be processed this way so just assert that's the case
        if primary_intent:
            assert primary_intent.action_type == CASE_ACTION_COMMTRACK
            transaction = CaseTransaction.ledger_transaction(self.case, primary_intent.form)
            if deprecation_intent:
                assert transaction.is_saved()
            elif transaction not in self.case.get_tracked_models_to_create(CaseTransaction):
                # hack: clear the sync log id so this modification always counts
                # since consumption data could change server-side
                transaction.sync_log_id = None
                self.case.track_create(transaction)
        elif deprecation_intent:
            transaction = self.case.get_transaction_by_form_id(deprecation_intent.form.orig_id)
            transaction.sync_log_id = None
            transaction.type -= CaseTransaction.TYPE_LEDGER
            self.case.track_update(transaction)

    def update_from_case_update(self, case_update, xformdoc, other_forms=None):
        self._apply_case_update(case_update, xformdoc)
        self.add_transaction_for_form(self.case, case_update, xformdoc)

    @staticmethod
    def add_transaction_for_form(case, case_update, form):
        types = [CaseTransaction.type_from_action_type_slug(a.action_type_slug) for a in case_update.actions]
        transaction = CaseTransaction.form_transaction(case, form, case_update.guess_modified_on(), types)
        for trans in case.get_tracked_models_to_create(CaseTransaction):
            if transaction == trans:
                trans.type |= transaction.type
                break
示例#6
0
    def update_from_case_update(self, case_update, xformdoc, other_forms=None):
        self._apply_case_update(case_update, xformdoc)

        transaction = CaseTransaction.form_transaction(self.case, xformdoc)
        if transaction not in self.case.get_tracked_models_to_create(CaseTransaction):
            # don't add multiple transactions for the same form
            self.case.track_create(transaction)
示例#7
0
    def _rebuild_case_from_transactions(case, detail, updated_xforms=None):
        transactions = get_case_transactions(case.case_id, updated_xforms=updated_xforms)
        strategy = SqlCaseUpdateStrategy(case)

        rebuild_transaction = CaseTransaction.rebuild_transaction(case, detail)
        strategy.rebuild_from_transactions(transactions, rebuild_transaction)
        return case
示例#8
0
    def update_from_case_update(self, case_update, xformdoc, other_forms=None):
        self._apply_case_update(case_update, xformdoc)

        types = [CaseTransaction.type_from_action_type_slug(a.action_type_slug) for a in case_update.actions]
        transaction = CaseTransaction.form_transaction(self.case, xformdoc, types)
        if transaction not in self.case.get_tracked_models_to_create(CaseTransaction):
            # don't add multiple transactions for the same form
            self.case.track_create(transaction)
示例#9
0
    def _fetch_case_transaction_forms(self, transactions, updated_xforms=None):
        """
        Fetch the forms for a list of transactions, caching them on each transaction

        :param transactions: list of ``CaseTransaction`` objects:
        :param updated_xforms: optional list of forms that have been changed.
        """
        form_ids = {tx.form_id for tx in transactions if tx.form_id}
        updated_xforms_map = {
            xform.form_id: xform
            for xform in updated_xforms if not xform.is_deprecated
        } if updated_xforms else {}

        updated_xform_ids = set(updated_xforms_map)
        form_ids_to_fetch = list(form_ids - updated_xform_ids)
        form_load_counter("rebuild_case",
                          self.case.domain)(len(form_ids_to_fetch))
        xform_map = {
            form.form_id: form
            for form in XFormInstance.objects.get_forms_with_attachments_meta(
                form_ids_to_fetch)
        }

        forms_missing_transactions = list(updated_xform_ids - form_ids)
        for form_id in forms_missing_transactions:
            # Add in any transactions that aren't already present
            form = updated_xforms_map[form_id]
            case_updates = [
                update for update in get_case_updates(form)
                if update.id == self.case.case_id
            ]
            types = [
                CaseTransaction.type_from_action_type_slug(a.action_type_slug)
                for case_update in case_updates for a in case_update.actions
            ]
            modified_on = case_updates[0].guess_modified_on()
            new_transaction = CaseTransaction.form_transaction(
                self.case, form, modified_on, types)
            transactions.append(new_transaction)

        def get_form(form_id):
            if form_id in updated_xforms_map:
                return updated_xforms_map[form_id]

            try:
                return xform_map[form_id]
            except KeyError:
                raise XFormNotFound(form_id)

        for case_transaction in transactions:
            if case_transaction.form_id:
                try:
                    case_transaction.cached_form = get_form(
                        case_transaction.form_id)
                except XFormNotFound:
                    logging.error('Form not found during rebuild: %s',
                                  case_transaction.form_id)
示例#10
0
 def apply_action_intents(self, primary_intent, deprecation_intent=None):
     # for now we only allow commtrack actions to be processed this way so just assert that's the case
     assert primary_intent.action_type == CASE_ACTION_COMMTRACK
     transaction = CaseTransaction.ledger_transaction(self.case, primary_intent.form)
     if deprecation_intent:
         assert transaction.is_saved()
     elif transaction not in self.case.get_tracked_models_to_create(CaseTransaction):
         # hack: clear the sync log id so this modification always counts
         # since consumption data could change server-side
         transaction.sync_log_id = None
         self.case.track_create(transaction)
示例#11
0
def create_form_for_test(domain, case_id=None, attachments=None, save=True, state=XFormInstanceSQL.NORMAL):
    """
    Create the models directly so that these tests aren't dependent on any
    other apps. Not testing form processing here anyway.
    :param case_id: create case with ID if supplied
    :param attachments: additional attachments dict
    :param save: if False return the unsaved form
    :return: form object
    """
    from corehq.form_processor.utils import get_simple_form_xml

    form_id = uuid4().hex
    user_id = 'user1'
    utcnow = datetime.utcnow()

    form_xml = get_simple_form_xml(form_id, case_id)

    form = XFormInstanceSQL(
        form_id=form_id,
        xmlns='http://openrosa.org/formdesigner/form-processor',
        received_on=utcnow,
        user_id=user_id,
        domain=domain,
        state=state
    )

    attachments = attachments or {}
    attachment_tuples = map(
        lambda a: Attachment(name=a[0], raw_content=a[1], content_type=a[1].content_type),
        attachments.items()
    )
    attachment_tuples.append(Attachment('form.xml', form_xml, 'text/xml'))

    FormProcessorSQL.store_attachments(form, attachment_tuples)

    cases = []
    if case_id:
        case = CommCareCaseSQL(
            case_id=case_id,
            domain=domain,
            type='',
            owner_id=user_id,
            opened_on=utcnow,
            modified_on=utcnow,
            modified_by=user_id,
            server_modified_on=utcnow,
        )
        case.track_create(CaseTransaction.form_transaction(case, form))
        cases = [case]

    if save:
        FormProcessorSQL.save_processed_models(ProcessedForms(form, None), cases)
    return form
示例#12
0
    def _rebuild_case_from_transactions(case, detail, updated_xforms=None):
        transactions = get_case_transactions(case.case_id, updated_xforms=updated_xforms)
        strategy = SqlCaseUpdateStrategy(case)

        rebuild_transaction = CaseTransaction.rebuild_transaction(case, detail)
        unarchived_form_id = None
        if detail.type == CaseTransaction.TYPE_REBUILD_FORM_ARCHIVED and not detail.archived:
            # we're rebuilding because a form was un-archived
            unarchived_form_id = detail.form_id
        strategy.rebuild_from_transactions(
            transactions, rebuild_transaction, unarchived_form_id=unarchived_form_id
        )
        return case
示例#13
0
class SqlCaseUpdateStrategy(UpdateStrategy):
    case_implementation_class = CommCareCase

    def apply_action_intents(self, primary_intent, deprecation_intent=None):
        # for now we only allow commtrack actions to be processed this way so just assert that's the case

        if primary_intent:
            if primary_intent.action_type != CASE_ACTION_COMMTRACK:
                raise StockProcessingError(
                    'intent not of expected type: {}'.format(
                        primary_intent.action_type))
            if not deprecation_intent:
                # don't create / update transaction if we're processing an edited form
                transaction = CaseTransaction.ledger_transaction(
                    self.case, primary_intent.form)
                if transaction not in self.case.get_tracked_models_to_create(
                        CaseTransaction):
                    # hack: clear the sync log id so this modification always counts
                    # since consumption data could change server-side
                    transaction.sync_log_id = None
                    self.case.track_create(transaction)
        elif deprecation_intent:
            # edited form no longer has a ledger block to update existing transaction
            # to remove leger type
            transaction = self.case.get_transaction_by_form_id(
                deprecation_intent.form.orig_id)
            if not transaction.is_saved():
                raise StockProcessingError('Deprecated transaction not saved')
            transaction.sync_log_id = None
            transaction.type -= CaseTransaction.TYPE_LEDGER
            self.case.track_update(transaction)

    def update_from_case_update(self, case_update, xformdoc, other_forms=None):
        self._apply_case_update(case_update, xformdoc)
        self.add_transaction_for_form(self.case, case_update, xformdoc)

    @staticmethod
    def add_transaction_for_form(case, case_update, form):
        types = [
            CaseTransaction.type_from_action_type_slug(a.action_type_slug)
            for a in case_update.actions
        ]
        transaction = CaseTransaction.form_transaction(
            case, form, case_update.guess_modified_on(), types)
        if transaction not in case.get_tracked_models_to_create(
                CaseTransaction):
            case.track_create(transaction)
示例#14
0
    def _rebuild_case_from_transactions(case, detail, updated_xforms=None):
        transactions = CaseAccessorSQL.get_case_transactions_by_case_id(
            case,
            updated_xforms=updated_xforms)
        strategy = SqlCaseUpdateStrategy(case)

        rebuild_transaction = CaseTransaction.rebuild_transaction(case, detail)
        if updated_xforms:
            rebuild_transaction.server_date = updated_xforms[0].edited_on
        unarchived_form_id = None
        if detail.type == CaseTransaction.TYPE_REBUILD_FORM_ARCHIVED and not detail.archived:
            # we're rebuilding because a form was un-archived
            unarchived_form_id = detail.form_id
        strategy.rebuild_from_transactions(
            transactions, rebuild_transaction, unarchived_form_id=unarchived_form_id
        )
        return case, rebuild_transaction
示例#15
0
    def reconcile_transactions(self):
        transactions = self.case.transactions
        sorted_transactions = sorted(
            transactions,
            key=_transaction_sort_key_function(self.case)
        )
        if sorted_transactions:
            if not sorted_transactions[0].is_case_create:
                error = "Case {0} first transaction not create transaction: {1}"
                raise ReconciliationError(
                    error.format(self.case.case_id, sorted_transactions[0])
                )

        CaseAccessorSQL.fetch_case_transaction_forms(self.case, sorted_transactions)
        rebuild_detail = RebuildWithReason(reason="client_date_reconciliation")
        rebuild_transaction = CaseTransaction.rebuild_transaction(self.case, rebuild_detail)
        self.rebuild_from_transactions(sorted_transactions, rebuild_transaction)
示例#16
0
def create_case(case) -> CommCareCaseSQL:
    form = XFormInstanceSQL(
        form_id=uuid4().hex,
        xmlns='http://commcarehq.org/formdesigner/form-processor',
        received_on=case.server_modified_on,
        user_id=case.owner_id,
        domain=case.domain,
    )
    transaction = CaseTransaction(
        type=CaseTransaction.TYPE_FORM,
        form_id=form.form_id,
        case=case,
        server_date=case.server_modified_on,
    )
    with patch.object(FormProcessorSQL, "publish_changes_to_kafka"):
        case.track_create(transaction)
        processed_forms = ProcessedForms(form, [])
        FormProcessorSQL.save_processed_models(processed_forms, [case])
示例#17
0
    def get_case(self, case_json=None):
        utcnow = datetime.utcnow()
        case_json = self.props if case_json is None else case_json
        intent_case = CommCareCase(
            domain=SOURCE_DOMAIN,
            type=self.CASE_TYPE,
            case_json=case_json,
            case_id=uuid.uuid4().hex,
            user_id="local_user1",
            opened_on=utcnow,
            modified_on=utcnow,
        )
        intent_case.track_create(CaseTransaction(form_id="form123", type=CaseTransaction.TYPE_FORM))

        def _mock_subcases(*args, **kwargs):
            return self.subcases

        intent_case.get_subcases = _mock_subcases
        return intent_case
示例#18
0
class TestCaseESAccessors(BaseESAccessorsTest):

    es_index_infos = [CASE_INDEX_INFO, USER_INDEX_INFO]

    def setUp(self):
        super(TestCaseESAccessors, self).setUp()
        self.owner_id = 'batman'
        self.user_id = 'robin'
        self.case_type = 'heroes'

    def _send_case_to_es(self,
                         domain=None,
                         owner_id=None,
                         user_id=None,
                         case_type=None,
                         opened_on=None,
                         closed_on=None,
                         modified_on=None):

        case = CommCareCase(case_id=uuid.uuid4().hex,
                            domain=domain or self.domain,
                            owner_id=owner_id or self.owner_id,
                            modified_by=user_id or self.user_id,
                            type=case_type or self.case_type,
                            opened_on=opened_on or datetime.now(),
                            opened_by=user_id or self.user_id,
                            closed_on=closed_on,
                            modified_on=modified_on or datetime.now(),
                            closed_by=user_id or self.user_id,
                            server_modified_on=datetime.utcnow(),
                            closed=bool(closed_on))

        case.track_create(
            CaseTransaction(
                type=CaseTransaction.TYPE_FORM,
                form_id=uuid.uuid4().hex,
                case=case,
                server_date=opened_on,
            ))
def _create_case(domain=None, form_id=None, case_type=None, user_id=None, closed=False):
    """
    Create the models directly so that these tests aren't dependent on any
    other apps. Not testing form processing here anyway.
    :return: case_id
    """
    domain = domain or DOMAIN
    form_id = form_id or uuid.uuid4().hex
    case_id = uuid.uuid4().hex
    user_id = user_id or 'user1'
    utcnow = datetime.utcnow()

    form = XFormInstanceSQL(
        form_id=form_id,
        xmlns='http://openrosa.org/formdesigner/form-processor',
        received_on=utcnow,
        user_id=user_id,
        domain=domain
    )

    cases = []
    if case_id:
        case = CommCareCaseSQL(
            case_id=case_id,
            domain=domain,
            type=case_type or '',
            owner_id=user_id,
            opened_on=utcnow,
            modified_on=utcnow,
            modified_by=user_id,
            server_modified_on=utcnow,
            closed=closed or False
        )
        case.track_create(CaseTransaction.form_transaction(case, form))
        cases = [case]

    FormProcessorSQL.save_processed_models(ProcessedForms(form, None), cases)
    return CaseAccessorSQL.get_case(case_id)
    def test_ignores_before_rebuild_transaction(self):
        with freeze_time("2018-10-10"):
            case = self._create_case()

        with freeze_time("2018-10-11"):
            new_old_xform = self._create_form()
        with freeze_time("2018-10-08"):
            new_old_trans = self._create_case_transaction(case, new_old_xform)
        with freeze_time("2018-10-11"):
            case.track_create(new_old_trans)
            FormProcessorSQL.save_processed_models(ProcessedForms(new_old_xform, []), [case])

        self.assertFalse(case.check_transaction_order())

        with freeze_time("2018-10-13"):
            new_rebuild_xform = self._create_form()
            rebuild_detail = RebuildWithReason(reason="shadow's golden coin")
            rebuild_transaction = CaseTransaction.rebuild_transaction(case, rebuild_detail)
            case.track_create(rebuild_transaction)
            FormProcessorSQL.save_processed_models(ProcessedForms(new_rebuild_xform, []), [case])

        case = CaseAccessorSQL.get_case(case.case_id)
        update_strategy = SqlCaseUpdateStrategy(case)
        self.assertFalse(update_strategy.reconcile_transactions_if_necessary())
    def _create_case_transaction(self, case, form=None, submitted_on=None, action_types=None):
        form = form or self._create_form()
        submitted_on = submitted_on or datetime.utcnow()

        return CaseTransaction.form_transaction(case, form, submitted_on, action_types)