How to use and reference forms and cases programatically

With the introduction of the new architecture for form and case data it is now necessary to use generic functions and accessors to access and operate on the models.

This document provides a basic guide for how to do that.

Models

In the codebase there are now two models for form and case data.

Couch

SQL

CommCareCase

CommCareCaseSQL

CommCareCaseAction

CaseTransaction

CommCareCaseAttachment

CaseAttachmentSQL

CommCareCaseIndex

CommCareCaseIndexSQL

XFormInstance

XFormInstanceSQL

XFormOperation

XFormOperationSQL

StockReport

StockTransaction

LedgerTransaction

StockState

LedgerValue

Some of these models define a common interface that allows you to perform the same operations irrespective of the type. Some examples are shown below:

Form Instance

Property / method

Description

form.form_id

The instance ID of the form

form.is_normal

form.is_deleted

form.is_archived

form.is_error

form.is_deprecated

form.is_duplicate

form.is_submission_error_log

Replacement for checking the doc_type of a form

form.attachments

The form attachment objects

form.get_attachment

Get an attachment by name

form.archive

Archive a form

form.unarchive

Unarchive a form

form.to_json

Get the JSON representation of a form

form.form_data

Get the XML form data

Case

Property / method

Description

case.case_id

ID of the case

case.is_deleted

Replacement for doc_type check

case.case_name

Name of the case

case.get_attachment

Get attachment by name

case.dynamic_case_properties

Dictionary of dynamic case properties

case.get_subcases

Get subcase objects

case.get_index_map

Get dictionary of case indices

Model acessors

To access models from the database there are classes that abstract the actual DB operations. These classes are generally names <type>Accessors and must be instantiated with a domain name in order to know which DB needs to be queried.

Forms

  • FormAccessors(domain).get_form(form_id)

  • FormAccessors(domain).get_forms(form_ids)

  • FormAccessors(domain).iter_forms(form_ids)

  • FormAccessors(domain).save_new_form(form)

    • only for new forms

  • FormAccessors(domain).get_with_attachments(form)

    • Preload attachments to avoid having to the the DB again

Cases

  • CaseAccessors(domain).get_case(case_id)

  • CaseAccessors(domain).get_cases(case_ids)

  • CaseAccessors(domain).iter_cases(case_ids)

  • CaseAccessors(domain).get_case_ids_in_domain(type=’dog’)

Ledgers

  • LedgerAccessors(domain).get_ledger_values_for_case(case_id)

For more details see:

  • corehq.form_processor.interfaces.dbaccessors.FormAccessors

  • corehq.form_processor.interfaces.dbaccessors.CaseAccessors

  • corehq.form_processor.interfaces.dbaccessors.LedgerAccessors

Branching

In special cases code may need to be branched into SQL and Couch versions.

This can be accomplished using the should_use_sql_backend(domain) function.:

if should_use_sql_backend(domain_name):
    # do SQL specifc stuff here
else:
    # do couch stuff here

Unit Tests

In most cases tests that use form / cases/ ledgers should be run on both backends as follows:

@run_with_all_backends
def test_my_function(self):
    ...

If you really need to run a test on only one of the backends you can do the following:

@override_settings(TESTS_SHOULD_USE_SQL_BACKEND=True)
def test_my_test(self):
    ...

To create a form in unit tests use the following pattern:

from corehq.form_processor.tests.utils import run_with_all_backends
from corehq.form_processor.utils import get_simple_wrapped_form, TestFormMetadata

@run_with_all_backends
def test_my_form_function(self):
    # This TestFormMetadata specifies properties about the form to be created
    metadata = TestFormMetadata(
        domain=self.user.domain,
        user_id=self.user._id,
    )
    form = get_simple_wrapped_form(
        form_id,
        metadata=metadata
    )

Creating cases can be done with the CaseFactory:

from corehq.form_processor.tests.utils import run_with_all_backends
from casexml.apps.case.mock import CaseFactory

@run_with_all_backends
def test_my_case_function(self):
    factory = CaseFactory(domain='foo')
    factory.create_case(
        case_type='my_case_type',
        owner_id='owner1',
        case_name='bar',
        update={'prop1': 'abc'}
    )

Cleaning up

Cleaning up in tests can be done using the FormProcessorTestUtils1 class:

from corehq.form_processor.tests.utils import FormProcessorTestUtils

def tearDown(self):
    FormProcessorTestUtils.delete_all_cases()
    # OR
    FormProcessorTestUtils.delete_all_cases(
        domain=domain
    )

    FormProcessorTestUtils.delete_all_xforms()
    # OR
    FormProcessorTestUtils.delete_all_xforms(
        domain=domain
    )