Skip to content

Commit

Permalink
Add NamedBytesIO to work around pyxform change…
Browse files Browse the repository at this point in the history
  • Loading branch information
jnm committed Nov 6, 2024
1 parent da9a3d6 commit 3812fc4
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 1 deletion.
4 changes: 3 additions & 1 deletion kobo/apps/openrosa/apps/viewer/models/data_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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
from kpi.utils.pyxform_compatibility import NamedBytesIO


class ColumnRename(models.Model):
Expand Down Expand Up @@ -157,8 +158,9 @@ def add_instances(self):

def save(self, *args, **kwargs):
if self.xls:
xls_io = NamedBytesIO.fromfieldfile(self.xls)
survey = create_survey_from_xls(
self.xls, default_name=DEFAULT_SURVEY_NAME
xls_io, default_name=DEFAULT_SURVEY_NAME
)
if survey.name == DEFAULT_SURVEY_NAME:
survey.name = survey.id_string
Expand Down
38 changes: 38 additions & 0 deletions kpi/utils/pyxform_compatibility.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from io import BytesIO
from pyxform.constants import ALLOW_CHOICE_DUPLICATES


def allow_choice_duplicates(content: dict) -> None:
"""
Modify `content` to include `allow_choice_duplicates=Yes` in the settings
Expand All @@ -13,3 +15,39 @@ def allow_choice_duplicates(content: dict) -> None:
settings = content.setdefault('settings', {})
if ALLOW_CHOICE_DUPLICATES not in settings:
settings[ALLOW_CHOICE_DUPLICATES] = 'yes'


class NamedBytesIO(BytesIO):
"""
Changes in XLSForm/pyxform#718 prevent
`pyxform.builder.create_survey_from_xls()` from accepting a
`django.db.models.fields.files.FieldFile`. Only instances of
`bytes | BytesIO | IOBase` are now accepted for treatment as file-like
objects, and furthermore, anything that is not already a `BytesIO` will
have its contents placed inside a newly instantiated one.
Problem: `BytesIO`s do not have `name`s, and the constructor for
`pyxform.xls2json.SurveyReader` fails because of that.
Workaround: a `BytesIO` with a `name` 🙃
For more details, see
https://github.com/kobotoolbox/kpi/pull/5126#discussion_r1829763316
"""

def __init__(self, *args, name=None, **kwargs):
if name is None:
raise NotImplementedError('Use `BytesIO` if no `name` is needed')
super().__init__(*args, **kwargs)
self.name = name

@classmethod
def fromfieldfile(cls, django_fieldfile):
"""
Given a Django `FieldFile`, return an instance of `NamedBytesIO`
à la `datetime.datetime.fromtimestamp()`
"""
new_instance = cls(django_fieldfile.read(), name=django_fieldfile.name)
django_fieldfile.seek(0) # Be kind: rewind
return new_instance

0 comments on commit 3812fc4

Please sign in to comment.