def get_simple_wrapped_form(form_id, case_id=None, metadata=None, save=True): from corehq.form_processor.interfaces.processor import FormProcessorInterface metadata = metadata or TestFormMetadata() xml = get_simple_form_xml(form_id=form_id, metadata=metadata) form_json = convert_xform_to_json(xml) interface = FormProcessorInterface(domain=metadata.domain) wrapped_form = interface.new_xform(form_json) wrapped_form.domain = metadata.domain wrapped_form.received_on = metadata.received_on interface.store_attachments(wrapped_form, [Attachment('form.xml', xml, 'text/xml')]) if save: interface.save_processed_models([wrapped_form]) return wrapped_form
def __init__(self, instance=None, attachments=None, auth_context=None, domain=None, app_id=None, build_id=None, path=None, location=None, submit_ip=None, openrosa_headers=None, last_sync_token=None, received_on=None, date_header=None, partial_submission=False, case_db=None, force_logs=False): assert domain, "'domain' is required" assert instance, instance assert not isinstance(instance, HttpRequest), instance self.domain = domain self.app_id = app_id self.build_id = build_id # get_location has good default self.location = location or couchforms.get_location() self.received_on = received_on self.date_header = date_header self.submit_ip = submit_ip self.last_sync_token = last_sync_token self.openrosa_headers = openrosa_headers or {} self.instance = instance self.attachments = attachments or {} self.auth_context = auth_context or DefaultAuthContext() self.path = path self.interface = FormProcessorInterface(domain) self.formdb = FormAccessors(domain) self.partial_submission = partial_submission # always None except in the case where a system form is being processed as part of another submission # e.g. for closing extension cases self.case_db = case_db if case_db: assert case_db.domain == domain self.force_logs = force_logs self.is_openrosa_version3 = self.openrosa_headers.get( OPENROSA_VERSION_HEADER, '') == OPENROSA_VERSION_3 self.track_load = form_load_counter("form_submission", domain)
def _get_case_and_ledger_updates(domain, sql_form): """ Get a CaseStockProcessingResult with the appropriate cases and ledgers to be saved. See SubmissionPost.process_xforms_for_cases and methods it calls for the equivalent section of the form-processing code. """ from corehq.apps.commtrack.processing import process_stock interface = FormProcessorInterface(domain) assert sql_form.domain xforms = [sql_form] with interface.casedb_cache( domain=domain, lock=False, deleted_ok=True, xforms=xforms, load_src="couchsqlmigration", ) as case_db: touched_cases = interface.get_cases_from_forms(case_db, xforms) extensions_to_close = get_all_extensions_to_close( domain, list(touched_cases.values())) case_result = CaseProcessingResult( domain, [update.case for update in touched_cases.values()], [], # ignore dirtiness_flags, extensions_to_close) for case in case_result.cases: case_db.post_process_case(case, sql_form) case_db.mark_changed(case) cases = case_result.cases try: stock_result = process_stock(xforms, case_db) cases = case_db.get_cases_for_saving(sql_form.received_on) stock_result.populate_models() except MissingFormXml: stock_result = None return CaseStockProcessingResult( case_result=case_result, case_models=cases, stock_result=stock_result, )
def _handle_duplicate(new_doc): """ Handle duplicate xforms and xform editing ('deprecation') existing doc *must* be validated as an XFormInstance in the right domain and *must* include inline attachments """ interface = FormProcessorInterface(new_doc.domain) conflict_id = new_doc.form_id existing_doc = FormAccessors( new_doc.domain).get_with_attachments(conflict_id) existing_md5 = existing_doc.xml_md5() new_md5 = new_doc.xml_md5() if existing_md5 != new_md5: if new_doc.xmlns != existing_doc.xmlns: # if the XMLNS has changed this probably isn't a form edit # it could be a UUID clash (yes we've had that before) # Assign a new ID to the form and process as normal + notify_admins xform = interface.assign_new_id(new_doc) soft_assert(to='{}@{}.com'.format('skelly', 'dimagi'), exponential_backoff=False)( False, "Potential UUID clash", { 'incoming_form_id': conflict_id, 'existing_form_id': existing_doc.form_id, 'new_form_id': xform.form_id, 'incoming_xmlns': new_doc.xmlns, 'existing_xmlns': existing_doc.xmlns, 'domain': new_doc.domain, }) return FormProcessingResult(xform) else: # if the form contents are not the same: # - "Deprecate" the old form by making a new document with the same contents # but a different ID and a doc_type of XFormDeprecated # - Save the new instance to the previous document to preserve the ID existing_doc, new_doc = apply_deprecation(existing_doc, new_doc, interface) return FormProcessingResult(new_doc, existing_doc) else: # follow standard dupe handling, which simply saves a copy of the form # but a new doc_id, and a doc_type of XFormDuplicate duplicate = interface.deduplicate_xform(new_doc) return FormProcessingResult(duplicate)
def _perfom_post_save_actions(form, save=True): interface = FormProcessorInterface(form.domain) cache = interface.casedb_cache(domain=form.domain, lock=False, deleted_ok=True, xforms=[form]) with cache as casedb: case_stock_result = SubmissionPost.process_xforms_for_cases([form], casedb) try: save and SubmissionPost.do_post_save_actions( casedb, [form], case_stock_result) except PostSaveError: error_message = "Error performing post save operations" return ReprocessingResult(form, None, None, error_message) return ReprocessingResult(form, case_stock_result.case_models, None, None)
def _get_or_update_cases(xforms, case_db): """ Given an xform document, update any case blocks found within it, returning a dictionary mapping the case ids affected to the couch case document objects """ domain = getattr(case_db, 'domain', None) touched_cases = FormProcessorInterface(domain).get_cases_from_forms(case_db, xforms) _validate_indices(case_db, [case_update_meta.case for case_update_meta in touched_cases.values()]) dirtiness_flags = _get_all_dirtiness_flags_from_cases(case_db, touched_cases) extensions_to_close = get_all_extensions_to_close(domain, touched_cases.values()) return CaseProcessingResult( domain, [update.case for update in touched_cases.values()], dirtiness_flags, extensions_to_close )
def _migrate_form(domain, couch_form): """ This copies the couch form into a new sql form but does not save it. See form_processor.parsers.form._create_new_xform and SubmissionPost._set_submission_properties for what this should do. """ interface = FormProcessorInterface(domain) form_data = couch_form.form with force_phone_timezones_should_be_processed(): adjust_datetimes(form_data) sql_form = interface.new_xform(form_data) sql_form.form_id = couch_form.form_id # some legacy forms don't have ID's so are assigned random ones if sql_form.xmlns is None: sql_form.xmlns = '' return _copy_form_properties(domain, sql_form, couch_form)
def populate_models(self): self.populated = True interface = FormProcessorInterface(domain=self.domain) processor = interface.ledger_processor ledger_db = interface.ledger_db for helper in self.stock_report_helpers: assert helper.domain == self.domain normal_helpers = [srh for srh in self.stock_report_helpers if not srh.deprecated] deprecated_helpers = [srh for srh in self.stock_report_helpers if srh.deprecated] models_result = processor.get_models_to_update( self.xform.form_id, normal_helpers, deprecated_helpers, ledger_db ) self.models_to_save, self.models_to_delete = models_result
def _create_new_xform(domain, instance_xml, attachments=None, auth_context=None): """ create but do not save an XFormInstance from an xform payload (xml_string) optionally set the doc _id to a predefined value (_id) return doc _id of the created doc `process` is transformation to apply to the form right before saving This is to avoid having to save multiple times If xml_string is bad xml - raise couchforms.XMLSyntaxError :param domain: :returns: FormProcessingResult or raises an error """ from corehq.form_processor.interfaces.processor import FormProcessorInterface interface = FormProcessorInterface(domain) assert attachments is not None form_data = convert_xform_to_json(instance_xml) if not form_data.get('@xmlns'): raise MissingXMLNSError("Form is missing a required field: XMLNS") adjust_datetimes(form_data) xform = interface.new_xform(form_data) xform.domain = domain xform.auth_context = auth_context # Maps all attachments to uniform format and adds form.xml to list before storing attachments = [ Attachment(name=a[0], raw_content=a[1], content_type=a[1].content_type) for a in attachments.items() ] attachments.append( Attachment(name='form.xml', raw_content=instance_xml, content_type='text/xml')) interface.store_attachments(xform, attachments) assert xform.is_normal return FormProcessingResult(xform)
def test_casedb_already_has_cases(self): casedb_cache = FormProcessorInterface().casedb_cache case = CaseFactory().create_case() case_db = casedb_cache( initial=[CommCareCase(_id='fake1'), CommCareCase(_id='fake2')]) form = XFormInstance.get(case.xform_ids[0]) def assert_exactly_one_case(sender, xform, cases, **kwargs): global case_count case_count = len(cases) cases_received.connect(assert_exactly_one_case) try: process_cases_with_casedb([form], case_db) self.assertEqual(1, case_count) finally: cases_received.disconnect(assert_exactly_one_case)
def __init__(self, domain=None, strip_history=False, deleted_ok=False, lock=False, wrap=True, initial=None, xforms=None): self._populate_from_initial(initial) self.domain = domain self.cached_xforms = xforms if xforms is not None else [] self.strip_history = strip_history self.deleted_ok = deleted_ok self.lock = lock self.wrap = wrap if self.lock and not self.wrap: raise ValueError('Currently locking only supports explicitly wrapping cases!') self.locks = [] self._changed = set() # this is used to allow casedb to be re-entrant. Each new context pushes the parent context locks # onto this stack and restores them when the context exits self.lock_stack = [] self.processor_interface = FormProcessorInterface(self.domain)
def _count_forms(self, case_id, context): domain = context.root_doc['domain'] xmlns = [self.xmlns] if isinstance(self.xmlns, six.string_types) else self.xmlns cache_key = (self.__class__.__name__, case_id, hashlib.md5(','.join(xmlns)).hexdigest()) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) xforms = FormProcessorInterface(domain).get_case_forms(case_id) count = len([ form for form in xforms if form.xmlns in xmlns and form.domain == domain ]) context.set_cache_value(cache_key, count) return count
def test_casedb_already_has_cases(self): casedb_cache = FormProcessorInterface().casedb_cache case = CaseFactory().create_case() case_db = casedb_cache(initial=[ CommCareCase(case_id='fake1'), CommCareCase(case_id='fake2'), ]) form = XFormInstance.objects.get_form(case.xform_ids[0]) received = [] def receive_cases(sender, xform, cases, **kwargs): received.extend(cases) cases_received.connect(receive_cases) try: process_cases_with_casedb([form], case_db) self.assertEqual(len(received), 1) finally: cases_received.disconnect(receive_cases)
def apply_deprecation(existing_xform, new_xform, interface=None): # if the form contents are not the same: # - "Deprecate" the old form by making a new document with the same contents # but a different ID and a doc_type of XFormDeprecated # - Save the new instance to the previous document to preserve the ID interface = interface or FormProcessorInterface(existing_xform.domain) interface.copy_attachments(existing_xform, new_xform) new_xform.form_id = existing_xform.form_id existing_xform = interface.assign_new_id(existing_xform) existing_xform.orig_id = new_xform.form_id # and give the new doc server data of the old one and some metadata new_xform.received_on = existing_xform.received_on new_xform.deprecated_form_id = existing_xform.form_id new_xform.edited_on = datetime.datetime.utcnow() existing_xform.edited_on = new_xform.edited_on return interface.apply_deprecation(existing_xform, new_xform)
def _get_forms(self, case_id, context): domain = context.root_doc['domain'] cache_key = (self.__class__.__name__, case_id, self.xmlns, self.reverse) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) xforms = FormProcessorInterface(domain).get_case_forms(case_id) xforms = sorted( [form for form in xforms if form.xmlns == self.xmlns and form.domain == domain], key=lambda x: x.received_on ) if not xforms: form = None else: index = -1 if self.reverse else 0 form = xforms[index].to_json() context.set_cache_value(cache_key, form) return form
def _handle_id_conflict(xform, domain): """ For id conflicts, we check if the files contain exactly the same content, If they do, we just log this as a dupe. If they don't, we deprecate the previous form and overwrite it with the new form's contents. """ assert domain conflict_id = xform.form_id interface = FormProcessorInterface(domain) if interface.is_duplicate(conflict_id, domain): # It looks like a duplicate/edit in the same domain so pursue that workflow. return _handle_duplicate(xform) else: # the same form was submitted to two domains, or a form was submitted with # an ID that belonged to a different doc type. these are likely developers # manually testing or broken API users. just resubmit with a generated ID. xform = interface.assign_new_id(xform) return FormProcessingResult(xform)
def process_stock(xforms, case_db=None): """ process the commtrack xml constructs in an incoming submission """ if not case_db: case_db = FormProcessorInterface(xforms[0].domain).casedb_cache( domain=xforms[0].domain, load_src="process_stock", ) else: assert isinstance(case_db, AbstractCaseDbCache) stock_report_helpers = [] case_action_intents = [] sorted_forms = sorted(xforms, key=lambda f: 1 if f.is_deprecated else 0) for xform in sorted_forms: try: actions_for_form = get_stock_actions(xform) stock_report_helpers += actions_for_form.stock_report_helpers case_action_intents += actions_for_form.case_action_intents except MissingFormXml: if not xform.is_deprecated: raise # in the case where the XML is missing for the deprecated form add a # deprecation intent for all cases touched by the primary form case_ids = {intent.case_id for intent in case_action_intents} case_action_intents += get_ledger_case_action_intents( xform, case_ids) # validate the parsed transactions for stock_report_helper in stock_report_helpers: stock_report_helper.validate() relevant_cases = mark_cases_changed(case_action_intents, case_db) return StockProcessingResult( xform=sorted_forms[0], relevant_cases=relevant_cases, stock_report_helpers=stock_report_helpers, )
def handle(self, log_file, **options): to_archive = [] case_id = options.get('case_id') case_forms = FormProcessorInterface(domain).get_case_forms(case_id) for form in case_forms: if form.user_id in ("system", "", None) and form.metadata.username == "system": updates = get_case_updates(form) if options.get('case_property') is not None: update_actions = [ update.get_update_action() for update in updates if update.id == case_id ] for action in update_actions: if isinstance(action, CaseUpdateAction): if options.get('case_property') in set( action.dynamic_properties.keys()): to_archive.append(form) else: to_archive.append(form) to_archive.sort(key=lambda f: f.received_on) to_archive = to_archive[:-1] print("Will archive {} forms".format(len(to_archive))) xform_archived.disconnect(rebuild_form_cases) with open(log_file, "w") as f: for form in with_progress_bar(to_archive): f.write(form.form_id + "\n") f.flush() if options['commit']: form.archive( user_id="archive_single_case_repeater_forms_script") xform_archived.connect(rebuild_form_cases) rebuild_case_from_forms( domain, case_id, UserRequestedRebuild( user_id="archive_single_case_repeater_forms_script"))
def __init__(self, instance=None, attachments=None, auth_context=None, domain=None, app_id=None, build_id=None, path=None, location=None, submit_ip=None, openrosa_headers=None, last_sync_token=None, received_on=None, date_header=None, partial_submission=False, case_db=None): assert domain, domain assert instance, instance assert not isinstance(instance, HttpRequest), instance self.domain = domain self.app_id = app_id self.build_id = build_id # get_location has good default self.location = location or couchforms.get_location() self.received_on = received_on self.date_header = date_header self.submit_ip = submit_ip self.last_sync_token = last_sync_token self.openrosa_headers = openrosa_headers or {} self.instance = instance self.attachments = attachments or {} self.auth_context = auth_context or DefaultAuthContext() self.path = path self.interface = FormProcessorInterface(domain) self.formdb = FormAccessors(domain) self.partial_submission = partial_submission # always None except in the case where a system form is being processed as part of another submission # e.g. for closing extension cases self.case_db = case_db
def _get_forms(self, case_id, context): domain = context.root_doc['domain'] xmlns = [self.xmlns] if isinstance(self.xmlns, six.string_types) else self.xmlns cache_key = (self.__class__.__name__, case_id, hashlib.md5(','.join(xmlns)).hexdigest(), self.reverse) if context.get_cache_value(cache_key) is not None: return context.get_cache_value(cache_key) xforms = FormProcessorInterface(domain).get_case_forms(case_id) xforms = sorted( [form for form in xforms if form.xmlns in xmlns and form.domain == domain], key=lambda x: x.received_on ) if not xforms: form = None else: index = -1 if self.reverse else 0 form = xforms[index].to_json() context.set_cache_value(cache_key, form) return form
def _migrate_form_and_attachments(domain, couch_form): """ This copies the couch form into a new sql form but does not save it. See form_processor.parsers.form._create_new_xform and SubmissionPost._set_submission_properties for what this should do. """ interface = FormProcessorInterface(domain) form_data = couch_form.form # todo: timezone migration if we want here # adjust_datetimes(form_data) sql_form = interface.new_xform(form_data) assert isinstance(sql_form, XFormInstanceSQL) sql_form.domain = domain # todo: attachments. # note that if these are in the blobdb then we likely don't need to move them, # just need to bring the references across # interface.store_attachments(xform, attachments) # submission properties sql_form.auth_context = couch_form.auth_context sql_form.submit_ip = couch_form.submit_ip # todo: this property appears missing from sql forms - do we need it? # sql_form.path = couch_form.path sql_form.openrosa_headers = couch_form.openrosa_headers sql_form.last_sync_token = couch_form.last_sync_token sql_form.received_on = couch_form.received_on sql_form.date_header = couch_form.date_header sql_form.app_id = couch_form.app_id sql_form.build_id = couch_form.build_id # export_tag intentionally removed # sql_form.export_tag = ["domain", "xmlns"] sql_form.partial_submission = couch_form.partial_submission return sql_form
def get_form_ready_to_save(metadata, is_db_test=False): from corehq.form_processor.parsers.form import process_xform_xml from corehq.form_processor.utils import get_simple_form_xml, convert_xform_to_json from corehq.form_processor.interfaces.processor import FormProcessorInterface from corehq.form_processor.models import Attachment assert metadata is not None metadata.domain = metadata.domain or uuid.uuid4().hex form_id = uuid.uuid4().hex form_xml = get_simple_form_xml(form_id=form_id, metadata=metadata) if is_db_test: wrapped_form = process_xform_xml(metadata.domain, form_xml).submitted_form else: interface = FormProcessorInterface(domain=metadata.domain) form_json = convert_xform_to_json(form_xml) wrapped_form = interface.new_xform(form_json) wrapped_form.domain = metadata.domain interface.store_attachments(wrapped_form, [ Attachment(name='form.xml', raw_content=form_xml, content_type='text/xml') ]) wrapped_form.received_on = metadata.received_on wrapped_form.app_id = metadata.app_id return wrapped_form
def _perfom_post_save_actions(form, save=True): interface = FormProcessorInterface(form.domain) cache = interface.casedb_cache( domain=form.domain, lock=False, deleted_ok=True, xforms=[form] ) with cache as casedb: case_stock_result = SubmissionPost.process_xforms_for_cases([form], casedb) case_models = case_stock_result.case_models if interface.use_sql_domain: forms = ProcessedForms(form, None) stock_result = case_stock_result.stock_result try: FormProcessorSQL.publish_changes_to_kafka(forms, case_models, stock_result) except Exception: error_message = "Error publishing to kafka" return ReprocessingResult(form, None, None, error_message) try: save and SubmissionPost.do_post_save_actions(casedb, [form], case_stock_result) except PostSaveError: error_message = "Error performing post save operations" return ReprocessingResult(form, None, None, error_message) return ReprocessingResult(form, case_models, None, None)
def __init__(self, submitted_form): self.submitted_form = submitted_form self.interface = FormProcessorInterface(self.submitted_form.domain)
def _handle_duplicate(new_doc): """ Handle duplicate xforms and xform editing ('deprecation') existing doc *must* be validated as an XFormInstance in the right domain and *must* include inline attachments :returns: A two-tuple: `(<new form>, <duplicate form or None>)` The new form may have a different `form_id` than `new_doc.form_id`. """ interface = FormProcessorInterface(new_doc.domain) conflict_id = new_doc.form_id try: existing_doc = FormAccessors(new_doc.domain).get_with_attachments(conflict_id) except ResourceNotFound: # Original form processing failed but left behind a form doc with no # attachments. It's safe to delete this now since we're going to re-process # the form anyway. from couchforms.models import XFormInstance XFormInstance.get_db().delete_doc(conflict_id) return new_doc, None try: existing_md5 = existing_doc.xml_md5() except MissingFormXml: existing_md5 = None if not existing_doc.is_error: existing_doc.problem = 'Missing form.xml' new_md5 = new_doc.xml_md5() if existing_md5 != new_md5: _soft_assert = soft_assert(to='{}@{}.com'.format('skelly', 'dimagi'), exponential_backoff=False) if new_doc.xmlns != existing_doc.xmlns: # if the XMLNS has changed this probably isn't a form edit # it could be a UUID clash (yes we've had that before) # Assign a new ID to the form and process as normal + notify_admins xform = interface.assign_new_id(new_doc) _soft_assert( False, "Potential UUID clash", { 'incoming_form_id': conflict_id, 'existing_form_id': existing_doc.form_id, 'new_form_id': xform.form_id, 'incoming_xmlns': new_doc.xmlns, 'existing_xmlns': existing_doc.xmlns, 'domain': new_doc.domain, } ) return xform, None else: if existing_doc.is_error and not existing_doc.initial_processing_complete: # edge case from ICDS where a form errors and then future re-submissions of the same # form do not have the same MD5 hash due to a bug on mobile: # see https://dimagi-dev.atlassian.net/browse/ICDS-376 # since we have a new form and the old one was not successfully processed # we can effectively ignore this form and process the new one as normal if not interface.use_sql_domain: new_doc._rev, existing_doc._rev = existing_doc._rev, new_doc._rev interface.assign_new_id(existing_doc) existing_doc.save() return new_doc, None else: # if the form contents are not the same: # - "Deprecate" the old form by making a new document with the same contents # but a different ID and a doc_type of XFormDeprecated # - Save the new instance to the previous document to preserve the ID NotAllowed.check(new_doc.domain) existing_doc, new_doc = apply_deprecation(existing_doc, new_doc, interface) return new_doc, existing_doc else: # follow standard dupe handling, which simply saves a copy of the form # but a new doc_id, and a doc_type of XFormDuplicate duplicate = interface.deduplicate_xform(new_doc) return duplicate, existing_doc
def setUpClass(cls): super(StrictDatetimesTest, cls).setUpClass() cls.domain = 'strict-datetimes-test-domain' cls.interface = FormProcessorInterface(cls.domain)
def reprocess_form(sender, xform, *args, **kwargs): from corehq.form_processor.interfaces.processor import FormProcessorInterface FormProcessorInterface(xform.domain).ledger_processor.process_form_unarchived(xform)
def test_update_responses(self): formxml = FormSubmissionBuilder( form_id='123', form_properties={ 'breakfast': 'toast', # Simple questions 'lunch': 'sandwich', 'cell': { # Simple group 'cytoplasm': 'squishy', 'organelles': 'grainy', }, 'shelves': [ # Simple repeat group {'position': 'top'}, {'position': 'middle'}, {'position': 'bottom'}, ], 'grandparent': [ # Repeat group with child group {'name': 'Haruki'}, {'name': 'Sugako'}, { 'name': 'Emma', 'parent': { 'name': 'Haruki', 'child': { 'name': 'Nao', }, } }, ], 'body': [ # Repeat group with child repeat group {'arm': [ {'elbow': '1'}, {'finger': '5'}, ]}, {'leg': [ {'knee': '1'}, {'toe': '5'}, ]}, ], } ).as_xml_string() pic = UploadedFile(BytesIO(b"fake"), 'pic.jpg', content_type='image/jpeg') xform = submit_form_locally(formxml, DOMAIN, attachments={ "image": pic }).xform updates = { 'breakfast': 'fruit', 'cell/organelles': 'bulbous', 'shelves[1]/position': 'third', 'shelves[3]/position': 'first', 'grandparent[1]/name': 'Haruki #1', 'grandparent[3]/name': 'Ema', 'grandparent[3]/parent/name': 'Haruki #2', 'grandparent[3]/parent/child/name': 'Nao-chan', 'body[1]/arm[1]/elbow': '2', 'body[2]/leg[2]/toe': '10', } errors = FormProcessorInterface(DOMAIN).update_responses( xform, updates, 'user1') form = FormAccessors(DOMAIN).get_form(xform.form_id) self.assertEqual(0, len(errors)) self.assertEqual('fruit', form.form_data['breakfast']) self.assertEqual('sandwich', form.form_data['lunch']) self.assertEqual('squishy', form.form_data['cell']['cytoplasm']) self.assertEqual('bulbous', form.form_data['cell']['organelles']) self.assertEqual('third', form.form_data['shelves'][0]['position']) self.assertEqual('middle', form.form_data['shelves'][1]['position']) self.assertEqual('first', form.form_data['shelves'][2]['position']) self.assertEqual('Haruki #1', form.form_data['grandparent'][0]['name']) self.assertEqual('Sugako', form.form_data['grandparent'][1]['name']) self.assertEqual('Ema', form.form_data['grandparent'][2]['name']) self.assertEqual('Haruki #2', form.form_data['grandparent'][2]['parent']['name']) self.assertEqual( 'Nao-chan', form.form_data['grandparent'][2]['parent']['child']['name']) self.assertEqual('2', form.form_data['body'][0]['arm'][0]['elbow']) self.assertEqual('5', form.form_data['body'][0]['arm'][1]['finger']) self.assertEqual('1', form.form_data['body'][1]['leg'][0]['knee']) self.assertEqual('10', form.form_data['body'][1]['leg'][1]['toe']) self.assertIn("image", form.attachments) self.assertEqual(form.get_attachment("image"), b"fake")
def _create_form(self, domain): metadata = TestFormMetadata(domain=domain) form = get_form_ready_to_save(metadata) FormProcessorInterface(domain=domain).save_processed_models([form]) return form
def setUp(self): self.interface = FormProcessorInterface()