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. :returns: A two-tuple: `(<new form>, <duplicate form or None>)` The new form may have a different `form_id` than `xform.form_id`. """ 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. logging.info('Handling duplicate doc id %s for domain %s', conflict_id, domain) 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. interface.assign_new_id(xform) logging.info('Reassigned doc id from %s to %s', conflict_id, xform.form_id) return xform, None
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 existing_md5 = existing_doc.xml_md5() 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 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 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 _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 _handle_id_conflict(instance, 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, instance) 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 _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 _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 _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 existing_md5 = existing_doc.xml_md5() 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 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) device_id = new_doc.metadata.deviceID # we expect edits from formplayer so exclude those _soft_assert( device_id == FORMPLAYER_DEVICE_ID, "Form edit", { 'form_id': new_doc.form_id, 'deprecated_form': existing_doc.form_id, 'domain': new_doc.domain, 'device_id': device_id, 'cc_version': new_doc.metadata.appVersion, } ) 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 _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 existing_doc = XFormInstance.objects.get_with_attachments( conflict_id, new_doc.domain) is_icds = settings.SERVER_ENVIRONMENT in settings.ICDS_ENVS try: if is_icds and new_doc.metadata.deviceID == existing_doc.metadata.deviceID: # ICDS does not use 'edit form' functionality via the web and form editing is not possible # on mobile devices so it's safe to assume this is a duplicate without checking md5 etc. duplicate = interface.deduplicate_xform(new_doc) return duplicate, existing_doc existing_md5 = existing_doc.xml_md5() except MissingFormXml: existing_md5 = new_md5 = None if not existing_doc.is_error: existing_doc.problem = 'Missing form.xml' else: # only do this if we need to do the comparison new_md5 = new_doc.xml_md5() if existing_md5 is None or existing_md5 != new_md5: def _deprecate_old_form(): existing, new = apply_deprecation(existing_doc, new_doc, interface) return new, existing def _replace_old_form(): interface.assign_new_id(existing_doc) existing_doc.orig_id = new_doc.form_id existing_doc.save() return new_doc, None _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: # 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 if not existing_doc.initial_processing_complete: # 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 return _replace_old_form() elif not interface.form_has_case_transactions( existing_doc.form_id): # likely an error during saving return _replace_old_form() else: return _deprecate_old_form() 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 return _deprecate_old_form() 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