Skip to content

Commit

Permalink
feat: Enable support for XLSForm name setting (#5137)
Browse files Browse the repository at this point in the history
## Checklist

1. [x] If you've added code that should be tested, add tests
2. [ ] If you've changed APIs, update (or create!) the documentation
3. [x] Ensure the tests pass
4. [x] Run `./python-format.sh` to make sure that your code lints and
that you've followed [our coding
style](https://github.com/kobotoolbox/kpi/blob/main/CONTRIBUTING.md)
5. [x] Write a title and, if necessary, a description of your work
suitable for publishing in our [release
notes](https://community.kobotoolbox.org/tag/release-notes)
6. [x] Mention any related issues in this repository (as #ISSUE) and in
other repositories (as kobotoolbox/other#ISSUE)
7. [x] Open an issue in the
[docs](https://github.com/kobotoolbox/docs/issues/new) if there are
UI/UX changes

## Description

This is an advanced feature for unusual cases where it's necessary to
[override the root XML node
name](https://xlsform.org/en/#specify-xforms-root-node-name) used in
submissions. This may be used for an upcoming feature to allow
customizable confirmation messages in Enketo.

## Notes

- Implemented logic to check and set the name attribute from XLSForms in
the survey object.
- The existing behavior where name was not utilized has been modified to
honor the name setting, defaulting to the id_string if name is not
provided or is invalid.

## Related issues

Part of #5112

---------

Co-authored-by: John N. Milner <[email protected]>
  • Loading branch information
rajpatel24 and jnm authored Nov 4, 2024
1 parent 98b9a95 commit c3ee4c9
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 4 deletions.
10 changes: 6 additions & 4 deletions kobo/apps/openrosa/apps/viewer/models/data_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
DictOrganizer,
)
from kobo.apps.openrosa.libs.utils.model_tools import queryset_iterator, set_uuid
from kpi.constants import DEFAULT_SURVEY_NAME
from kpi.utils.mongo_helper import MongoHelper


Expand Down Expand Up @@ -156,10 +157,11 @@ def add_instances(self):

def save(self, *args, **kwargs):
if self.xls:
survey = create_survey_from_xls(self.xls)
survey.update({
'name': survey.id_string,
})
survey = create_survey_from_xls(
self.xls, default_name=DEFAULT_SURVEY_NAME
)
if survey.name == DEFAULT_SURVEY_NAME:
survey.name = survey.id_string
self.json = survey.to_json()
self.xml = survey.to_xml()
self._mark_start_time_boolean()
Expand Down
2 changes: 2 additions & 0 deletions kpi/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
ASSET_TYPE_TEMPLATE: [ASSET_TYPE_SURVEY, ASSET_TYPE_TEMPLATE]
}

DEFAULT_SURVEY_NAME = '__kobo_default_survey_name_value__'

ASSET_TYPE_ARG_NAME = 'asset_type'

# List of app labels that need to read/write data from KoBoCAT database
Expand Down
76 changes: 76 additions & 0 deletions kpi/tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from rest_framework import serializers

from kobo.apps.kobo_auth.shortcuts import User
from kobo.apps.openrosa.apps.logger.models import XForm
from kobo.apps.openrosa.apps.logger.xform_instance_parser import XFormInstanceParser
from kpi.constants import (
ASSET_TYPE_SURVEY,
ASSET_TYPE_COLLECTION,
Expand Down Expand Up @@ -779,3 +781,77 @@ def test_anonymous_as_baseline_for_authenticated(self):
for user_obj in AnonymousUser(), self.anotheruser:
self.assertTrue(user_obj.has_perm(
PERM_VIEW_ASSET, self.asset))


class TestAssetNameSettingHandling(AssetsTestCase):
"""
Tests for the 'name' setting in the asset content
"""
def test_asset_name_matches_instance_root(self):
"""
Test if 'name' setting is provided, it should match with the root node.
"""
content = {
'survey': [
{
'type': 'text',
'name': 'some_text',
'label': 'Enter some text',
},
],
'settings': {'name': 'custom_root_node_name'}
}

# Create and deploy the asset
asset = Asset.objects.create(
owner=User.objects.get(username=self.user),
content=content,
asset_type=ASSET_TYPE_SURVEY
)
asset.deploy(backend='mock', active=True)

# Get the deployed XForm and parse it
xform = XForm.objects.get(id_string=asset.uid)
parser = XFormInstanceParser(xform.xml, xform.data_dictionary())

# Access the first child element of the <instance> node
instance_node = parser.get_root_node().getElementsByTagName('instance')[0]
root_element = instance_node.firstChild

# Assert that the name setting matches the root node name
assert root_element.nodeName == 'custom_root_node_name'

def test_asset_without_name_setting(self):
"""
Test if 'name' setting is not provided, the root node should fall back
to asset UID
"""
content = {
'survey': [
{
'type': 'text',
'name': 'some_text',
'label': 'Enter some text',
},
],
# No 'name' setting provided in this case
}

# Create and deploy the asset
asset = Asset.objects.create(
owner=User.objects.get(username=self.user),
content=content,
asset_type=ASSET_TYPE_SURVEY
)
asset.deploy(backend='mock', active=True)

# Get the deployed XForm and parse it
xform = XForm.objects.get(id_string=asset.uid)
parser = XFormInstanceParser(xform.xml, xform.data_dictionary())

# Access the first child element of the <instance> node
instance_node = parser.get_root_node().getElementsByTagName('instance')[0]
root_element = instance_node.firstChild

# Assert that the root node name is the asset.uid
assert root_element.nodeName == asset.uid

0 comments on commit c3ee4c9

Please sign in to comment.