surveyjs 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- surveyjs-0.1.0/PKG-INFO +137 -0
- surveyjs-0.1.0/README.md +118 -0
- surveyjs-0.1.0/pyproject.toml +33 -0
- surveyjs-0.1.0/setup.cfg +4 -0
- surveyjs-0.1.0/surveyjs/__init__.py +21 -0
- surveyjs-0.1.0/surveyjs/creator.py +165 -0
- surveyjs-0.1.0/surveyjs/form.py +147 -0
- surveyjs-0.1.0/surveyjs/questions/__init__.py +2 -0
- surveyjs-0.1.0/surveyjs/questions/boolean.py +46 -0
- surveyjs-0.1.0/surveyjs/questions/checkbox.py +65 -0
- surveyjs-0.1.0/surveyjs/questions/comment.py +25 -0
- surveyjs-0.1.0/surveyjs/questions/dropdown.py +59 -0
- surveyjs-0.1.0/surveyjs/questions/expression.py +49 -0
- surveyjs-0.1.0/surveyjs/questions/file.py +53 -0
- surveyjs-0.1.0/surveyjs/questions/html.py +21 -0
- surveyjs-0.1.0/surveyjs/questions/image.py +43 -0
- surveyjs-0.1.0/surveyjs/questions/imagepicker.py +40 -0
- surveyjs-0.1.0/surveyjs/questions/matrix.py +61 -0
- surveyjs-0.1.0/surveyjs/questions/matrixdropdown.py +55 -0
- surveyjs-0.1.0/surveyjs/questions/matrixdynamic.py +66 -0
- surveyjs-0.1.0/surveyjs/questions/multipletext.py +44 -0
- surveyjs-0.1.0/surveyjs/questions/nonvalue.py +77 -0
- surveyjs-0.1.0/surveyjs/questions/panel.py +49 -0
- surveyjs-0.1.0/surveyjs/questions/paneldynamic.py +78 -0
- surveyjs-0.1.0/surveyjs/questions/question.py +208 -0
- surveyjs-0.1.0/surveyjs/questions/radiogroup.py +58 -0
- surveyjs-0.1.0/surveyjs/questions/ranking.py +36 -0
- surveyjs-0.1.0/surveyjs/questions/rating.py +65 -0
- surveyjs-0.1.0/surveyjs/questions/signaturepad.py +44 -0
- surveyjs-0.1.0/surveyjs/questions/tagbox.py +52 -0
- surveyjs-0.1.0/surveyjs/questions/text.py +77 -0
- surveyjs-0.1.0/surveyjs.egg-info/PKG-INFO +137 -0
- surveyjs-0.1.0/surveyjs.egg-info/SOURCES.txt +58 -0
- surveyjs-0.1.0/surveyjs.egg-info/dependency_links.txt +1 -0
- surveyjs-0.1.0/surveyjs.egg-info/top_level.txt +1 -0
- surveyjs-0.1.0/tests/test_form.py +97 -0
- surveyjs-0.1.0/tests/test_nested_questions.py +17 -0
- surveyjs-0.1.0/tests/test_question_boolean.py +57 -0
- surveyjs-0.1.0/tests/test_question_checkbox.py +79 -0
- surveyjs-0.1.0/tests/test_question_comment.py +60 -0
- surveyjs-0.1.0/tests/test_question_dropdown.py +60 -0
- surveyjs-0.1.0/tests/test_question_expression.py +48 -0
- surveyjs-0.1.0/tests/test_question_file.py +69 -0
- surveyjs-0.1.0/tests/test_question_html.py +48 -0
- surveyjs-0.1.0/tests/test_question_image.py +62 -0
- surveyjs-0.1.0/tests/test_question_imagepicker.py +61 -0
- surveyjs-0.1.0/tests/test_question_matrix.py +69 -0
- surveyjs-0.1.0/tests/test_question_matrixdropdown.py +72 -0
- surveyjs-0.1.0/tests/test_question_matrixdynamic.py +75 -0
- surveyjs-0.1.0/tests/test_question_multipletext.py +68 -0
- surveyjs-0.1.0/tests/test_question_panel.py +68 -0
- surveyjs-0.1.0/tests/test_question_paneldynamic.py +95 -0
- surveyjs-0.1.0/tests/test_question_radiogroup.py +66 -0
- surveyjs-0.1.0/tests/test_question_ranking.py +66 -0
- surveyjs-0.1.0/tests/test_question_rating.py +63 -0
- surveyjs-0.1.0/tests/test_question_signaturepad.py +60 -0
- surveyjs-0.1.0/tests/test_question_tagbox.py +69 -0
- surveyjs-0.1.0/tests/test_question_text.py +123 -0
- surveyjs-0.1.0/tests/test_survey.py +88 -0
surveyjs-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nova Code (https://www.novacode.nl)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
surveyjs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: surveyjs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SurveyJS (JSON data) API for Python
|
|
5
|
+
Author-email: Bob Leers <bob@novacode.nl>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/novacode-nl/python-surveyjs
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Classifier: Topic :: File Formats :: JSON
|
|
14
|
+
Classifier: Topic :: File Formats :: JSON :: JSON Schema
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# surveyjs (Python)
|
|
21
|
+
|
|
22
|
+
SurveyJS (JSON Form Builder) data API for Python.
|
|
23
|
+
|
|
24
|
+
For information about the SurveyJS project, see https://surveyjs.io
|
|
25
|
+
|
|
26
|
+
## Introduction
|
|
27
|
+
|
|
28
|
+
**surveyjs** is a Python package which loads and transforms
|
|
29
|
+
SurveyJS **Creator JSON** (survey schema) and **Form JSON** (submission data)
|
|
30
|
+
into **usable Python objects**.
|
|
31
|
+
|
|
32
|
+
Its main aim is to provide easy access to a SurveyJS Form's questions (fields, layout elements, etc.)
|
|
33
|
+
also captured as **Python objects**, which makes this API very versatile and usable.
|
|
34
|
+
|
|
35
|
+
**Notes about terms:**
|
|
36
|
+
- **SurveyCreator:** The Survey Creator (form builder) schema which is the design of a Form.
|
|
37
|
+
- **SurveyForm:** A filled-in Survey Form, aka Form response, submission.
|
|
38
|
+
- **Question:** Input (field) and layout elements in SurveyJS (Creator and Form). Question is not a semantic term for a layout element (e.g. panel), but we follow the SurveyJS convention of calling all components "questions".
|
|
39
|
+
|
|
40
|
+
**Question types:**
|
|
41
|
+
Source code (file prefix `question`): https://github.com/surveyjs/survey-library/tree/master/packages/survey-core/src
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- Compatible with Python 3.8 and later
|
|
46
|
+
- Constructor of the **SurveyCreator** and **SurveyForm** class only requires
|
|
47
|
+
the JSON (string or dict).
|
|
48
|
+
- Get a SurveyForm object's Questions as usable Python objects
|
|
49
|
+
e.g. datetime, boolean, list (for checkbox), dict (for matrix) etc.
|
|
50
|
+
- Support for all SurveyJS question types
|
|
51
|
+
- Open source (MIT License)
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
pip install surveyjs
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Source Install
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
git clone <repo-url>
|
|
63
|
+
cd python-surveyjs
|
|
64
|
+
pip install -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage Examples
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from surveyjs import SurveyCreator, SurveyForm
|
|
71
|
+
|
|
72
|
+
# survey_json is a SurveyJS Creator JSON schema (string or dict)
|
|
73
|
+
# form_json is a SurveyJS Form submission JSON (string or dict)
|
|
74
|
+
|
|
75
|
+
creator = SurveyCreator(survey_json)
|
|
76
|
+
form = SurveyForm(form_json, creator)
|
|
77
|
+
|
|
78
|
+
# Text question
|
|
79
|
+
print(form.input_questions['firstName'].label)
|
|
80
|
+
# 'First Name'
|
|
81
|
+
|
|
82
|
+
print(form.input_questions['firstName'].value)
|
|
83
|
+
# 'Bob'
|
|
84
|
+
|
|
85
|
+
# Checkbox question
|
|
86
|
+
print(form.input_questions['colors'].value)
|
|
87
|
+
# ['red', 'blue']
|
|
88
|
+
|
|
89
|
+
# Rating question
|
|
90
|
+
print(form.input_questions['satisfaction'].value)
|
|
91
|
+
# 4
|
|
92
|
+
|
|
93
|
+
# Boolean question
|
|
94
|
+
print(form.input_questions['agree'].value)
|
|
95
|
+
# True
|
|
96
|
+
|
|
97
|
+
# Matrix question
|
|
98
|
+
print(form.input_questions['quality'].value)
|
|
99
|
+
# {'affordable': 'good', 'does-what-it-claims': 'excellent'}
|
|
100
|
+
|
|
101
|
+
# Access by attribute
|
|
102
|
+
print(form.data.firstName.value)
|
|
103
|
+
# 'Bob'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Unit Tests
|
|
107
|
+
|
|
108
|
+
### Run all tests
|
|
109
|
+
|
|
110
|
+
From toplevel directory:
|
|
111
|
+
```
|
|
112
|
+
poetry run python -m unittest
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Run specific (questions) unittests
|
|
116
|
+
|
|
117
|
+
All questions, from toplevel directory:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
poetry run python -m unittest tests/test_question_*.py
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Nested questions (complexity), from toplevel directory:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
poetry run python -m unittest tests/test_nested_questions.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Run specific component unittest
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
poetry run python -m unittest tests.test_question_ranking.TestQuestionRanking.test_choices
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
[MIT](LICENSE)
|
surveyjs-0.1.0/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# surveyjs (Python)
|
|
2
|
+
|
|
3
|
+
SurveyJS (JSON Form Builder) data API for Python.
|
|
4
|
+
|
|
5
|
+
For information about the SurveyJS project, see https://surveyjs.io
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
**surveyjs** is a Python package which loads and transforms
|
|
10
|
+
SurveyJS **Creator JSON** (survey schema) and **Form JSON** (submission data)
|
|
11
|
+
into **usable Python objects**.
|
|
12
|
+
|
|
13
|
+
Its main aim is to provide easy access to a SurveyJS Form's questions (fields, layout elements, etc.)
|
|
14
|
+
also captured as **Python objects**, which makes this API very versatile and usable.
|
|
15
|
+
|
|
16
|
+
**Notes about terms:**
|
|
17
|
+
- **SurveyCreator:** The Survey Creator (form builder) schema which is the design of a Form.
|
|
18
|
+
- **SurveyForm:** A filled-in Survey Form, aka Form response, submission.
|
|
19
|
+
- **Question:** Input (field) and layout elements in SurveyJS (Creator and Form). Question is not a semantic term for a layout element (e.g. panel), but we follow the SurveyJS convention of calling all components "questions".
|
|
20
|
+
|
|
21
|
+
**Question types:**
|
|
22
|
+
Source code (file prefix `question`): https://github.com/surveyjs/survey-library/tree/master/packages/survey-core/src
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- Compatible with Python 3.8 and later
|
|
27
|
+
- Constructor of the **SurveyCreator** and **SurveyForm** class only requires
|
|
28
|
+
the JSON (string or dict).
|
|
29
|
+
- Get a SurveyForm object's Questions as usable Python objects
|
|
30
|
+
e.g. datetime, boolean, list (for checkbox), dict (for matrix) etc.
|
|
31
|
+
- Support for all SurveyJS question types
|
|
32
|
+
- Open source (MIT License)
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
pip install surveyjs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Source Install
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
git clone <repo-url>
|
|
44
|
+
cd python-surveyjs
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage Examples
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from surveyjs import SurveyCreator, SurveyForm
|
|
52
|
+
|
|
53
|
+
# survey_json is a SurveyJS Creator JSON schema (string or dict)
|
|
54
|
+
# form_json is a SurveyJS Form submission JSON (string or dict)
|
|
55
|
+
|
|
56
|
+
creator = SurveyCreator(survey_json)
|
|
57
|
+
form = SurveyForm(form_json, creator)
|
|
58
|
+
|
|
59
|
+
# Text question
|
|
60
|
+
print(form.input_questions['firstName'].label)
|
|
61
|
+
# 'First Name'
|
|
62
|
+
|
|
63
|
+
print(form.input_questions['firstName'].value)
|
|
64
|
+
# 'Bob'
|
|
65
|
+
|
|
66
|
+
# Checkbox question
|
|
67
|
+
print(form.input_questions['colors'].value)
|
|
68
|
+
# ['red', 'blue']
|
|
69
|
+
|
|
70
|
+
# Rating question
|
|
71
|
+
print(form.input_questions['satisfaction'].value)
|
|
72
|
+
# 4
|
|
73
|
+
|
|
74
|
+
# Boolean question
|
|
75
|
+
print(form.input_questions['agree'].value)
|
|
76
|
+
# True
|
|
77
|
+
|
|
78
|
+
# Matrix question
|
|
79
|
+
print(form.input_questions['quality'].value)
|
|
80
|
+
# {'affordable': 'good', 'does-what-it-claims': 'excellent'}
|
|
81
|
+
|
|
82
|
+
# Access by attribute
|
|
83
|
+
print(form.data.firstName.value)
|
|
84
|
+
# 'Bob'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Unit Tests
|
|
88
|
+
|
|
89
|
+
### Run all tests
|
|
90
|
+
|
|
91
|
+
From toplevel directory:
|
|
92
|
+
```
|
|
93
|
+
poetry run python -m unittest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Run specific (questions) unittests
|
|
97
|
+
|
|
98
|
+
All questions, from toplevel directory:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
poetry run python -m unittest tests/test_question_*.py
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Nested questions (complexity), from toplevel directory:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
poetry run python -m unittest tests/test_nested_questions.py
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Run specific component unittest
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
poetry run python -m unittest tests.test_question_ranking.TestQuestionRanking.test_choices
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "surveyjs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SurveyJS (JSON data) API for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Bob Leers", email = "bob@novacode.nl"}
|
|
14
|
+
]
|
|
15
|
+
dependencies = []
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Topic :: File Formats :: JSON",
|
|
23
|
+
"Topic :: File Formats :: JSON :: JSON Schema"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/novacode-nl/python-surveyjs"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
include = ["surveyjs*"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
surveyjs-0.1.0/setup.cfg
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']
|
|
@@ -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 {}
|
|
@@ -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)
|