surveyjs 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- surveyjs/__init__.py +21 -0
- surveyjs/creator.py +165 -0
- surveyjs/form.py +147 -0
- surveyjs/questions/__init__.py +2 -0
- surveyjs/questions/boolean.py +46 -0
- surveyjs/questions/checkbox.py +65 -0
- surveyjs/questions/comment.py +25 -0
- surveyjs/questions/dropdown.py +59 -0
- surveyjs/questions/expression.py +49 -0
- surveyjs/questions/file.py +53 -0
- surveyjs/questions/html.py +21 -0
- surveyjs/questions/image.py +43 -0
- surveyjs/questions/imagepicker.py +40 -0
- surveyjs/questions/matrix.py +61 -0
- surveyjs/questions/matrixdropdown.py +55 -0
- surveyjs/questions/matrixdynamic.py +66 -0
- surveyjs/questions/multipletext.py +44 -0
- surveyjs/questions/nonvalue.py +77 -0
- surveyjs/questions/panel.py +49 -0
- surveyjs/questions/paneldynamic.py +78 -0
- surveyjs/questions/question.py +208 -0
- surveyjs/questions/radiogroup.py +58 -0
- surveyjs/questions/ranking.py +36 -0
- surveyjs/questions/rating.py +65 -0
- surveyjs/questions/signaturepad.py +44 -0
- surveyjs/questions/tagbox.py +52 -0
- surveyjs/questions/text.py +77 -0
- surveyjs-0.1.0.dist-info/METADATA +137 -0
- surveyjs-0.1.0.dist-info/RECORD +32 -0
- surveyjs-0.1.0.dist-info/WHEEL +5 -0
- surveyjs-0.1.0.dist-info/licenses/LICENSE +21 -0
- surveyjs-0.1.0.dist-info/top_level.txt +1 -0
surveyjs/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
"""
|
|
3
|
+
SurveyJS Python Package
|
|
4
|
+
|
|
5
|
+
This module serves as the entry point for the surveyjs package, providing
|
|
6
|
+
access to the core components for working with SurveyJS forms and the
|
|
7
|
+
survey creator.
|
|
8
|
+
|
|
9
|
+
It imports and exposes the following classes:
|
|
10
|
+
- SurveyForm: Handles the rendering and processing of SurveyJS forms.
|
|
11
|
+
- SurveyCreator: Provides functionality for creating and managing surveys.
|
|
12
|
+
|
|
13
|
+
Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
14
|
+
See LICENSE file for full licensing details.
|
|
15
|
+
"""
|
|
16
|
+
# See LICENSE file for full licensing details.
|
|
17
|
+
|
|
18
|
+
from surveyjs.creator import SurveyCreator
|
|
19
|
+
from surveyjs.form import SurveyForm
|
|
20
|
+
|
|
21
|
+
__all__ = ['SurveyCreator', 'SurveyForm']
|
surveyjs/creator.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
|
|
9
|
+
from surveyjs.questions.question import Question
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SurveyCreator:
|
|
15
|
+
"""
|
|
16
|
+
Represents a SurveyJS Creator schema (the survey blueprint/design).
|
|
17
|
+
|
|
18
|
+
Parses the survey JSON schema and creates Question objects for each
|
|
19
|
+
question element found in the schema.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
schema_json,
|
|
25
|
+
language='en',
|
|
26
|
+
i18n=None,
|
|
27
|
+
question_class_mapping={},
|
|
28
|
+
**kwargs
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
@param schema_json: SurveyJS Creator JSON schema (str or dict)
|
|
32
|
+
@param language: Language code for translations (default: 'en')
|
|
33
|
+
@param i18n: Translations dict
|
|
34
|
+
"""
|
|
35
|
+
if isinstance(schema_json, dict):
|
|
36
|
+
self.schema = schema_json
|
|
37
|
+
else:
|
|
38
|
+
self.schema = json.loads(schema_json)
|
|
39
|
+
|
|
40
|
+
self.language = language
|
|
41
|
+
self.i18n = i18n or {}
|
|
42
|
+
|
|
43
|
+
self.question_class_mapping = question_class_mapping
|
|
44
|
+
|
|
45
|
+
# All questions (input + layout) keyed by name
|
|
46
|
+
self.questions = OrderedDict()
|
|
47
|
+
|
|
48
|
+
# Input-only questions keyed by name (no panels, html, image)
|
|
49
|
+
self.input_questions = OrderedDict()
|
|
50
|
+
|
|
51
|
+
# Questions keyed by internal id
|
|
52
|
+
self.question_ids = OrderedDict()
|
|
53
|
+
|
|
54
|
+
# Load all questions from the schema
|
|
55
|
+
self.load_questions()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def title(self):
|
|
59
|
+
return self.schema.get('title', '')
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def description(self):
|
|
63
|
+
return self.schema.get('description', '')
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def pages(self):
|
|
67
|
+
return self.schema.get('pages', [])
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def components(self):
|
|
71
|
+
return self.questions
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def input_components(self):
|
|
75
|
+
return self.input_questions
|
|
76
|
+
|
|
77
|
+
def load_questions(self):
|
|
78
|
+
"""Load questions from the schema, handling both pages-based and
|
|
79
|
+
flat elements-based schemas."""
|
|
80
|
+
pages = self.schema.get('pages')
|
|
81
|
+
if pages:
|
|
82
|
+
for page in pages:
|
|
83
|
+
elements = page.get('elements', [])
|
|
84
|
+
self._load_elements(elements)
|
|
85
|
+
else:
|
|
86
|
+
# Single page: elements at top level
|
|
87
|
+
elements = self.schema.get('elements', [])
|
|
88
|
+
self._load_elements(elements)
|
|
89
|
+
|
|
90
|
+
def _load_elements(self, elements, parent=None):
|
|
91
|
+
"""Recursively load elements from the schema."""
|
|
92
|
+
for element in elements:
|
|
93
|
+
if 'type' not in element:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
question_obj = self.get_question_object(element)
|
|
97
|
+
if not question_obj:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
question_obj.load(
|
|
101
|
+
question_owner=self,
|
|
102
|
+
parent=parent,
|
|
103
|
+
data=None,
|
|
104
|
+
is_form=False,
|
|
105
|
+
)
|
|
106
|
+
self.questions[question_obj.name] = question_obj
|
|
107
|
+
|
|
108
|
+
if question_obj.id:
|
|
109
|
+
self.question_ids[question_obj.id] = question_obj
|
|
110
|
+
|
|
111
|
+
# Recurse into panel/page elements
|
|
112
|
+
if element['type'] in ('panel', 'paneldynamic'):
|
|
113
|
+
nested = element.get('elements', [])
|
|
114
|
+
self._load_elements(nested, parent=question_obj)
|
|
115
|
+
|
|
116
|
+
def get_question_class(self, element):
|
|
117
|
+
"""Dynamically load the question class based on the element type."""
|
|
118
|
+
element_type = element.get('type')
|
|
119
|
+
element_type_cap = element_type.capitalize() if element_type else None
|
|
120
|
+
if not element_type:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
cls_mapping = self.question_class_mapping.get(element_type)
|
|
124
|
+
if cls_mapping:
|
|
125
|
+
cls_mapping = cls_mapping.capitalize() if isinstance(cls_mapping, str) else cls_mapping
|
|
126
|
+
if isinstance(cls_mapping, str):
|
|
127
|
+
cls_name = f"Question{cls_mapping}"
|
|
128
|
+
import_path = f"surveyjs.questions.{element_type}.{cls_mapping}"
|
|
129
|
+
try:
|
|
130
|
+
module = __import__(import_path, fromlist=[cls_name])
|
|
131
|
+
cls = getattr(module, cls_name)
|
|
132
|
+
return cls
|
|
133
|
+
except (AttributeError, ModuleNotFoundError) as e:
|
|
134
|
+
logger.warning(
|
|
135
|
+
"Could not load question class for type '%s': %s. "
|
|
136
|
+
"Falling back to base Question.", element_type, e
|
|
137
|
+
)
|
|
138
|
+
return Question
|
|
139
|
+
else:
|
|
140
|
+
return cls_mapping
|
|
141
|
+
else:
|
|
142
|
+
cls_name = f"Question{element_type_cap}"
|
|
143
|
+
import_path = 'surveyjs.questions.%s' % element_type
|
|
144
|
+
try:
|
|
145
|
+
module = __import__(import_path, fromlist=[cls_name])
|
|
146
|
+
return getattr(module, cls_name)
|
|
147
|
+
except (AttributeError, ModuleNotFoundError) as e:
|
|
148
|
+
logger.warning(
|
|
149
|
+
"Could not load question class for type '%s': %s. "
|
|
150
|
+
"Falling back to base Question.", element_type, e
|
|
151
|
+
)
|
|
152
|
+
return Question
|
|
153
|
+
|
|
154
|
+
def get_question_object(self, element):
|
|
155
|
+
"""Create a question object from an element dict."""
|
|
156
|
+
cls = self.get_question_class(element)
|
|
157
|
+
if cls is None:
|
|
158
|
+
return None
|
|
159
|
+
return cls(element, self, language=self.language, i18n=self.i18n)
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def form(self):
|
|
163
|
+
"""Placeholder form dict (always empty). Useful in contexts where
|
|
164
|
+
a question owner's form is requested."""
|
|
165
|
+
return {}
|
surveyjs/form.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
|
|
8
|
+
from surveyjs import SurveyCreator
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SurveyForm:
|
|
14
|
+
"""
|
|
15
|
+
Represents a filled-in SurveyJS form (submission data).
|
|
16
|
+
|
|
17
|
+
Parses the form submission JSON against a SurveyCreator schema and creates
|
|
18
|
+
Question objects with values populated from the submission.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
form_json,
|
|
24
|
+
creator=None,
|
|
25
|
+
creator_schema_json=None,
|
|
26
|
+
lang="en",
|
|
27
|
+
**kwargs
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
@param form_json: SurveyJS Form submission JSON (str or dict)
|
|
31
|
+
@param creator: A SurveyCreator instance
|
|
32
|
+
@param creator_schema_json: SurveyCreator schema JSON (str or dict)
|
|
33
|
+
Alternative to providing a SurveyCreator instance.
|
|
34
|
+
@param lang: Language code for translations (default: 'en')
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
if creator and creator_schema_json:
|
|
38
|
+
raise Exception(
|
|
39
|
+
"Constructor accepts either creator or creator_schema_json, not both."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if isinstance(form_json, dict):
|
|
43
|
+
self.form = form_json
|
|
44
|
+
else:
|
|
45
|
+
self.form = json.loads(form_json)
|
|
46
|
+
|
|
47
|
+
self.creator = creator
|
|
48
|
+
self.creator_schema_json = creator_schema_json
|
|
49
|
+
|
|
50
|
+
if self.creator is None and self.creator_schema_json:
|
|
51
|
+
self.creator = SurveyCreator(self.creator_schema_json)
|
|
52
|
+
|
|
53
|
+
if self.creator:
|
|
54
|
+
if not isinstance(self.creator, SurveyCreator):
|
|
55
|
+
raise TypeError("creator must be a SurveyCreator instance")
|
|
56
|
+
elif self.creator_schema_json:
|
|
57
|
+
assert isinstance(self.creator_schema_json, str)
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
raise Exception(
|
|
61
|
+
"Provide either the argument: creator or creator_schema_json."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
self.lang = lang
|
|
65
|
+
# defaults to English (en) date/time format
|
|
66
|
+
self.date_format = kwargs.get('date_format', '%m/%d/%Y')
|
|
67
|
+
self.time_format = kwargs.get('time_format', '%H:%M:%S')
|
|
68
|
+
|
|
69
|
+
# All questions (input + layout) keyed by name
|
|
70
|
+
self.questions = OrderedDict()
|
|
71
|
+
|
|
72
|
+
# Input-only questions keyed by name
|
|
73
|
+
self.input_questions = OrderedDict()
|
|
74
|
+
|
|
75
|
+
# Questions keyed by id
|
|
76
|
+
self.question_ids = {}
|
|
77
|
+
|
|
78
|
+
# Load questions from survey schema + populate values from form data
|
|
79
|
+
self.load_questions()
|
|
80
|
+
|
|
81
|
+
# Create attribute-style accessor
|
|
82
|
+
self._data = FormData(self)
|
|
83
|
+
|
|
84
|
+
def set_creator_by_creator_schema_json(self):
|
|
85
|
+
self.creator = SurveyCreator(
|
|
86
|
+
self.creator_schema_json,
|
|
87
|
+
language=self.lang,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def data(self):
|
|
92
|
+
"""Attribute-style access to input questions."""
|
|
93
|
+
return self._data
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def components(self):
|
|
97
|
+
""" Alias for questions, including both input and layout questions."""
|
|
98
|
+
return self.questions
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def input_components(self):
|
|
102
|
+
""" Alias for questions, including input queastions."""
|
|
103
|
+
return self.input_questions
|
|
104
|
+
|
|
105
|
+
def load_questions(self):
|
|
106
|
+
"""Load questions from the survey schema and populate values from
|
|
107
|
+
form submission data."""
|
|
108
|
+
for key, creator_question in self.creator.questions.items():
|
|
109
|
+
# Create a new question object (don't affect the Survey's question)
|
|
110
|
+
question_obj = self.creator.get_question_object(creator_question.raw)
|
|
111
|
+
question_obj.load(
|
|
112
|
+
question_owner=self,
|
|
113
|
+
parent=None,
|
|
114
|
+
data=self.form,
|
|
115
|
+
is_form=True,
|
|
116
|
+
)
|
|
117
|
+
self.questions[key] = question_obj
|
|
118
|
+
|
|
119
|
+
if question_obj.id:
|
|
120
|
+
self.question_ids[question_obj.id] = question_obj
|
|
121
|
+
|
|
122
|
+
def get_question_by_name(self, name):
|
|
123
|
+
"""Get a question by its name."""
|
|
124
|
+
return self.input_questions.get(name)
|
|
125
|
+
|
|
126
|
+
def get_value(self, name):
|
|
127
|
+
"""Get the value of a question by name."""
|
|
128
|
+
question = self.input_questions.get(name)
|
|
129
|
+
if question:
|
|
130
|
+
return question.value
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class FormData:
|
|
135
|
+
"""Provides attribute-style access to form input questions.
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
form.data.firstName.value # => 'Bob'
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(self, form):
|
|
142
|
+
self._form = form
|
|
143
|
+
|
|
144
|
+
def __getattr__(self, key):
|
|
145
|
+
if key.startswith('_'):
|
|
146
|
+
return super().__getattribute__(key)
|
|
147
|
+
return self._form.input_questions.get(key)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionBoolean(Question):
|
|
8
|
+
"""SurveyJS Boolean (Yes/No) question.
|
|
9
|
+
|
|
10
|
+
Value is typically True/False.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def label_true(self):
|
|
15
|
+
return self.raw.get('labelTrue', 'Yes')
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def label_false(self):
|
|
19
|
+
return self.raw.get('labelFalse', 'No')
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def value_true(self):
|
|
23
|
+
return self.raw.get('valueTrue', True)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def value_false(self):
|
|
27
|
+
return self.raw.get('valueFalse', False)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def render_as(self):
|
|
31
|
+
"""Render mode: 'default' (toggle) or 'checkbox' or 'radio'."""
|
|
32
|
+
return self.raw.get('renderAs', 'default')
|
|
33
|
+
|
|
34
|
+
def to_bool(self):
|
|
35
|
+
"""Convert the value to a Python bool."""
|
|
36
|
+
val = self.value
|
|
37
|
+
if val is None:
|
|
38
|
+
return None
|
|
39
|
+
if isinstance(val, bool):
|
|
40
|
+
return val
|
|
41
|
+
if val == self.value_true:
|
|
42
|
+
return True
|
|
43
|
+
if val == self.value_false:
|
|
44
|
+
return False
|
|
45
|
+
# Fallback
|
|
46
|
+
return bool(val)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionCheckbox(Question):
|
|
8
|
+
"""SurveyJS Checkbox (multiple selection) question.
|
|
9
|
+
|
|
10
|
+
Value is typically a list of selected choice values.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def choices(self):
|
|
15
|
+
return self.raw.get('choices', [])
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def choices_values(self):
|
|
19
|
+
"""Get normalized choice values as a list of dicts."""
|
|
20
|
+
result = []
|
|
21
|
+
for choice in self.choices:
|
|
22
|
+
if isinstance(choice, dict):
|
|
23
|
+
result.append({
|
|
24
|
+
'value': choice.get('value'),
|
|
25
|
+
'text': choice.get('text', str(choice.get('value', '')))
|
|
26
|
+
})
|
|
27
|
+
else:
|
|
28
|
+
result.append({'value': choice, 'text': str(choice)})
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def value_texts(self):
|
|
33
|
+
"""Get the display texts for the currently selected values."""
|
|
34
|
+
val = self.value
|
|
35
|
+
if not val or not isinstance(val, list):
|
|
36
|
+
return []
|
|
37
|
+
texts = []
|
|
38
|
+
choice_map = {c['value']: c['text'] for c in self.choices_values}
|
|
39
|
+
for v in val:
|
|
40
|
+
texts.append(choice_map.get(v, str(v)))
|
|
41
|
+
return texts
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def has_other(self):
|
|
45
|
+
return self.raw.get('showOtherItem', self.raw.get('hasOther', False))
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def has_none(self):
|
|
49
|
+
return self.raw.get('showNoneItem', self.raw.get('hasNone', False))
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def has_select_all(self):
|
|
53
|
+
return self.raw.get('showSelectAllItem', self.raw.get('hasSelectAll', False))
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def max_selected_choices(self):
|
|
57
|
+
return self.raw.get('maxSelectedChoices', 0)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def min_selected_choices(self):
|
|
61
|
+
return self.raw.get('minSelectedChoices', 0)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def col_count(self):
|
|
65
|
+
return self.raw.get('colCount', 0)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionComment(Question):
|
|
8
|
+
"""SurveyJS Comment (Long Text / Multi-line) question."""
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def max_length(self):
|
|
12
|
+
return self.raw.get('maxLength', 0)
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def rows(self):
|
|
16
|
+
"""Number of visible rows in the text area."""
|
|
17
|
+
return self.raw.get('rows', 4)
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def auto_grow(self):
|
|
21
|
+
return self.raw.get('autoGrow', False)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def allow_resize(self):
|
|
25
|
+
return self.raw.get('allowResize', True)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionDropdown(Question):
|
|
8
|
+
"""SurveyJS Dropdown (single-select) question."""
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def choices(self):
|
|
12
|
+
return self.raw.get('choices', [])
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def choices_values(self):
|
|
16
|
+
result = []
|
|
17
|
+
for choice in self.choices:
|
|
18
|
+
if isinstance(choice, dict):
|
|
19
|
+
result.append({
|
|
20
|
+
'value': choice.get('value'),
|
|
21
|
+
'text': choice.get('text', str(choice.get('value', '')))
|
|
22
|
+
})
|
|
23
|
+
else:
|
|
24
|
+
result.append({'value': choice, 'text': str(choice)})
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def value_text(self):
|
|
29
|
+
"""Get the display text for the current value."""
|
|
30
|
+
val = self.value
|
|
31
|
+
for choice in self.choices_values:
|
|
32
|
+
if choice['value'] == val:
|
|
33
|
+
return choice['text']
|
|
34
|
+
return str(val) if val is not None else None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def has_other(self):
|
|
38
|
+
return self.raw.get('showOtherItem', self.raw.get('hasOther', False))
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def other_text(self):
|
|
42
|
+
return self.raw.get('otherText', 'Other')
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def choices_by_url(self):
|
|
46
|
+
"""Get choices loaded from a URL configuration."""
|
|
47
|
+
return self.raw.get('choicesByUrl', None)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def search_enabled(self):
|
|
51
|
+
return self.raw.get('searchEnabled', True)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def placeholder_text(self):
|
|
55
|
+
return self.raw.get('placeholder', '')
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def allow_clear(self):
|
|
59
|
+
return self.raw.get('allowClear', True)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionExpression(Question):
|
|
8
|
+
"""SurveyJS Expression question.
|
|
9
|
+
|
|
10
|
+
A read-only calculated field. Value is computed from an expression.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def expression(self):
|
|
15
|
+
"""The calculation expression."""
|
|
16
|
+
return self.raw.get('expression', '')
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def display_style(self):
|
|
20
|
+
"""'none', 'decimal', 'currency', or 'percent'."""
|
|
21
|
+
return self.raw.get('displayStyle', 'none')
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def currency(self):
|
|
25
|
+
return self.raw.get('currency', 'USD')
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def maximum_fraction_digits(self):
|
|
29
|
+
return self.raw.get('maximumFractionDigits', -1)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def minimum_fraction_digits(self):
|
|
33
|
+
return self.raw.get('minimumFractionDigits', -1)
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def use_grouping(self):
|
|
37
|
+
return self.raw.get('useGrouping', True)
|
|
38
|
+
|
|
39
|
+
def to_number(self):
|
|
40
|
+
"""Try to convert the expression value to a number."""
|
|
41
|
+
val = self.value
|
|
42
|
+
if val is None:
|
|
43
|
+
return None
|
|
44
|
+
try:
|
|
45
|
+
if isinstance(val, (int, float)):
|
|
46
|
+
return val
|
|
47
|
+
return float(val)
|
|
48
|
+
except (ValueError, TypeError):
|
|
49
|
+
return None
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionFile(Question):
|
|
8
|
+
"""SurveyJS File Upload question.
|
|
9
|
+
|
|
10
|
+
Value is typically a list of file metadata objects or base64 data.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def store_data_as_text(self):
|
|
15
|
+
"""Whether files are stored as base64 text."""
|
|
16
|
+
return self.raw.get('storeDataAsText', True)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def allow_multiple(self):
|
|
20
|
+
return self.raw.get('allowMultiple', False)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def max_size(self):
|
|
24
|
+
"""Maximum file size in bytes (0 = unlimited)."""
|
|
25
|
+
return self.raw.get('maxSize', 0)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def accepted_types(self):
|
|
29
|
+
"""Accepted file types (MIME types or extensions)."""
|
|
30
|
+
return self.raw.get('acceptedTypes', '')
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def allow_camera_access(self):
|
|
34
|
+
return self.raw.get('allowCameraAccess', False)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def source_type(self):
|
|
38
|
+
return self.raw.get('sourceType', 'file')
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def files(self):
|
|
42
|
+
"""Get the list of file objects from the value."""
|
|
43
|
+
val = self.value
|
|
44
|
+
if val is None:
|
|
45
|
+
return []
|
|
46
|
+
if isinstance(val, list):
|
|
47
|
+
return val
|
|
48
|
+
return [val]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def file_count(self):
|
|
52
|
+
"""Number of uploaded files."""
|
|
53
|
+
return len(self.files)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright 2026 Nova Code (https://www.novacode.nl)
|
|
2
|
+
# See LICENSE file for full licensing details.
|
|
3
|
+
|
|
4
|
+
from .question import Question
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class QuestionHtml(Question):
|
|
8
|
+
"""SurveyJS HTML question.
|
|
9
|
+
|
|
10
|
+
Displays static HTML content. Not an input element.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def is_input(self):
|
|
15
|
+
"""HTML elements are display-only, not input questions."""
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def html(self):
|
|
20
|
+
"""Get the HTML content."""
|
|
21
|
+
return self.raw.get('html', '')
|