Skip to content

update_xlsform.py

Append mandatory fields to the XLSForm for use in FMTM.

Parameters:

Name Type Description Default
custom_form(BytesIO)

the XLSForm data uploaded, wrapped in BytesIO.

required
form_name(str)

the friendly form name in ODK web view.

required
additional_entities(list[str])

add extra select_one_from_file fields to reference an additional Entity list (set of geometries). The values should be plural, so that 's' will be stripped in the field name.

required
new_geom_type DbGeomType

the type of geometry required when collecting new geometry data: point, line, polygon.

POINT

Returns:

Name Type Description
tuple (str, BytesIO)

the xFormId + the update XLSForm wrapped in BytesIO.

Source code in osm_fieldwork/update_xlsform.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
async def append_mandatory_fields(
    custom_form: BytesIO,
    form_name: str = f"fmtm_{uuid4()}",
    additional_entities: list[str] = None,
    new_geom_type: DbGeomType = DbGeomType.POINT,
) -> tuple[str, BytesIO]:
    """Append mandatory fields to the XLSForm for use in FMTM.

    Args:
        custom_form(BytesIO): the XLSForm data uploaded, wrapped in BytesIO.
        form_name(str): the friendly form name in ODK web view.
        additional_entities(list[str]): add extra select_one_from_file fields to
            reference an additional Entity list (set of geometries).
            The values should be plural, so that 's' will be stripped in the
            field name.
        new_geom_type (DbGeomType): the type of geometry required when collecting
            new geometry data: point, line, polygon.

    Returns:
        tuple(str, BytesIO): the xFormId + the update XLSForm wrapped in BytesIO.
    """
    log.info("Appending field mapping questions to XLSForm")
    custom_sheets = pd.read_excel(custom_form, sheet_name=None, engine="calamine")

    if "survey" not in custom_sheets:
        msg = "Survey sheet is required in XLSForm!"
        log.error(msg)
        raise ValueError(msg)

    custom_sheets = standardize_xlsform_sheets(custom_sheets)

    log.debug("Merging survey sheet XLSForm data")
    survey_df = create_survey_df(new_geom_type)
    custom_sheets["survey"] = merge_dataframes(survey_df, custom_sheets.get("survey"), digitisation_df)

    # Ensure the 'choices' sheet exists in custom_sheets
    if "choices" not in custom_sheets or custom_sheets["choices"] is None:
        custom_sheets["choices"] = pd.DataFrame(columns=["list_name", "name", "label::english(en)"])

    log.debug("Merging choices sheet XLSForm data")
    custom_sheets["choices"] = merge_dataframes(choices_df, custom_sheets.get("choices"), digitisation_choices_df)

    # Append or overwrite 'entities' and 'settings' sheets
    log.debug("Overwriting entities and settings XLSForm sheets")
    custom_sheets["entities"] = entities_df
    if "entities" not in custom_sheets:
        msg = "Entities sheet is required in XLSForm!"
        log.error(msg)
        raise ValueError(msg)
    if "settings" not in custom_sheets:
        msg = "Settings sheet is required in XLSForm!"
        log.error(msg)
        raise ValueError(msg)

    # Extract existing form id if present, else set to random uuid
    if "form_id" in custom_sheets["settings"]:
        xform_id = custom_sheets["settings"]["form_id"]
        log.debug(f"Extracted existing form_id field: {xform_id}")
    else:
        xform_id = str(uuid4())

    current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log.debug(f"Setting xFormId = {xform_id} | version = {current_datetime} | form_name = {form_name}")

    # Set the 'version' column to the current timestamp
    custom_sheets["settings"]["version"] = current_datetime
    custom_sheets["settings"]["form_id"] = xform_id
    custom_sheets["settings"]["form_title"] = form_name
    if "default_language" not in custom_sheets["settings"]:
        custom_sheets["settings"]["default_language"] = "en"

    # Append select_one_from_file for additional entities
    if additional_entities:
        log.debug("Adding additional entity list reference to XLSForm")
        for entity_name in additional_entities:
            custom_sheets["survey"] = append_select_one_from_file_row(custom_sheets["survey"], entity_name)

    # Return spreadsheet wrapped as BytesIO memory object
    output = BytesIO()
    with pd.ExcelWriter(output, engine="openpyxl") as writer:
        for sheet_name, df in custom_sheets.items():
            df.to_excel(writer, sheet_name=sheet_name, index=False)
    output.seek(0)
    return (xform_id, output)

options: show_source: false heading_level: 3