Пример #1
0
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

    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():
            NotAllowed.check(new_doc.domain)
            existing, new = apply_deprecation(existing_doc, new_doc, interface)
            return new, existing

        def _replace_old_form():
            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.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 interface.use_sql_domain and 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