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