Environmental Information
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
- Publish an XLSForm to a project.
PATCH /api/v1/forms/<pk> with the same XLSForm (or any XLSForm where the settings-sheet version matches the existing XForm.version).
- 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.
Environmental Information
Problem description
When a form is replaced (via
XFormViewSet.partial_update→_try_update_xlsform),create_xform_versioninonadata/libs/utils/logger_tools.pyis called to save a snapshot inXFormVersion. The model hasunique_together = ["xform", "version"], andcreate_xform_versionswallowsIntegrityErrorsilently:If the replacement XLSForm/XML carries the same
versionstring as the existingXFormVersionrow (a common case — users frequently re-upload a fixed XLSForm without bumping the settings-sheetversion), the insert fails on the unique constraint and is silently dropped. Meanwhiledd.xls/dd.xmlhave already been overwritten on theDataDictionary, so the prior XLSForm/XML is unrecoverable and no new version row is recorded.A second, related bug exists in
publish_xml_form:dd.versionis never set from the parsed XML, so initial XML-form publishes also fail to create anXFormVersionrow (the empty version causes the same silently-swallowedIntegrityError).Affected call sites:
onadata/apps/api/viewsets/xform_viewset.py::_try_update_xlsformonadata/libs/utils/logger_tools.py::publish_xls_form,publish_xml_form,create_xform_versiononadata/apps/viewer/models/data_dictionary.py::DataDictionary.saveExpected behavior
Each form replacement persists a new
XFormVersionrow that captures the replacement'sxls,xml,json, andversion, 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
PATCH /api/v1/forms/<pk>with the same XLSForm (or any XLSForm where the settings-sheetversionmatches the existingXForm.version).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-bumpXForm.versionto<original>-N(smallest unused N >= 2) when a replacement would otherwise collide. For XLSForms the bump runs inDataDictionary.save()beforesurvey.to_xml()so the XML/JSON/version stay in sync. For raw XML uploadspublish_xml_formextracts the version from the XML, bumps it, and surgically rewrites the<data version="...">attribute.publish_xml_formalso now setsdd.versionon initial publish so the firstXFormVersionrow is persisted.