diff --git a/questionpy_sdk/webserver/context.py b/questionpy_sdk/webserver/context.py index 2af9363..336865d 100644 --- a/questionpy_sdk/webserver/context.py +++ b/questionpy_sdk/webserver/context.py @@ -8,6 +8,7 @@ CheckboxElement, CheckboxGroupElement, FormElement, + GeneratedIdElement, GroupElement, HiddenElement, OptionsFormDefinition, @@ -23,6 +24,7 @@ CxdCheckboxGroupElement, CxdFormElement, CxdFormSection, + CxdGeneratedIdElement, CxdGroupElement, CxdHiddenElement, CxdOptionsFormDefinition, @@ -43,6 +45,7 @@ RadioGroupElement: CxdRadioGroupElement, SelectElement: CxdSelectElement, HiddenElement: CxdHiddenElement, + GeneratedIdElement: CxdGeneratedIdElement, } diff --git a/questionpy_sdk/webserver/elements.py b/questionpy_sdk/webserver/elements.py index 8bc4cd4..d0abd89 100644 --- a/questionpy_sdk/webserver/elements.py +++ b/questionpy_sdk/webserver/elements.py @@ -4,6 +4,7 @@ from re import Pattern, sub from typing import Annotated, Any, TypeAlias +from uuid import uuid4 from pydantic import BaseModel, Field, computed_field @@ -12,6 +13,7 @@ CheckboxGroupElement, FormElement, # noqa: F401 FormSection, + GeneratedIdElement, GroupElement, HiddenElement, Option, @@ -212,6 +214,16 @@ def __init__(self, **data: Any): super().__init__(**data, elements=[]) +class CxdGeneratedIdElement(GeneratedIdElement, _CxdFormElement): + cxd_value: str | None = None + + def add_form_data_value(self, element_form_data: Any) -> None: + if element_form_data: + self.cxd_value = element_form_data + else: + self.cxd_value = str(uuid4()) + + CxdFormElement: TypeAlias = Annotated[ CxdStaticTextElement | CxdTextInputElement @@ -222,7 +234,8 @@ def __init__(self, **data: Any): | CxdSelectElement | CxdHiddenElement | CxdGroupElement - | CxdRepetitionElement, + | CxdRepetitionElement + | CxdGeneratedIdElement, Field(discriminator="kind"), ] diff --git a/questionpy_sdk/webserver/routes/options.py b/questionpy_sdk/webserver/routes/options.py index 706a694..a438e62 100644 --- a/questionpy_sdk/webserver/routes/options.py +++ b/questionpy_sdk/webserver/routes/options.py @@ -1,9 +1,9 @@ # This file is part of the QuestionPy SDK. (https://questionpy.org) # The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md. # (c) Technische Universität Berlin, innoCampus - from http.client import UNPROCESSABLE_ENTITY from typing import TYPE_CHECKING, Never +from uuid import uuid4 import aiohttp_jinja2 from aiohttp import web @@ -69,8 +69,22 @@ async def repeat_element(request: web.Request) -> web.Response: data = await request.json() question_form_data = parse_form_data(data["form_data"]) repetition_list = get_nested_form_data(question_form_data, data["repetition_name"]) - if isinstance(repetition_list, list) and "increment" in data: - repetition_list.extend([repetition_list[-1]] * int(data["increment"])) + + if not isinstance(repetition_list, list) or "increment" not in data: + raise web.HTTPUnprocessableEntity + + new_repetition_items: list[dict] = [repetition_list[-1].copy() for _ in range(int(data["increment"]))] + # ID elements must be unique, so new items need a new value. + for new_repetition_item in new_repetition_items: + id_element_names = new_repetition_item.get("qpy_id_elements") + if not isinstance(id_element_names, list): + continue + + for id_element_name in id_element_names: + if id_element_name in new_repetition_item: + new_repetition_item[id_element_name] = str(uuid4()) + + repetition_list.extend(new_repetition_items) try: await _save_updated_form_data(question_form_data, webserver) diff --git a/questionpy_sdk/webserver/templates/elements/id.html.jinja2 b/questionpy_sdk/webserver/templates/elements/id.html.jinja2 new file mode 100644 index 0000000..35cee5b --- /dev/null +++ b/questionpy_sdk/webserver/templates/elements/id.html.jinja2 @@ -0,0 +1,4 @@ + +