Verified Commit a3e4ddda authored by Erxleben, Fredo's avatar Erxleben, Fredo
Browse files

Introduce HasMandatory mixin and apply it to Question and Q-Collection


Signed-off-by: Erxleben, Fredo's avatarFredo Erxleben <f.erxleben@hzdr.de>
parent 2dbd55be
Pipeline #105981 failed with stages
in 1 minute and 27 seconds
......@@ -31,6 +31,46 @@ from hifis_surveyval.models.mixins.uses_settings import UsesSettings
from hifis_surveyval.models.translated import Translated
class HasMandatory(ABC):
"""
This mixin provides functionality for optional mandatory indicators.
Model elements may require something to be present (e.g. an answer)
directly or indirectly as a child of this object.
"""
YAML_TOKEN = "mandatory"
"""The token used in metadata YAML files to indicate mandatory-ness."""
def __init__(self, is_mandatory: bool, *args, **kwargs):
"""
Initialize an object with mandatory-ness indicator.
Args:
is_mandatory:
Whether this object has mandatory elements or not.
*args:
Will be forwarded to other mixins in the initialization order.
**kwargs:
Will be forwarded to other mixins in the initialization order.
"""
super(HasMandatory, self).__init__(*args, **kwargs)
self._is_mandatory = is_mandatory
@property
def is_mandatory(self) -> bool:
"""
Check whether this question is marked as mandatory.
Mandatory questions are expected to be answered by participants.
Returns:
True, if the question was marked as mandatory in the metadata,
False otherwise
"""
return self._is_mandatory
class HasLabel(ABC):
"""
This mixin provides a label property.
......@@ -144,6 +184,8 @@ class HasID(UsesSettings):
same.
"""
# TODO Move the repective YAML token in here
known_ids: Set[str] = set()
def __init__(
......
......@@ -34,13 +34,21 @@ from pandas import Series
from hifis_surveyval.core.settings import Settings
from hifis_surveyval.models.answer_option import AnswerOption
from hifis_surveyval.models.answer_types import VALID_ANSWER_TYPES
from hifis_surveyval.models.mixins.mixins import HasLabel, HasText, HasID
from hifis_surveyval.models.mixins.mixins import (HasLabel, HasText, HasID,
HasMandatory,
)
from hifis_surveyval.models.mixins.yaml_constructable import (
YamlConstructable, YamlDict)
from hifis_surveyval.models.translated import Translated
class Question(YamlConstructable, HasID, HasLabel, HasText):
class Question(
YamlConstructable,
HasID,
HasLabel,
HasText,
HasMandatory
):
"""
Questions model concrete questions that could be answered in the survey.
......@@ -58,7 +66,6 @@ class Question(YamlConstructable, HasID, HasLabel, HasText):
token_ID = "id"
token_ANSWER_OPTIONS = "answers"
token_DATA_TYPE = "datatype"
token_MANDATORY = "mandatory"
schema = schema.Schema(
{
......@@ -66,7 +73,7 @@ class Question(YamlConstructable, HasID, HasLabel, HasText):
HasLabel.YAML_TOKEN: str,
HasText.YAML_TOKEN: dict,
token_DATA_TYPE: lambda t: t in VALID_ANSWER_TYPES,
token_MANDATORY: bool,
HasMandatory.YAML_TOKEN: bool,
schema.Optional(token_ANSWER_OPTIONS, default=[]): list,
schema.Optional(str): object, # Catchall for unsupported yaml data
}
......@@ -117,10 +124,10 @@ class Question(YamlConstructable, HasID, HasLabel, HasText):
parent_id=parent_id,
label=label,
translations=text,
is_mandatory=mandatory,
settings=settings
)
self._answer_type = answer_type
self._is_mandatory = mandatory
# Answer options are stored with their short ID as keys for easy
# lookup when associating answers, since answers contain these as
......@@ -174,11 +181,6 @@ class Question(YamlConstructable, HasID, HasLabel, HasText):
If answer options were present, but none of the answer options
had an ID that matched the given value
"""
# TODO this check should be performed when marking invalid answers,
# but must not prevent answers from being included in the first place
# Mandatory questions must have an answer
# if self._is_mandatory and not value:
# raise ValueError("No answer was given, but it was mandatory")
if not value:
# Convert empty strings to None to properly indicate that no
......@@ -225,19 +227,6 @@ class Question(YamlConstructable, HasID, HasLabel, HasText):
if participant_id in self._answers:
del self._answers[participant_id]
@property
def is_mandatory(self) -> bool:
"""
Check whether this question is marked as mandatory.
Mandatory questions are expected to be answered by participants.
Returns:
True, if the question was marked as mandatory in the metadata,
False otherwise
"""
return self._is_mandatory
@property
def answers(self) -> Dict[str, Optional[object]]: # NOTE (0) below
"""
......@@ -308,7 +297,7 @@ class Question(YamlConstructable, HasID, HasLabel, HasText):
label=yaml[HasLabel.YAML_TOKEN],
text=Translated(yaml[HasText.YAML_TOKEN]),
answer_type=answer_type,
mandatory=yaml[Question.token_MANDATORY],
mandatory=yaml[HasMandatory.YAML_TOKEN],
settings=settings
)
......
......@@ -32,7 +32,9 @@ from pandas import DataFrame, Series, concat
from schema import Optional, Schema
from hifis_surveyval.core.settings import Settings
from hifis_surveyval.models.mixins.mixins import HasLabel, HasText, HasID
from hifis_surveyval.models.mixins.mixins import (HasLabel, HasText, HasID,
HasMandatory,
)
from hifis_surveyval.models.mixins.yaml_constructable import (
YamlConstructable,
YamlDict,
......@@ -41,7 +43,13 @@ from hifis_surveyval.models.question import Question
from hifis_surveyval.models.translated import Translated
class QuestionCollection(YamlConstructable, HasID, HasLabel, HasText):
class QuestionCollection(
YamlConstructable,
HasID,
HasLabel,
HasText,
HasMandatory
):
"""
QuestionCollections group a set of questions into a common context.
......@@ -58,6 +66,7 @@ class QuestionCollection(YamlConstructable, HasID, HasLabel, HasText):
HasLabel.YAML_TOKEN: str,
HasText.YAML_TOKEN: dict,
Optional(token_QUESTIONS, default=[]): list,
Optional(HasMandatory.YAML_TOKEN, default=False): bool,
Optional(str): object, # catchall
}
)
......@@ -68,6 +77,7 @@ class QuestionCollection(YamlConstructable, HasID, HasLabel, HasText):
text: Translated,
label: str,
questions: List[Question],
mandatory:bool,
settings: Settings,
) -> None:
"""
......@@ -90,6 +100,10 @@ class QuestionCollection(YamlConstructable, HasID, HasLabel, HasText):
questions:
A list of questions that are contained within the question
collection.
mandatory:
Whether there is an answer to at least one of the contained
questions expected from each participant in oder to consider
the participant's answer data complete.
settings:
The settings used by the framework
"""
......@@ -97,6 +111,7 @@ class QuestionCollection(YamlConstructable, HasID, HasLabel, HasText):
object_id=collection_id,
label=label,
translations=text,
is_mandatory=mandatory,
settings=settings,
)
self._questions: Dict[str, Question] = {
......@@ -211,5 +226,6 @@ class QuestionCollection(YamlConstructable, HasID, HasLabel, HasText):
text=text,
label=yaml[HasLabel.YAML_TOKEN],
questions=questions,
mandatory=yaml[HasMandatory.YAML_TOKEN],
settings=settings
)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment