Skip to content

XForm version not saved when a form is replaced with the same version string #3076

@kelvin-muchiri

Description

@kelvin-muchiri

Environmental Information

  • Onadata version: 5.15.3

Problem description

When a form is replaced (via XFormViewSet.partial_update_try_update_xlsform), create_xform_version in onadata/libs/utils/logger_tools.py is called to save a snapshot in XFormVersion. The model has unique_together = ["xform", "version"], and create_xform_version swallows IntegrityError silently:

try:
    with transaction.atomic():
        versioned_xform = XFormVersion.objects.create(...)
except IntegrityError:
    pass

If the replacement XLSForm/XML carries the same version string as the existing XFormVersion row (a common case — users frequently re-upload a fixed XLSForm without bumping the settings-sheet version), the insert fails on the unique constraint and is silently dropped. Meanwhile dd.xls/dd.xml have already been overwritten on the DataDictionary, so the prior XLSForm/XML is unrecoverable and no new version row is recorded.

A second, related bug exists in publish_xml_form: dd.version is never set from the parsed XML, so initial XML-form publishes also fail to create an XFormVersion row (the empty version causes the same silently-swallowed IntegrityError).

Affected call sites:

  • onadata/apps/api/viewsets/xform_viewset.py::_try_update_xlsform
  • onadata/libs/utils/logger_tools.py::publish_xls_form, publish_xml_form, create_xform_version
  • onadata/apps/viewer/models/data_dictionary.py::DataDictionary.save

Expected behavior

Each form replacement persists a new XFormVersion row that captures the replacement's xls, xml, json, and version, regardless of whether the user bumped the version string in the XLSForm/XML. The replacement is also surfaced to clients (e.g., ODK Collect) as a new version so devices fetch the updated form.

Steps to reproduce the behavior

  1. Publish an XLSForm to a project.
  2. PATCH /api/v1/forms/<pk> with the same XLSForm (or any XLSForm where the settings-sheet version matches the existing XForm.version).
  3. Inspect XFormVersion.objects.filter(xform=...) — only the original row exists. The replacement is lost.

Additional Information

Proposed fix on branch feat/onadata-1143-bump-version-on-form-replace: auto-bump XForm.version to <original>-N (smallest unused N >= 2) when a replacement would otherwise collide. For XLSForms the bump runs in DataDictionary.save() before survey.to_xml() so the XML/JSON/version stay in sync. For raw XML uploads publish_xml_form extracts the version from the XML, bumps it, and surgically rewrites the <data version="..."> attribute. publish_xml_form also now sets dd.version on initial publish so the first XFormVersion row is persisted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions