Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add: form creation with attachments #92

Merged
merged 1 commit into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions pyodk/_endpoints/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def get(
def create(
self,
definition: PathLike | str | bytes,
attachments: Iterable[PathLike | str] | None = None,
ignore_warnings: bool | None = True,
form_id: str | None = None,
project_id: int | None = None,
Expand All @@ -133,6 +134,7 @@ def create(

:param definition: The path to the file to upload (string or PathLike), or the
form definition in memory (string (XML) or bytes (XLS/XLSX)).
:param attachments: The paths of the form attachment file(s) to upload.
:param ignore_warnings: If True, create the form if there are XLSForm warnings.
:param form_id: The xmlFormId of the Form being referenced.
:param project_id: The id of the project this form belongs to.
Expand All @@ -145,7 +147,9 @@ def create(
form_id=form_id,
project_id=project_id,
)
params["publish"] = True

# Create the new Form definition, in draft state.
params["publish"] = False
response = self.session.response_or_error(
method="POST",
url=self.session.urlformat(self.urls.forms, project_id=pid),
Expand All @@ -155,7 +159,23 @@ def create(
data=form_def,
)
data = response.json()
return Form(**data)

# In case the form_id parameter was None, use the (maybe generated) response value.
form = Form(**data)
fp_ids = {"form_id": form.xmlFormId, "project_id": project_id}

# Upload the attachments, if any.
if attachments is not None:
fda = FormDraftAttachmentService(session=self.session, **self._default_kw())
for attach in attachments:
if not fda.upload(file_path=attach, **fp_ids):
raise PyODKError("Form create (attachment upload) failed.")

# Publish the draft.
if not fd.publish(**fp_ids):
raise PyODKError("Form create (draft publish) failed.")

return form

def update(
self,
Expand Down
25 changes: 24 additions & 1 deletion tests/endpoints/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ def test_get__ok(self):
)
self.assertIsInstance(observed, Form)

def test_create__ok(self):
@get_mock_context
def test_create__ok(self, ctx: MockContext):
"""Should return a FormType object."""
fixture = forms_data.test_forms
with patch.object(Session, "request") as mock_session:
Expand All @@ -126,6 +127,28 @@ def test_create__ok(self):
)
self.assertIsInstance(observed, Form)

@get_mock_context
def test_create__with_attachments__ok(self, ctx: MockContext):
"""Should return a FormType object."""
fixture = forms_data.test_forms
with patch.object(Session, "request") as mock_session:
mock_session.return_value.status_code = 200
mock_session.return_value.json.return_value = fixture["response_data"][1]
with Client() as client, utils.get_temp_file(suffix=".xml") as fp:
fp.write_text(forms_data.get_xml__range_draft())
observed = client.forms.create(
definition=fp,
project_id=fixture["project_id"],
form_id=fixture["response_data"][1]["xmlFormId"],
attachments=["/some/path/a.jpg", "/some/path/b.jpg"],
)
self.assertIsInstance(observed, Form)
self.assertEqual(2, ctx.fda_upload.call_count)
ctx.fd_publish.assert_called_once_with(
form_id=fixture["response_data"][1]["xmlFormId"],
project_id=fixture["project_id"],
)

def test_update__def_or_attach_required(self):
"""Should raise an error if both 'definition' and 'attachments' are None."""
with self.assertRaises(PyODKError) as err:
Expand Down
12 changes: 11 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ def test_form_create__new_definition_xlsx(self):
form = self.client.forms.create(definition=wb)
self.assertTrue(form.xmlFormId.startswith("uuid:"))

def test_form_create__new_definition_xlsx_and_attachments(self):
"""Should create a new form with the new definition and attachment."""
form_def = forms_data.get_md__pull_data()
wb = md_table_to_bytes(mdstr=form_def)
form = self.client.forms.create(
definition=wb,
attachments=[(RESOURCES / "forms" / "fruits.csv").as_posix()],
)
self.assertTrue(form.xmlFormId.startswith("uuid:"))

# Below tests assume project has forms by these names already published.
def test_form_update__new_definition(self):
"""Should create a new version with the new definition."""
Expand Down Expand Up @@ -175,7 +185,7 @@ def test_form_update__new_definition_and_attachments__non_ascii_dingbat(self):
self.assertEqual(form.xmlFormId, "✅")

def test_form_update__with_version_updater__non_ascii_specials(self):
"""Should create a new version with new definition and attachment."""
"""Should create a new version with new definition."""
self.client.forms.update(
form_id="'=+/*-451%/%",
attachments=[],
Expand Down
Loading