diff --git a/questionpy_sdk/webserver/question_ui.py b/questionpy_sdk/webserver/question_ui.py index 65f208e..eed84f6 100644 --- a/questionpy_sdk/webserver/question_ui.py +++ b/questionpy_sdk/webserver/question_ui.py @@ -4,6 +4,7 @@ from __future__ import annotations import re +from enum import StrEnum from functools import cached_property from random import Random from typing import Any @@ -149,11 +150,23 @@ def __init__(self) -> None: self.required_fields: list[str] = [] +class QuestionDisplayRole(StrEnum): + DEVELOPER = "DEVELOPER" + PROCTOR = "PROCTOR" + SCORER = "SCORER" + TEACHER = "TEACHER" + + class QuestionDisplayOptions(BaseModel): general_feedback: bool = True feedback: bool = True right_answer: bool = True - context: dict = {} + roles: set[QuestionDisplayRole] = { + QuestionDisplayRole.DEVELOPER, + QuestionDisplayRole.PROCTOR, + QuestionDisplayRole.SCORER, + QuestionDisplayRole.TEACHER, + } readonly: bool = False @@ -262,20 +275,14 @@ def _hide_unwanted_feedback(self) -> None: def _hide_if_role(self) -> None: """Hides elements based on user role. - Removes elements with `qpy:if-role` attributes if the user matches none of the given roles in this context. + Removes elements with `qpy:if-role` attributes if the user matches none of the roles. """ - if self._options.context.get("role") == "admin": - return - for element in _assert_element_list(self._xpath("//*[@qpy:if-role]")): - attr = element.attrib.get(f"{{{self.QPY_NAMESPACE}}}if-role") - if attr is None: - continue - allowed_roles = attr.split() + if attr := element.get(f"{{{self.QPY_NAMESPACE}}}if-role"): + allowed_roles = [role.upper() for role in re.split(r"[\s|]+", attr)] + has_role = any(role in allowed_roles and role in self._options.roles for role in QuestionDisplayRole) - if self._options.context.get("role") not in allowed_roles: - parent = element.getparent() - if parent is not None: + if not has_role and (parent := element.getparent()) is not None: parent.remove(element) def _set_input_values_and_readonly(self) -> None: diff --git a/tests/questionpy_sdk/webserver/test_data/if-role.xhtml b/tests/questionpy_sdk/webserver/test_data/if-role.xhtml index 3d85a5f..81110d4 100644 --- a/tests/questionpy_sdk/webserver/test_data/if-role.xhtml +++ b/tests/questionpy_sdk/webserver/test_data/if-role.xhtml @@ -3,5 +3,6 @@
You're a developer!
You're a scorer!
You're a proctor!
-
You're any of the above!
+
You're any of the above!
diff --git a/tests/questionpy_sdk/webserver/test_question_ui.py b/tests/questionpy_sdk/webserver/test_question_ui.py index 2c352be..2d8cb91 100644 --- a/tests/questionpy_sdk/webserver/test_question_ui.py +++ b/tests/questionpy_sdk/webserver/test_question_ui.py @@ -8,6 +8,7 @@ from questionpy_sdk.webserver.question_ui import ( QuestionDisplayOptions, + QuestionDisplayRole, QuestionFormulationUIRenderer, QuestionMetadata, QuestionUIRenderer, @@ -122,14 +123,23 @@ def test_should_show_inline_feedback(result: str) -> None: @pytest.mark.parametrize( - ("user_context", "expected"), + ("options", "expected"), [ ( - "guest", + QuestionDisplayOptions(roles=set()), "
", ), ( - "admin", + QuestionDisplayOptions(roles={QuestionDisplayRole.SCORER}), + """ +
+
You're a scorer!
+
You're any of the above!
+
+ """, + ), + ( + QuestionDisplayOptions(), """
You're a teacher!
@@ -143,10 +153,7 @@ def test_should_show_inline_feedback(result: str) -> None: ], ) @pytest.mark.ui_file("if-role") -def test_element_visibility_based_on_role(user_context: str, expected: str, xml_content: str) -> None: - options = QuestionDisplayOptions() - options.context["role"] = user_context - +def test_element_visibility_based_on_role(options: QuestionDisplayOptions, expected: str, xml_content: str) -> None: renderer = QuestionUIRenderer(xml_content, {}, options) result = renderer.html