├── .envrc ├── .github └── workflows │ └── unittests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── formiodata ├── builder.py ├── components │ ├── __init__.py │ ├── address.py │ ├── button.py │ ├── checkbox.py │ ├── columns.py │ ├── component.py │ ├── content.py │ ├── currency.py │ ├── datagrid.py │ ├── datetime.py │ ├── day.py │ ├── editgrid.py │ ├── email.py │ ├── fieldset.py │ ├── file.py │ ├── grid_base.py │ ├── htmlelement.py │ ├── layout_base.py │ ├── number.py │ ├── panel.py │ ├── password.py │ ├── phoneNumber.py │ ├── phone_number.py │ ├── radio.py │ ├── resource.py │ ├── select.py │ ├── selectboxes.py │ ├── signature.py │ ├── survey.py │ ├── table.py │ ├── tabs.py │ ├── textarea.py │ ├── textfield.py │ ├── time.py │ └── url.py ├── form.py └── utils.py ├── nix └── pkgs.nix ├── pyproject.toml ├── shell.nix └── tests ├── __init__.py ├── data ├── test_conditional_visibility_json_logic_builder.json ├── test_conditional_visibility_json_logic_hide_secret.json ├── test_conditional_visibility_json_logic_show_secret.json ├── test_conditional_visibility_nested_json_logic_builder.json ├── test_conditional_visibility_nested_json_logic_hide_global_secret_only.json ├── test_conditional_visibility_nested_json_logic_hide_secret.json ├── test_conditional_visibility_nested_json_logic_show_global_secret_only.json ├── test_conditional_visibility_nested_json_logic_show_secret.json ├── test_conditional_visibility_simple_builder.json ├── test_conditional_visibility_simple_hide_password.json ├── test_conditional_visibility_simple_show_selectboxes.json ├── test_conditional_visibility_simple_show_textfield.json ├── test_datagrid_in_panel_builder.json ├── test_datagrid_in_panel_one_row_form.json ├── test_example_builder.json ├── test_example_builder_with_resource.json ├── test_example_form.json ├── test_example_form_check_default.json ├── test_example_form_empty.json ├── test_example_form_validation_errors.json ├── test_example_form_with_resource.json ├── test_nested_components_builder.json ├── test_nested_components_form.json └── test_resources_submission.json ├── test_builder.py ├── test_common.py ├── test_component.py ├── test_component_address.py ├── test_component_class_mapping.py ├── test_component_datagrid.py ├── test_component_datetime.py ├── test_component_day.py ├── test_component_editgrid.py ├── test_component_email.py ├── test_component_file_storage_base64.py ├── test_component_file_storage_url.py ├── test_component_number.py ├── test_component_panel.py ├── test_component_phoneNumber.py ├── test_component_radio.py ├── test_component_resource.py ├── test_component_select_multiple.py ├── test_component_select_one.py ├── test_component_selectboxes.py ├── test_component_survey.py ├── test_component_table.py ├── test_component_tabs.py ├── test_component_textfield.py ├── test_conditional_visibility_json_logic.py ├── test_conditional_visibility_nested_json_logic.py ├── test_conditional_visibility_simple.py ├── test_datagrid_in_panel.py ├── test_default_value_component.py ├── test_nested_components.py ├── test_performance_nested_components.py ├── test_submission.py ├── test_validation_error_simple.py ├── test_view_render_component.py └── utils.py /.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | -------------------------------------------------------------------------------- /.github/workflows/unittests.yml: -------------------------------------------------------------------------------- 1 | name: Test Python-formio-data 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | test-without-json-logic: 10 | name: Basic unit tests 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Check out the repo 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up Nix with direnv support 19 | uses: aldoborrero/use-nix-action@v2 20 | with: 21 | nix_path: nixpkgs=channel:nixos-23.05 22 | 23 | - name: Install basic dependencies via poetry 24 | run: poetry install 25 | 26 | - name: Run the tests 27 | run: poetry run python -m unittest 28 | 29 | 30 | test-with-json-logic: 31 | name: Unit tests with json_logic 32 | 33 | runs-on: ubuntu-latest 34 | 35 | steps: 36 | - name: Check out the repo 37 | uses: actions/checkout@v3 38 | 39 | - name: Set up Nix with direnv support 40 | uses: aldoborrero/use-nix-action@v2 41 | with: 42 | nix_path: nixpkgs=channel:nixos-23.05 43 | 44 | - name: Install dependencies via poetry, including json_logic 45 | run: poetry install -E json_logic 46 | 47 | - name: Run the tests 48 | run: poetry run python -m unittest 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.py[co] 3 | __pycache__ 4 | env 5 | dist/ 6 | build/ 7 | tmp 8 | .direnv 9 | .idea 10 | poetry.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.1.0 4 | 5 | Fix `value_label` property (getter) in `selectComponent`, if no value is set. 6 | 7 | **WARNING - Backward incompatibility**\ 8 | This also changes `value_label` return value from `False` to `None` if no value is set.\ 9 | So check whether to update any usage (code).\ 10 | However no need when `None` is used in comparisons (`if` etc), which converts to boolean `False`. 11 | 12 | ## 2.0.3 13 | 14 | Add `dataSrc` property (getter) in `selectboxesComponent`. 15 | 16 | ## 2.0.2 17 | 18 | Improve the `selectComponent` class to support the property Data Source Type (`dataSrc`) with URL.\ 19 | This applies to the methods `value_label` and `value_labels`, which use the stored label(s).\ 20 | So this doesn't execute a request to the URL, because we can't take any control (e.g. due to authentication) over the host/server/service, 21 | 22 | ## 2.0.1 23 | 24 | Improve the `Component` class `conditional_visible_when` method, to also obtain a Dictionary for the value of triggering component.\ 25 | Remove the now redundant `conditional_visible_when` method from the `selectboxesComponent`, because a generic implementation is now in the `Component` class. 26 | 27 | ## 2.0.0 28 | 29 | Fix `datetimeComponent` which is always stored as ISO with combined date, time and timezone.\ 30 | The previous implementation was wrong and now backwards imcompatible.\ 31 | 32 | **Warning**: 33 | 34 | Test all `datetimeComponent` setters and their impact on implementations. 35 | 36 | ## 1.2.7 37 | 38 | Fix ValueError in `datetimeComponent` value setter exception handler:\ 39 | `ValueError: : "fromisoformat: argument must be str" while evaluating` 40 | 41 | ## 1.2.6 42 | 43 | - Fix `datetimeComponent` value setter, to properly parse when the 44 | `enableTime` property is `False`.\ 45 | This fixes a bug/regression in version 1.2.3. 46 | - Update README concerning the datetime component value. 47 | 48 | ## 1.2.5 49 | 50 | Improve the load methods for components and `gridRow`, by passing 51 | whether it applies to a Form `is_form`, otherwise it's obtained as a 52 | Builder. 53 | 54 | Ensure an empty form `gridRow` doesn't appear in a grid's `rows` 55 | property, made possible by the other `is_form` change. 56 | 57 | ## 1.2.4 58 | 59 | Implementation of "simple" validation required. 60 | 61 | For a Form object the validation errors can be retrieved by the new 62 | `validation_errors()` method. 63 | 64 | The new component method `validation_errors()` can be extended and 65 | returns either a dictionary or a list (for grid components) with the 66 | validation errors. 67 | 68 | ## 1.2.3 69 | 70 | Improve the `datetimeComponent` value setter, to properly parse a date 71 | with a custom format, when the `enableTime` (new property) is `False`. 72 | 73 | Provide the `component_class_mapping` (interface) in the keyword arguments of the Form (class) instantiation. 74 | 75 | ## 1.2.2 76 | 77 | Refactored the `Component` class `conditionally_visible` method, to 78 | call the following 2 methods which can be extended in component 79 | subclasses: 80 | - `conditional_visible_json_when` 81 | - `conditional_visible_json_logic` 82 | 83 | Implemented the `conditional_visible_json_when` method for the `selectboxesComponent`.\ 84 | Extended the unittest `ConditionalVisibilitySimpleTestCase` with simple conditional visibility for the `selectboxesComponent`. 85 | 86 | ## 1.2.1 87 | 88 | Fix `get_component_object` (Builder) method to handle `ModuleNotFoundError`.\ 89 | Therefor implemented the `get_component_class` method to determine the class with a fallback to the base `Component` class. 90 | 91 | ## 1.2.0 92 | 93 | New "component class mapping feature" for the Builder instantiation:\ 94 | Map a custom component type to an implemented component class, which is then loaded. 95 | 96 | An example is available in the unittests of file: `tests/test_component_class_mapping.py` 97 | 98 | Also refactored the Builder constructor, from some `kwargs` to keyword arguments. 99 | 100 | ## 1.1.0 101 | 102 | Put component classes as files in the new `components` directory.\ 103 | Change the instantiation of a component in the `get_component_object` method of the `Builder` class. 104 | 105 | **Warning**: 106 | 107 | This changes the `import` declaration (path) of how components should be imported. 108 | 109 | **Old style import:**: 110 | 111 | ```python 112 | from formiodata.components import textfieldComponent 113 | ``` 114 | 115 | **New style import:** 116 | 117 | ```python 118 | from formiodata.components.textfield import textfieldComponent 119 | ``` 120 | 121 | Also some additional minor improvements, e.g. remove unused imports and newlines. 122 | 123 | ## 1.0.5 124 | 125 | Add Component properties: 126 | - `tableView`: Display // Table View 127 | - `disabled`: Display // Disabled 128 | 129 | ## 1.0.4 130 | 131 | Add Component properties: 132 | - `conditional`: Conditional // Simple Conditional 133 | - `custom_conditional`: Conditional // Custom Conditional 134 | - `templates`: Templates (eg templates for layout and (data) grids.) 135 | - `logic`: Logic (trigger/action pairs). 136 | 137 | ## 1.0.3 138 | 139 | Add the country_code property in the addressComponent. 140 | 141 | ## 1.0.2 142 | 143 | Refactor builder component path properties, to store objects, with key and label getters. 144 | 145 | ## 1.0.1 146 | 147 | Fix error in `get_component_object` (`builder.py`) => `NameError: name 'logging is not defined'`\ 148 | Accidentally removed the `import logging` in previous version 1.0.0 149 | 150 | ## 1.0.0 151 | 152 | Implement builder component path properties (keys, labels). 153 | 154 | `builder_path_key` 155 | List of complete path components with keys. This includes layout components. 156 | 157 | `builder_path_label` 158 | List of complete path components with labels. This includes layout components. 159 | 160 | `builder_input_path_key` 161 | List of input components in path with keys. This only includes input components, so no layout components. 162 | 163 | `builder_input_path_label` 164 | List of input components in path with labels. This only includes input components, so no layout components. 165 | 166 | Also propagate this as first official release 1.0.0 167 | 168 | ## 0.5.1 169 | 170 | Fix `initEmpty` in `editgridComponent`, bug in previous version 0.5.0 171 | 172 | ## 0.5.0 173 | 174 | Implement `initEmpty` in `editgridComponent`, which obtains a different key (`openWhenEmpty`) in the form builder schema. 175 | 176 | ## 0.4.11 177 | 178 | Improvements: 179 | - Add support for editGrid component (#33). 180 | - Breaking change: in a dataGrid, renamed `gridRow` object's `datagrid` property to `grid`. 181 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nova Code 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # formio-data (Python) 2 | 3 | formio.js (JSON Form Builder) data API for Python. 4 | 5 | For information about the formio.js project, see https://github.com/formio/formio.js 6 | 7 | ## Introduction 8 | 9 | **python-formio-data** is a Python package, which loads and transforms 10 | formio.js **Builder JSON** and **Form JSON** into **usable Python objects**.\ 11 | It's main aim is to provide easy access to a Form its components/fields, also 12 | captured as **Python objects**, which makes this API very versatile and usable. 13 | 14 | **Notes about terms:** 15 | - **Builder:** The Form Builder which is the design/blueprint of a Form. 16 | - **Form:** A filled-in Form, aka Form submission. 17 | - **Component:** Input (field) or layout component in the Form Builder and Form. 18 | 19 | ## Features 20 | 21 | - Compatible with Python 3.6 and later 22 | - Constructor of the **Builder** and **Form** class, only requires 23 | the JSON and an optional language code for translations. 24 | - Get a Form object its Components as a usable object e.g. datetime, boolean, dict (for select component) etc. 25 | - Open source (MIT License) 26 | 27 | ## Installation 28 | 29 | The source code is currently hosted on GitHub at: 30 | https://github.com/novacode-nl/python-formio-data 31 | 32 | ### PyPI - Python Package Index 33 | 34 | Binary installers for the latest released version are available at the [Python 35 | Package Index](https://pypi.python.org/pypi/formio-data) 36 | 37 | ```sh 38 | pip(3) install formio-data 39 | ``` 40 | 41 | #### Optional dependencies 42 | 43 | To support conditional visibility using JSON logic, you can install 44 | the `json-logic-qubit` package (the `json-logic` package it is forked 45 | off of is currently unmaintained). It's also possible to install it 46 | via the pip feature `json_logic` like so: 47 | 48 | ```sh 49 | pip(3) install -U formio-data[json_logic] 50 | ``` 51 | 52 | ### Source Install with Poetry (recommended) 53 | 54 | Convenient for developers. Also useful for running the (unit)tests. 55 | 56 | ```sh 57 | git clone git@github.com:novacode-nl/python-formio-data.git 58 | poetry install 59 | ``` 60 | 61 | #### Optional dependencies 62 | 63 | When working in the project itself, use 64 | 65 | ```sh 66 | poetry install -E json_logic 67 | ``` 68 | 69 | ### Source Install with pip 70 | 71 | Optional dependencies need to be installed separately. 72 | 73 | ```sh 74 | pip(3) install -U -e python-formio-data 75 | ``` 76 | 77 | ## Using direnv 78 | 79 | You can use [nixpkgs](https://nixos.org/) to run a self-contained 80 | Python environment without any additional setup. Once you've 81 | installed nixpkgs, switch into the directory and type "nix-shell" to 82 | get a shell from which the correct Python with packages is available. 83 | 84 | If you're using [direnv](https://direnv.net/), use `direnv allow` 85 | after changing into the project directory and you're good to go. Also 86 | consider [nix-direnv](https://github.com/nix-community/nix-direnv) to 87 | speed up the experience (it can re-use a cached local installation). 88 | 89 | ## License 90 | [MIT](LICENSE) 91 | 92 | ## Contributing 93 | All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. 94 | 95 | ## Usage examples 96 | 97 | For more examples of usage, see the unit-tests. 98 | 99 | ``` python 100 | >> from formiodata import Builder, Form 101 | >> 102 | # builder_json is a formio.js Builder JSON document (text/string) 103 | # form_json is a formio.js Form JSON document (text/string) 104 | >> 105 | >> builder = Builder(builder_json) 106 | >> form = Form(builder, form_json) 107 | 108 | ################## 109 | # input components 110 | ################## 111 | 112 | # textfield label 113 | >> print(form.input_components['firstname'].label) 114 | 'First Name' 115 | 116 | # textfield value 117 | >> print(form.input_components['firstname'].value) 118 | 'Bob' 119 | 120 | # datetime label 121 | >> print(form.input_components['birthday'].label) 122 | 'Birthday' 123 | 124 | # datetime value 125 | >> print(form.input_components['birthday'].value) 126 | '2009-10-16' 127 | 128 | >> print(form.input_components['birthday'].to_date()) 129 | datetime.date(2009 10 16) 130 | 131 | # datagrid (rows property) 132 | >> print(form.input_components['datagridMeasurements'].rows) 133 | [ 134 | {'measurementDatetime': , 'measurementFahrenheit': }, 135 | {'measurementDatetime': , 'measurementFahrenheit': } 136 | ] 137 | 138 | >> for row in form.input_components['datagridMeasurements'].rows: 139 | >> dtime = row['measurementDatetime'] 140 | >> fahrenheit = row['measurementFahrenheit'] 141 | >> print(%s: %s, %s: %s' % (dt.label, dt.to_datetime(), fahrenheit.label, fahrenheit.value)) 142 | 143 | Datetime: datetime.datetime(2021, 5, 8, 11, 39, 0, 296487), Fahrenheit: 122 144 | Datetime: datetime.datetime(2021, 5, 8, 11, 41, 5, 919943), Fahrenheit: 131 145 | 146 | # alternative example, by getattr 147 | >> print(form.data.firstname.label) 148 | 'First Name' 149 | 150 | >> print(form.data.firstname.value) 151 | 'Bob' 152 | 153 | ################### 154 | # validation errors 155 | ################### 156 | 157 | >> print(form.validation_errors()) 158 | { 159 | 'companyName': 'Company Name is required', 160 | 'editgridActivities': [ 161 | {'description': 'Description is required'}, 162 | {}, # no validation error (row 2) 163 | {}, # no validation error (row 3) 164 | {'description': 'Description is required', 'startDate': 'Start Date is required'} 165 | ] 166 | } 167 | 168 | ############################# 169 | # component path (properties) 170 | ############################# 171 | 172 | # datagrid input 173 | >> datagridMeasurements = builder.components['datagridMeasurements'] 174 | 175 | # builder_path 176 | >> [ 177 | >> print(row.input_components['measurementDatetime'].builder_path) 178 | >> for row in datagridMeasurements.rows 179 | >> ] 180 | [, , , ] 181 | 182 | # builder_path_key 183 | >> [ 184 | >> print(row.input_components['measurementDatetime'].builder_path_key) 185 | >> for row in datagridMeasurements.rows 186 | >> ] 187 | ['pageMeasurements', 'columnsExternal', 'datagridMeasurements', 'measurementDatetime'] 188 | 189 | # builder_path_labels 190 | >> [ 191 | >> print(row.input_components['measurementDatetime'].builder_path_labels) 192 | >> for row in datagridMeasurements.rows 193 | >> ] 194 | ['Page Measurements', 'Columns External', 'Data Grid Measurements', 'Measurement Datetime'] 195 | 196 | # builder_input_path 197 | >> [ 198 | >> print(row.input_components['measurementDatetime'].builder_input_path) 199 | >> for row in datagridMeasurements.rows 200 | >> ] 201 | [, ] 202 | 203 | # builder_input_path_key 204 | >> [ 205 | >> print(row.input_components['measurementDatetime'].builder_input_path_key) 206 | >> for row in datagridMeasurements.rows 207 | >> ] 208 | ['datagridMeasurements', 'measurementDatetime'] 209 | 210 | # builder_input_path_labels 211 | >> [ 212 | >> print(row.input_components['measurementDatetime'].builder_input_path_labels) 213 | >> for row in datagridMeasurements.rows 214 | >> ] 215 | ['Data Grid Measurements', 'Measurement Datetime'] 216 | 217 | ################################# 218 | # components (layout, input etc.) 219 | ################################# 220 | 221 | # columns 222 | >> print(form.components['addressColumns']) 223 | 224 | 225 | >> print(form.components['addressColumns'].rows) 226 | [ 227 | {'firstName': , 'lastName: }, 228 | {'email': , 'companyName: } 229 | ] 230 | 231 | ########################## 232 | # components class mapping 233 | ########################## 234 | 235 | # Below an example which verbosely shows the feature: 236 | # - First set a custom component type 'custom_editgrid' in the Builder JSON schema. 237 | # - Check (assert) whether the component object is an instance of the mapped editgridComponent. 238 | # This code is also present in the unittest (file): tests/test_component_class_mapping.py 239 | 240 | schema_dict = json.loads(self.builder_json) 241 | 242 | # change 'editgrid' type to 'custom_editgrid' 243 | for comp in schema_dict['components']: 244 | if comp['key'] == 'editGrid': 245 | comp['type'] = 'custom_editgrid' 246 | 247 | component_class_mapping = {'custom_editgrid': editgridComponent} 248 | builder = Builder( 249 | schema_json, 250 | component_class_mapping=component_class_mapping, 251 | ) 252 | 253 | custom_editgrid = builder.components['editGrid'] 254 | self.assertIsInstance(custom_editgrid, editgridComponent) 255 | self.assertEqual(custom_editgrid.type, 'custom_editgrid') 256 | ``` 257 | 258 | ## Unit tests 259 | 260 | **Note:** 261 | 262 | Internet access is recommended for running the `fileStorageUrlComponentTestCase`, because this also tests the URL Storage (type).\ 263 | If no internet access, this test won't fail and a WARNING shall be logged regarding a ConnectionError. 264 | 265 | ### Run all unittests 266 | 267 | From toplevel directory: 268 | 269 | ``` 270 | poetry install -E json_logic # if you haven't already 271 | poetry run python -m unittest 272 | ``` 273 | 274 | ### Run component unittests 275 | 276 | All Components, from toplevel directory: 277 | 278 | ``` 279 | poetry run python -m unittest tests/test_component_*.py 280 | ``` 281 | 282 | Nested components (complexity), from toplevel directory: 283 | 284 | ``` 285 | poetry run python -m unittest tests/test_nested_components.py 286 | ``` 287 | 288 | ### Run specific component unittest 289 | 290 | ``` 291 | poetry run python -m unittest tests.test_component_day.dayComponentTestCase.test_get_form_dayMonthYear 292 | ``` 293 | -------------------------------------------------------------------------------- /formiodata/builder.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import json 5 | import logging 6 | 7 | from collections import OrderedDict 8 | from copy import deepcopy 9 | 10 | from formiodata.components.component import Component 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Builder: 16 | 17 | def __init__( 18 | self, 19 | schema_json, 20 | language='en', 21 | i18n={}, 22 | resources={}, 23 | load_path_objects=True, 24 | component_class_mapping={}, 25 | **kwargs 26 | ): 27 | """ 28 | @param schema_json 29 | @param language 30 | @param i18n 31 | @param resources 32 | @param resources 33 | @param load_path_objects 34 | @param component_class_mapping 35 | """ 36 | 37 | if isinstance(schema_json, dict): 38 | self.schema = schema_json 39 | else: 40 | self.schema = json.loads(schema_json) 41 | 42 | self.language = language 43 | self.i18n = i18n 44 | self.resources = resources 45 | self.load_path_objects = load_path_objects 46 | self.component_class_mapping = component_class_mapping 47 | 48 | # Raw components from the schema 49 | self._raw_components = [] 50 | 51 | # Raw components enriched with Component(object) API. 52 | self.raw_components = [] 53 | 54 | # Key/value dictionay of all components for instant access. 55 | self.components = OrderedDict() 56 | self.components_path_key = OrderedDict() 57 | self.component_ids = OrderedDict() 58 | 59 | # Key/value dictionay of Form input-only components (i.e., no layout components) for instant access. 60 | self.input_components = {} 61 | 62 | # Set/load component attrs intialized above. 63 | self.load_components() 64 | 65 | # TODO kwargs['component_cls'] 66 | # Custom component classes 67 | self._component_cls = [] 68 | 69 | def load_components(self): 70 | self._raw_components = self.schema.get('components') 71 | self.raw_components = deepcopy(self.schema.get('components')) 72 | if self.raw_components: 73 | self._load_components(self.raw_components) 74 | 75 | def _load_components(self, components, parent=None): 76 | """ 77 | @param components 78 | """ 79 | for component in components: 80 | # Only determine and load class if component type. 81 | if 'type' in component: 82 | component_obj = self.get_component_object(component) 83 | # start and traverse from toplevel 84 | component_obj.load(component_owner=self, parent=None, data=None, is_form=False) 85 | self.components[component_obj.key] = component_obj 86 | 87 | def get_component_class(self, component): 88 | component_type = component.get('type') 89 | try: 90 | mapping_value = self.component_class_mapping[component_type] 91 | if isinstance(mapping_value, str): 92 | cls_name = '%sComponent' % mapping_value 93 | import_path = 'formiodata.components.%s' % mapping_value 94 | module = __import__(import_path, fromlist=[cls_name]) 95 | cls = getattr(module, cls_name) 96 | else: 97 | cls = self.component_class_mapping[component_type] 98 | except KeyError: 99 | cls_name = '%sComponent' % component_type 100 | import_path = 'formiodata.components.%s' % component_type 101 | module = __import__(import_path, fromlist=[cls_name]) 102 | cls = getattr(module, cls_name) 103 | return cls 104 | 105 | def get_component_object(self, component): 106 | """ 107 | @param component 108 | """ 109 | component_type = component.get('type') 110 | if component_type: 111 | try: 112 | cls = self.get_component_class(component) 113 | component_obj = cls(component, self, language=self.language, i18n=self.i18n, resources=self.resources) 114 | return component_obj 115 | except (AttributeError, ModuleNotFoundError) as e: 116 | # TODO try to find/load first from self._component_cls else 117 | # re-raise exception or silence (log error and return False) 118 | logging.error(e) 119 | # TODO: implement property (by kwargs) whether to return 120 | # (raw) Component object or throw exception, 121 | return Component(component, self) 122 | else: 123 | msg = "Can't instantiate a (raw) component without a type.\n\n" \ 124 | "Component raw data\n" \ 125 | "==================\n" \ 126 | "%s\n" 127 | logging.warning(msg % component) 128 | return False 129 | 130 | @property 131 | def form(self): 132 | """ 133 | Placeholder form dict, always empty. Useful in contexts where the component owner's form 134 | is requested because there is a need for form data. 135 | """ 136 | return {} 137 | -------------------------------------------------------------------------------- /formiodata/components/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | -------------------------------------------------------------------------------- /formiodata/components/address.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class addressComponent(Component): 8 | 9 | _none_value = {} 10 | 11 | # XXX other providers not analysed and implemented yet. 12 | PROVIDER_GOOGLE = 'google' 13 | 14 | def _address_google(self, get_type, notation='long_name'): 15 | comps = self.value.get('address_components') 16 | if not comps: 17 | return None 18 | else: 19 | for comp in comps: 20 | if comp.get('types') and get_type in comp['types']: 21 | return comp.get(notation) 22 | return None 23 | 24 | @property 25 | def provider(self): 26 | return self.raw.get('provider') 27 | 28 | @property 29 | def postal_code(self): 30 | if self.provider == self.PROVIDER_GOOGLE: 31 | return self._address_google('postal_code') 32 | else: 33 | return None 34 | 35 | @property 36 | def street_name(self): 37 | if self.provider == self.PROVIDER_GOOGLE: 38 | return self._address_google('route') 39 | else: 40 | return None 41 | 42 | @property 43 | def street_number(self): 44 | if self.provider == self.PROVIDER_GOOGLE: 45 | return self._address_google('street_number') 46 | else: 47 | return None 48 | 49 | @property 50 | def city(self): 51 | if self.provider == self.PROVIDER_GOOGLE: 52 | return self._address_google('locality') 53 | else: 54 | return None 55 | 56 | @property 57 | def country(self): 58 | if self.provider == self.PROVIDER_GOOGLE: 59 | return self._address_google('country') 60 | else: 61 | return None 62 | 63 | @property 64 | def country_code(self): 65 | if self.provider == self.PROVIDER_GOOGLE: 66 | return self._address_google('country', 'short_name') 67 | else: 68 | return None 69 | -------------------------------------------------------------------------------- /formiodata/components/button.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class buttonComponent(Component): 8 | 9 | @property 10 | def is_form_component(self): 11 | return False 12 | 13 | def load_data(self, data, is_form=False): 14 | # just bypass this 15 | pass 16 | -------------------------------------------------------------------------------- /formiodata/components/checkbox.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class checkboxComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/columns.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .layout_base import layoutComponentBase 5 | 6 | 7 | class columnsComponent(layoutComponentBase): 8 | 9 | def load_data(self, data, is_form=False): 10 | for column in self.raw['columns']: 11 | for component in column['components']: 12 | # Only determine and load class if component type. 13 | if 'type' in component: 14 | component_obj = self.builder.get_component_object(component) 15 | component_obj.load( 16 | self.child_component_owner, 17 | parent=self, 18 | data=data, 19 | all_data=self._all_data, 20 | is_form=is_form, 21 | ) 22 | 23 | @property 24 | def rows(self): 25 | rows = [] 26 | 27 | row = [] 28 | col_data = {'column': None, 'components': []} 29 | total_width = 0 30 | 31 | for col in self.raw['columns']: 32 | components = [] 33 | 34 | for col_comp in col['components']: 35 | for key, comp in self.components.items(): 36 | if col_comp['id'] == comp.id: 37 | components.append(comp) 38 | 39 | if col['width'] >= 12: 40 | # add previous (loop) row 41 | if row: 42 | rows.append(row) 43 | 44 | # init new row and add to rows 45 | row = [{'column': col, 'components': components}] 46 | rows.append(row) 47 | 48 | # init next loop (new row and total_width) 49 | row = [] 50 | total_width = 0 51 | elif total_width >= 12: 52 | # add previous (loop) row 53 | rows.append(row) 54 | row = [] 55 | # init new row for next loop 56 | col_data = {'column': col, 'components': components} 57 | row.append(col_data) 58 | total_width = col['width'] 59 | else: 60 | if not row: 61 | row = [{'column': col, 'components': components}] 62 | else: 63 | col_data = {'column': col, 'components': components} 64 | row.append(col_data) 65 | total_width += col['width'] 66 | if row: 67 | # add last generated row 68 | rows.append(row) 69 | return rows 70 | 71 | def render(self): 72 | html_rows = [] 73 | for row in self.rows: 74 | html_cells = [] 75 | for col in row: 76 | for component in col['components']: 77 | if component.is_visible: 78 | component.render() 79 | else: 80 | component.html_component = '' 81 | html_cells.append(''+component.html_component+'') 82 | 83 | html_rows.append(''+(''.join(html_cells))+'') 84 | 85 | self.html_component = ''+(''.join(html_rows))+'
' 86 | -------------------------------------------------------------------------------- /formiodata/components/component.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import json 5 | import uuid 6 | import logging 7 | 8 | from collections import OrderedDict 9 | 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class Component: 15 | 16 | _none_value = None 17 | 18 | def __init__(self, raw, builder, **kwargs): 19 | self.raw = raw 20 | self.builder = builder 21 | 22 | self._parent = None 23 | self._component_owner = None 24 | # components can also be seen as children 25 | self.components = OrderedDict() 26 | 27 | # List of complete path components. This includes layout 28 | # components. 29 | self.builder_path = [] 30 | # includes input components, so no layout components. 31 | self.builder_input_path = [] 32 | 33 | # XXX uuid to ensure (hope this won't break anything) 34 | self.id = self.raw.get('id', str(uuid.uuid4())) 35 | 36 | # submission at this level {key: value, ...} 37 | # This includes a pseudo 'value' key which always encodes the current 38 | # component's value. NOTE: This should be refactored away for conditionals 39 | # to work 100% correct when there's another element with a "value" key. 40 | self.form = {} 41 | # Full raw data from the root on up {key: value, ...} 42 | self._all_data = {} 43 | 44 | # i18n (language, translations) 45 | self.language = kwargs.get('language', 'en') 46 | self.i18n = kwargs.get('i18n', {}) 47 | self.resources = kwargs.get('resources', {}) 48 | if self.resources and isinstance(self.resources, str): 49 | self.resources = json.loads(self.resources) 50 | self.html_component = "" 51 | self.defaultValue = self.raw.get('defaultValue') 52 | 53 | def load(self, component_owner, parent=None, data=None, all_data=None, is_form=False): 54 | self.component_owner = component_owner 55 | 56 | if parent: 57 | self.parent = parent 58 | 59 | self._all_data = all_data 60 | self.load_data(data, is_form=is_form) 61 | 62 | self.builder.component_ids[self.id] = self 63 | 64 | # path 65 | self.set_builder_paths() 66 | builder_path_keys = [p.key for p in self.builder_path] 67 | builder_path_key = '.'.join(builder_path_keys) 68 | self.builder.components_path_key[builder_path_key] = self 69 | 70 | def load_data(self, data, is_form=False): 71 | if self.input and data: 72 | try: 73 | self.value = data[self.key] 74 | self.raw_value = data[self.key] 75 | except KeyError: 76 | # NOTE: getter will read out defaultValue if it's missing in self.form 77 | # TODO: Is this the right approach? 78 | pass 79 | 80 | @property 81 | def id(self): 82 | return self._id 83 | 84 | @id.setter 85 | def id(self, id=False): 86 | if not id: 87 | id = str(uuid.uuid4()) 88 | self._id = id 89 | 90 | @property 91 | def key(self): 92 | return self.raw.get('key') 93 | 94 | @property 95 | def type(self): 96 | return self.raw.get('type') 97 | 98 | @property 99 | def input(self): 100 | return self.raw.get('input') 101 | 102 | @property 103 | def parent(self): 104 | return self._parent 105 | 106 | @parent.setter 107 | def parent(self, parent): 108 | if parent: 109 | self._parent = parent 110 | self._parent.components[self.key] = self 111 | 112 | @property 113 | def is_form_component(self): 114 | return bool(self.input) 115 | 116 | @property 117 | def component_owner(self): 118 | """The component's "owner". This is usually the Builder class which 119 | created it. But if this component is inside a datagrid 120 | component which may clone the form element, then the datagrid 121 | is the owner. Each component adds itself to the `input_components` 122 | property its owner. 123 | """ 124 | return self._component_owner 125 | 126 | @component_owner.setter 127 | def component_owner(self, component_owner): 128 | self._component_owner = component_owner 129 | if self.is_form_component: 130 | self._component_owner.input_components[self.key] = self 131 | 132 | @property 133 | def child_component_owner(self): 134 | """The owner object for child components, to use in the recursion""" 135 | return self.component_owner 136 | 137 | @property 138 | def builder_path_key(self): 139 | return [p.key for p in self.builder_path] 140 | 141 | @property 142 | def builder_path_label(self): 143 | return [p.label for p in self.builder_path] 144 | 145 | @property 146 | def builder_input_path_key(self): 147 | return [p.key for p in self.builder_input_path] 148 | 149 | @property 150 | def builder_input_path_label(self): 151 | return [p.label for p in self.builder_input_path] 152 | 153 | def set_builder_paths(self): 154 | builder_path = [self] 155 | builder_input_path = [] 156 | if self.is_form_component: 157 | if self.builder.load_path_objects: 158 | builder_input_path.append(self) 159 | parent = self.parent 160 | while parent: 161 | if hasattr(parent, 'key'): 162 | if self.builder.load_path_objects: 163 | builder_path.append(parent) 164 | if parent.is_form_component: 165 | if self.builder.load_path_objects: 166 | builder_input_path.append(parent) 167 | parent = parent.parent 168 | elif parent.__class__.__name__ == 'gridRow': 169 | parent = parent.grid 170 | else: 171 | parent = parent.component_owner 172 | builder_path.reverse() 173 | self.builder_path = builder_path 174 | # input path 175 | builder_input_path.reverse() 176 | self.builder_input_path = builder_input_path 177 | 178 | @property 179 | def validate(self): 180 | return self.raw.get('validate') 181 | 182 | @property 183 | def required(self): 184 | return self.raw.get('validate', {}).get('required') 185 | 186 | @property 187 | def properties(self): 188 | return self.raw.get('properties') 189 | 190 | @property 191 | def clearOnHide(self): 192 | if 'clearOnHide' in self.raw: 193 | return self.raw.get('clearOnHide') 194 | else: 195 | return None 196 | 197 | @property 198 | def label(self): 199 | label = self.raw.get('label') 200 | if self.i18n.get(self.language): 201 | return self.i18n[self.language].get(label, label) 202 | else: 203 | return label 204 | 205 | @label.setter 206 | def label(self, value): 207 | if self.raw.get('label'): 208 | self.raw['label'] = value 209 | 210 | @property 211 | def value(self): 212 | if self.clearOnHide is not None and not self.clearOnHide: 213 | default = self.defaultValue 214 | else: 215 | default = self._none_value 216 | return self.form.get('value', default) 217 | 218 | @value.setter 219 | def value(self, value): 220 | self.set_value(value) 221 | 222 | def _set_value(self, value): 223 | self.form['value'] = self._encode_value(value) 224 | 225 | @property 226 | def raw_value(self): 227 | return self.form['raw_value'] 228 | 229 | @raw_value.setter 230 | def raw_value(self, value): 231 | self._set_raw_value(value) 232 | 233 | def _set_raw_value(self, value): 234 | self.form['raw_value'] = value 235 | 236 | def set_value(self, value): 237 | """ Set raw_value and value at once! """ 238 | self._set_raw_value(value) 239 | self._set_value(value) 240 | 241 | @property 242 | def hidden(self): 243 | return self.raw.get('hidden') 244 | 245 | @property 246 | def tableView(self): 247 | return self.raw.get('tableView') 248 | 249 | @property 250 | def disabled(self): 251 | return self.raw.get('disabled') 252 | 253 | @property 254 | def conditional(self): 255 | return self.raw.get('conditional') 256 | 257 | @property 258 | def customConditional(self): 259 | return self.raw.get('customConditional') 260 | 261 | @property 262 | def templates(self): 263 | return self.raw.get('templates') 264 | 265 | @property 266 | def logic(self): 267 | return self.raw.get('logic') 268 | 269 | def _encode_value(self, value): 270 | return value 271 | 272 | def render(self): 273 | if self.value is not None: 274 | self.html_component = '

%s

' % self.value 275 | 276 | @property 277 | def conditionally_visible(self): 278 | """ 279 | If conditional visibility applies, evaluate to see if it is visible. 280 | Note that the component can also be hidden, which is a separate concept. 281 | 282 | IMPORTANT 283 | ========= 284 | Currently JSONLogic (json) precedes the Simple (when). 285 | This causes backward compatibility issues when changing the priority order. 286 | """ 287 | try: 288 | cond = self.raw['conditional'] 289 | if cond.get('json'): 290 | return self.conditional_visible_json_logic() 291 | elif cond.get('when'): 292 | return self.conditional_visible_when() 293 | except KeyError: 294 | # Unknown component or no 'when', 'eq' or 'show' property 295 | pass 296 | 297 | # By default, it's visible 298 | return True 299 | 300 | def conditional_visible_when(self): 301 | cond = self.raw['conditional'] 302 | triggering_component = self.component_owner.input_components[cond['when']] 303 | triggering_value = cond['eq'] 304 | if isinstance( 305 | triggering_component.value, dict 306 | ) and triggering_component.value.get(triggering_value): 307 | # E.g. triggering_component like selectboxesComponent 308 | return cond["show"] 309 | elif triggering_component.value == triggering_value: 310 | return cond['show'] 311 | else: 312 | return not cond['show'] 313 | 314 | def conditional_visible_json_logic(self): 315 | # Optional package 316 | try: 317 | from json_logic import jsonLogic 318 | context = {'data': self._all_data} 319 | try: 320 | context['row'] = self.component_owner.row 321 | except AttributeError: 322 | pass # only datagrid rows have a "row" attribute 323 | cond = self.raw['conditional'] 324 | return jsonLogic(cond['json'], context) 325 | except ImportError: 326 | logger.warning(f'Could not load json logic extension; will not evaluate visibility of {self.__class__.__name__} {self.id} ("{self.key}")') 327 | return True 328 | 329 | @property 330 | def is_visible(self): 331 | conditional = self.raw.get('conditional') 332 | if conditional and (conditional.get('json') or conditional.get('when')): 333 | # Not implement (JavaScript): 334 | # conditional_show = self.raw.get('show') 335 | return self.conditionally_visible 336 | else: 337 | return not self.hidden 338 | 339 | def validation_errors(self): 340 | errors = {} 341 | if self.required and not self.value: 342 | msg_tmpl = '{{field}} is required' 343 | if self.i18n.get(self.language): 344 | msg_tmpl = self.i18n[self.language].get(msg_tmpl, msg_tmpl) 345 | errors['required'] = msg_tmpl.replace('{{field}}', self.label) 346 | return errors 347 | -------------------------------------------------------------------------------- /formiodata/components/content.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class contentComponent(Component): 8 | # XXX should we move the initEmpty from base 9 | pass 10 | -------------------------------------------------------------------------------- /formiodata/components/currency.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class currencyComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/datagrid.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .grid_base import baseGridComponent 5 | 6 | 7 | class datagridComponent(baseGridComponent): 8 | # XXX should we move the initEmpty from base 9 | pass 10 | -------------------------------------------------------------------------------- /formiodata/components/datetime.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from copy import copy 5 | from datetime import datetime 6 | 7 | from .component import Component 8 | 9 | 10 | class datetimeComponent(Component): 11 | 12 | @property 13 | def enableTime(self): 14 | return self.raw.get('enableTime') 15 | 16 | def _format_mappings(self): 17 | """ 18 | Dictionary of mappings between Formio Datetime component 19 | (key) to Python format (value). 20 | 21 | Formio uses the (JS uibDateParser) format codes referenced in: 22 | https://github.com/angular-ui/bootstrap/tree/master/src/dateparser/docs#uibdateparsers-format-codes 23 | """ 24 | return { 25 | 'year': {'yyyy': '%Y', 'yy': '%y', 'y': '%y'}, 26 | 'month': {'MMMM': '%B', 'MMM': '%b', 'MM': '%m', 'M': '%-m'}, 27 | 'day': {'dd': '%d', 'd': '%-d'}, 28 | 'hour': {'HH': '%H', 'H': '%-H', 'hh': '%I', 'h': '%-I'}, 29 | 'minute': {'mm': '%M', 'm': '%-M'}, 30 | 'second': {'ss': '%S', 's': '%-S'}, 31 | 'am_pm': {'a': '%p'} 32 | } 33 | 34 | def _fromisoformat(self, value): 35 | # Backport of Python 3.7 datetime.fromisoformat 36 | if hasattr(datetime, 'fromisoformat'): 37 | # Python >= 3.7 38 | return datetime.fromisoformat(value) 39 | else: 40 | # Python < 3.7 41 | # replaces the fromisoformat, not available in Python < 3.7 42 | # 43 | # XXX following: 44 | # - Raises: '2021-02-25T00:00:00+01:00' does not match format '%Y-%m-%dT%H:%M%z' 45 | # - Due to %z not obtaing the colon in '+1:00' (tz offset) 46 | # - More info: https://stackoverflow.com/questions/54268458/datetime-strptime-issue-with-a-timezone-offset-with-colons 47 | # fmt_str = r"%Y-%m-%dT%H:%M:%S%z" 48 | # return datetime.strptime(value, fmt_str) 49 | # 50 | # REQUIREMENT (TODO document, setup dependency or try/except raise exception) 51 | # - pip install dateutil 52 | # - https://dateutil.readthedocs.io/ 53 | from dateutil.parser import parse 54 | return parse(value) 55 | 56 | @property 57 | def value(self): 58 | return super().value 59 | 60 | @value.setter 61 | def value(self, value): 62 | """ Inherit property setter the right way, URLs: 63 | - https://gist.github.com/Susensio/979259559e2bebcd0273f1a95d7c1e79 64 | - https://stackoverflow.com/questions/35290540/understanding-property-decorator-and-inheritance 65 | """ 66 | if not value: 67 | return value 68 | else: 69 | return super(self.__class__, self.__class__).value.fset(self, value) 70 | 71 | def to_datetime(self): 72 | if not self.raw_value: 73 | return None 74 | dt = self._fromisoformat(self.raw_value) 75 | return dt 76 | 77 | def to_date(self): 78 | if not self.raw_value: 79 | return None 80 | return self.to_datetime().date() 81 | -------------------------------------------------------------------------------- /formiodata/components/day.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import calendar 5 | 6 | from collections import OrderedDict 7 | 8 | from .component import Component 9 | 10 | 11 | class dayComponent(Component): 12 | 13 | @property 14 | def dayFirst(self): 15 | return self.raw.get('dayFirst') 16 | 17 | @property 18 | def value(self): 19 | return super().value 20 | 21 | @value.setter 22 | def value(self, value): 23 | """ 24 | Notes: 25 | - value format: dd/dd/yyyy 26 | - Empty value: '00/00/0000' 27 | """ 28 | val = OrderedDict() 29 | fields = self.raw['fields'] 30 | 31 | # XXX Maybe future formio versions have more formatting possibilites. 32 | if self.dayFirst: 33 | if not fields['day'].get('hide'): 34 | day_val = value[0:2] 35 | if day_val != '00': 36 | val['day'] = int(day_val) 37 | else: 38 | val['day'] = None 39 | if not fields['month'].get('hide'): 40 | month_val = value[3:5] 41 | if month_val != '00': 42 | val['month'] = int(month_val) 43 | else: 44 | val['month'] = None 45 | else: 46 | if not fields['month'].get('hide'): 47 | month_val = value[0:2] 48 | if month_val != '00': 49 | val['month'] = int(month_val) 50 | else: 51 | val['month'] = None 52 | if not fields['day'].get('hide'): 53 | day_val = value[3:5] 54 | if day_val != '00': 55 | val['day'] = int(day_val) 56 | else: 57 | val['day'] = None 58 | 59 | if not fields['year'].get('hide'): 60 | if not fields['year'].get('hide'): 61 | year_val = value[6:10] 62 | if year_val != '0000': 63 | val['year'] = int(year_val) 64 | else: 65 | val['year'] = None 66 | 67 | super(self.__class__, self.__class__).value.fset(self, val) 68 | 69 | @property 70 | def day(self): 71 | fields = self.raw['fields'] 72 | if not fields['day'].get('hide'): 73 | return self.value['day'] 74 | else: 75 | return None 76 | 77 | @property 78 | def month(self): 79 | fields = self.raw['fields'] 80 | if not fields['month'].get('hide'): 81 | return self.value['month'] 82 | else: 83 | return None 84 | 85 | @property 86 | def month_name(self): 87 | fields = self.raw['fields'] 88 | if self.value['month'] and not fields['month'].get('hide'): 89 | month_name = calendar.month_name[self.value['month']] 90 | if self.i18n.get(self.language): 91 | return self.i18n[self.language].get(month_name, month_name) 92 | else: 93 | return month_name 94 | else: 95 | return None 96 | 97 | @property 98 | def year(self): 99 | fields = self.raw['fields'] 100 | if not fields['year'].get('hide'): 101 | return self.value['year'] 102 | else: 103 | return None 104 | -------------------------------------------------------------------------------- /formiodata/components/editgrid.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .grid_base import baseGridComponent 5 | 6 | 7 | class editgridComponent(baseGridComponent): 8 | 9 | @property 10 | def initEmpty(self): 11 | return not self.raw.get('openWhenEmpty') 12 | -------------------------------------------------------------------------------- /formiodata/components/email.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class emailComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/fieldset.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .layout_base import layoutComponentBase 5 | 6 | 7 | class fieldsetComponent(layoutComponentBase): 8 | 9 | def load_data(self, data, is_form=False): 10 | for component in self.raw.get('components', []): 11 | # Only determine and load class if component type. 12 | if 'type' in component: 13 | component_obj = self.builder.get_component_object(component) 14 | component_obj.load( 15 | self.child_component_owner, 16 | parent=self, 17 | data=data, 18 | all_data=self._all_data, 19 | is_form=is_form, 20 | ) 21 | -------------------------------------------------------------------------------- /formiodata/components/file.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | from formiodata.utils import base64_encode_url 7 | 8 | 9 | class fileComponent(Component): 10 | 11 | def __init__(self, raw, builder, **kwargs): 12 | super().__init__(raw, builder, **kwargs) 13 | 14 | @property 15 | def storage(self): 16 | return self.raw.get('storage') 17 | 18 | @property 19 | def url(self): 20 | return self.raw.get('url') 21 | 22 | @property 23 | def base64(self): 24 | if self.storage == 'url': 25 | res = '' 26 | for val in self.form.get('value'): 27 | url = val.get('url') 28 | res += base64_encode_url(url) 29 | return res 30 | elif self.storage == 'base64': 31 | return super().value 32 | 33 | # @value.setter 34 | # def value(self, value): 35 | # """ Inherit property setter the right way, URLs: 36 | # - https://gist.github.com/Susensio/979259559e2bebcd0273f1a95d7c1e79 37 | # - https://stackoverflow.com/questions/35290540/understanding-property-decorator-and-inheritance 38 | # """ 39 | # super(self.__class__, self.__class__).value.fset(self, value) 40 | -------------------------------------------------------------------------------- /formiodata/components/grid_base.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from collections import OrderedDict 5 | 6 | from .component import Component 7 | 8 | 9 | class baseGridComponent(Component): 10 | 11 | class gridRow: 12 | """Not *really* a component, but it implements the same 13 | partial interface with input_components and components. 14 | TODO: Consider if there should be a shared base component for 15 | this (ComponentOwner?) 16 | """ 17 | def __init__(self, grid, data): 18 | self.grid = grid 19 | self.builder = grid.builder 20 | self.builder_path = None 21 | self.input_components = {} 22 | self.components = OrderedDict() 23 | self.form = grid.form 24 | self.row = data 25 | self.html_component = '' 26 | grid.create_component_objects(self, data) 27 | 28 | def render(self): 29 | html_components = [] 30 | for component in self.components.values(): 31 | if component.is_visible: 32 | component.render() 33 | else: 34 | component.html_component = '' 35 | html_components.append(''+component.html_component+'') 36 | self.html_component = ''+(''.join(html_components))+'' 37 | 38 | def __init__(self, raw, builder, **kwargs): 39 | # TODO when adding other data/grid components, create new 40 | # dataComponent class these can inherit from. 41 | self.input_components = {} 42 | self.rows = [] 43 | super().__init__(raw, builder, **kwargs) 44 | self.form = {'value': []} 45 | 46 | def create_component_objects(self, parent, data): 47 | """This is a weird one, it creates component object for the 48 | "blueprint" inside the Builder, with parent = grid, and in 49 | a form on each grid row with parent = gridRow 50 | """ 51 | for component in self.raw.get('components', []): 52 | # Only determine and load class if component type. 53 | if 'type' in component: 54 | component_obj = parent.builder.get_component_object(component) 55 | component_obj.load(component_owner=parent, parent=parent, data=data, all_data=self._all_data) 56 | parent.components[component_obj.key] = component_obj 57 | 58 | def load_data(self, data, is_form=False): 59 | # Always instantiate child components, even if no data. 60 | # This makes it exist both in the builder and in the form. 61 | self.create_component_objects(self, data) 62 | 63 | # TODO: Make sure data is always a dict here? 64 | if data and data.get(self.key): 65 | self._load_rows(data[self.key]) 66 | self.value = data[self.key] 67 | self.raw_value = data[self.key] 68 | elif not self.initEmpty and not is_form: 69 | self.rows = [self.gridRow(self, None)] 70 | 71 | def _load_rows(self, data): 72 | rows = [] 73 | 74 | for row in data: 75 | # EXAMPLE row (which is an entry in the data list): 76 | # {'email': 'personal@example.com', 'typeOfEmail': 'personal'} 77 | new_row = self.gridRow(self, row) 78 | 79 | if new_row: 80 | rows.append(new_row) 81 | self.rows = rows 82 | 83 | @property 84 | def labels(self): 85 | labels = OrderedDict() 86 | for comp in self.raw['components']: 87 | if self.i18n.get(self.language): 88 | label = self.i18n[self.language].get(comp['label'], comp['label']) 89 | else: 90 | label = comp['label'] 91 | labels[comp['key']] = label 92 | return labels 93 | 94 | @property 95 | def is_form_component(self): 96 | # NOTE: A grid is not _really_ a form component, but it 97 | # has a key in the JSON for loading the form, so it acts as 98 | # such, and it will create an entry in the "input_components" 99 | # property of its owner. 100 | return True 101 | 102 | @property 103 | def child_component_owner(self): 104 | return self 105 | 106 | @property 107 | def initEmpty(self): 108 | return self.raw.get('initEmpty') 109 | 110 | def validation_errors(self): 111 | errors = [] 112 | for row_idx, row in enumerate(self.rows): 113 | row_errors = {} 114 | for component_key, component in row.input_components.items(): 115 | component_errors = component.validation_errors() 116 | if bool(component_errors): 117 | # scalar (not grid) components retrieve a Dict 118 | # from method validation_errors() 119 | row_errors[component_key] = component_errors 120 | if bool(row_errors): 121 | errors.append(row_errors) 122 | else: 123 | errors.append({}) 124 | return errors 125 | 126 | def render(self): 127 | for row in self.rows: 128 | row.render() 129 | self.html_component = ''+(''.join([row.html_component for row in self.rows]))+'
' 130 | -------------------------------------------------------------------------------- /formiodata/components/htmlelement.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class htmlelementComponent(Component): 8 | 9 | @property 10 | def html(self): 11 | html = '<%s>%s' % (self.raw['tag'], self.raw['content'], self.raw['tag']) 12 | return html 13 | -------------------------------------------------------------------------------- /formiodata/components/layout_base.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class layoutComponentBase(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/number.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class numberComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/panel.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .layout_base import layoutComponentBase 5 | 6 | 7 | class panelComponent(layoutComponentBase): 8 | 9 | def load_data(self, data, is_form=False): 10 | for component in self.raw.get('components', []): 11 | # Only determine and load class if component type. 12 | if 'type' in component: 13 | component_obj = self.builder.get_component_object(component) 14 | component_obj.load( 15 | self.child_component_owner, 16 | parent=self, 17 | data=data, 18 | all_data=self._all_data, 19 | is_form=is_form, 20 | ) 21 | 22 | @property 23 | def title(self): 24 | title = self.raw.get('title') 25 | if not title: 26 | title = self.raw.get('label') 27 | 28 | if self.i18n.get(self.language): 29 | return self.i18n[self.language].get(title, title) 30 | else: 31 | return title 32 | -------------------------------------------------------------------------------- /formiodata/components/password.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class passwordComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/phoneNumber.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class phoneNumberComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/phone_number.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class phoneNumberComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/radio.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class radioComponent(Component): 8 | 9 | def _encode_value(self, value): 10 | # A number value got casted to integer, by json.loads(). 11 | # Ensure this becomes a string. 12 | return str(value) 13 | 14 | @property 15 | def values_labels(self): 16 | comp = self.component_owner.input_components.get(self.key) 17 | builder_values = comp.raw.get('values') 18 | values_labels = {} 19 | 20 | for b_val in builder_values: 21 | if self.i18n.get(self.language): 22 | label = self.i18n[self.language].get(b_val['label'], b_val['label']) 23 | else: 24 | label = b_val['label'] 25 | val = {'key': b_val['value'], 'label': label, 'value': b_val['value'] == self.value} 26 | values_labels[b_val['value']] = val 27 | return values_labels 28 | 29 | @property 30 | def value_label(self): 31 | comp = self.component_owner.input_components.get(self.key) 32 | builder_values = comp.raw.get('values') 33 | for b_val in builder_values: 34 | if b_val['value'] == self.value: 35 | if self.i18n.get(self.language): 36 | return self.i18n[self.language].get(b_val['label'], b_val['label']) 37 | else: 38 | return b_val['label'] 39 | else: 40 | return False 41 | -------------------------------------------------------------------------------- /formiodata/components/resource.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from formiodata.utils import decode_resource_template, fetch_dict_get_value 5 | 6 | from .component import Component 7 | 8 | 9 | class resourceComponent(Component): 10 | 11 | def __init__(self, raw, builder, **kwargs): 12 | super().__init__(raw, builder, **kwargs) 13 | self.item_data = {} 14 | self.template_label_keys = decode_resource_template(self.raw.get('template')) 15 | self.compute_resources() 16 | 17 | def compute_resources(self): 18 | if self.resources: 19 | resource_id = self.raw.get('resource') 20 | if resource_id and not resource_id == "" and resource_id in self.resources: 21 | resource_list = self.resources[resource_id] 22 | self.raw['data'] = {"values": []} 23 | for item in resource_list: 24 | label = fetch_dict_get_value(item, self.template_label_keys[:]) 25 | self.raw['data']['values'].append({ 26 | "label": label, 27 | "value": item['_id']['$oid'] 28 | }) 29 | 30 | @property 31 | def value_label(self): 32 | comp = self.component_owner.input_components.get(self.key) 33 | values = comp.raw.get('data') and comp.raw['data'].get('values') 34 | for val in values: 35 | if val['value'] == self.value: 36 | label = val['label'] 37 | if self.i18n.get(self.language): 38 | return self.i18n[self.language].get(label, label) 39 | else: 40 | return label 41 | else: 42 | return False 43 | 44 | @property 45 | def value_labels(self): 46 | comp = self.component_owner.input_components.get(self.key) 47 | values = comp.raw.get('data') and comp.raw['data'].get('values') 48 | value_labels = [] 49 | for val in values: 50 | if val['value'] in self.value: 51 | if self.i18n.get(self.language): 52 | value_labels.append(self.i18n[self.language].get(val['label'], val['label'])) 53 | else: 54 | value_labels.append(val['label']) 55 | return value_labels 56 | 57 | @property 58 | def data(self): 59 | return self.raw.get('data') 60 | 61 | @property 62 | def values(self): 63 | return self.raw.get('data').get('values') 64 | -------------------------------------------------------------------------------- /formiodata/components/select.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class selectComponent(Component): 8 | 9 | @property 10 | def dataSrc(self): 11 | return self.raw.get('dataSrc') 12 | 13 | @property 14 | def multiple(self): 15 | return self.raw.get('multiple') 16 | 17 | @property 18 | def value_label(self): 19 | if not self.value: 20 | return None 21 | comp = self.component_owner.input_components.get(self.key) 22 | if self.dataSrc == 'url': 23 | label = self.value['label'] 24 | if self.i18n.get(self.language): 25 | return self.i18n[self.language].get(label, label) 26 | else: 27 | return label 28 | else: 29 | data_type = comp.raw.get('dataType') 30 | values = comp.raw.get('data') and comp.raw['data'].get('values') 31 | for val in values: 32 | if data_type == 'number': 33 | data_val = int(val['value']) 34 | else: 35 | data_val = val['value'] 36 | 37 | if data_val == self.value: 38 | label = val['label'] 39 | if self.i18n.get(self.language): 40 | return self.i18n[self.language].get(label, label) 41 | else: 42 | return label 43 | else: 44 | return None 45 | 46 | @property 47 | def value_labels(self): 48 | comp = self.component_owner.input_components.get(self.key) 49 | value_labels = [] 50 | 51 | if self.dataSrc == 'url': 52 | for val in self.value: 53 | label = val['label'] 54 | if self.i18n.get(self.language): 55 | label = self.i18n[self.language].get(label, label) 56 | value_labels.append(label) 57 | else: 58 | data_type = comp.raw.get('dataType') 59 | values = comp.raw.get('data') and comp.raw['data'].get('values') 60 | 61 | for val in values: 62 | if data_type == 'number': 63 | data_val = int(val['value']) 64 | else: 65 | data_val = val['value'] 66 | 67 | if self.value and data_val in self.value: 68 | if self.i18n.get(self.language): 69 | value_labels.append(self.i18n[self.language].get(val['label'], val['label'])) 70 | else: 71 | value_labels.append(val['label']) 72 | return value_labels 73 | -------------------------------------------------------------------------------- /formiodata/components/selectboxes.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class selectboxesComponent(Component): 8 | 9 | @property 10 | def dataSrc(self): 11 | return self.raw.get('dataSrc') 12 | 13 | @property 14 | def values_labels(self): 15 | comp = self.component_owner.input_components.get(self.key) 16 | builder_values = comp.raw.get('values') 17 | values_labels = {} 18 | for b_val in builder_values: 19 | if self.value and b_val.get('value'): 20 | if self.i18n.get(self.language): 21 | label = self.i18n[self.language].get(b_val['label'], b_val['label']) 22 | else: 23 | label = b_val['label'] 24 | val = {'key': b_val['value'], 'label': label, 'value': self.value.get(b_val['value'])} 25 | values_labels[b_val['value']] = val 26 | return values_labels 27 | -------------------------------------------------------------------------------- /formiodata/components/signature.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class signatureComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/survey.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class surveyComponent(Component): 8 | 9 | @property 10 | def values_labels(self): 11 | comp = self.component_owner.input_components.get(self.key) 12 | builder_values = comp.raw.get('values') 13 | labels = [] 14 | for val in builder_values: 15 | if self.i18n.get(self.language): 16 | label = self.i18n[self.language].get(val['label'], val['label']) 17 | else: 18 | label = val['label'] 19 | labels.append(label) 20 | return labels 21 | 22 | @property 23 | def grid(self): 24 | comp = self.component_owner.input_components.get(self.key) 25 | builder_questions = comp.raw.get('questions') 26 | builder_values = comp.raw.get('values') 27 | grid = [] 28 | for question in builder_questions: 29 | # question 30 | if self.i18n.get(self.language): 31 | question_label = self.i18n[self.language].get(question['label'], question['label']) 32 | else: 33 | question_label = question['label'] 34 | question_dict = {'question_value': question['value'], 'question_label': question_label, 'values': []} 35 | 36 | # value 37 | for b_val in builder_values: 38 | if self.i18n.get(self.language): 39 | val_label = self.i18n[self.language].get(b_val['label'], b_val['label']) 40 | else: 41 | val_label = b_val['label'] 42 | 43 | value = { 44 | 'label': val_label, 45 | 'value': b_val['value'], 46 | 'checked': False # default as fallback (if new values in builder) 47 | } 48 | 49 | if self.value.get(question['value']): 50 | value['checked'] = self.value[question['value']] == b_val['value'] 51 | 52 | question_dict['values'].append(value) 53 | 54 | # append 55 | grid.append(question_dict) 56 | return grid 57 | -------------------------------------------------------------------------------- /formiodata/components/table.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .layout_base import layoutComponentBase 5 | 6 | 7 | class tableComponent(layoutComponentBase): 8 | def __init__(self, raw, builder, **kwargs): 9 | self.rows = [] 10 | super().__init__(raw, builder, **kwargs) 11 | 12 | def load_data(self, data, is_form=False): 13 | self.rows = [] 14 | 15 | for data_row in self.raw.get('rows', []): 16 | row = [] 17 | 18 | for col in data_row: 19 | components = [] 20 | for component in col['components']: 21 | # Only determine and load class if component type. 22 | if 'type' in component: 23 | component_obj = self.builder.get_component_object(component) 24 | component_obj.load( 25 | self.child_component_owner, 26 | parent=self, 27 | data=data, 28 | all_data=self._all_data, 29 | is_form=is_form, 30 | ) 31 | components.append(component_obj) 32 | 33 | row.append({'column': col, 'components': components}) 34 | 35 | self.rows.append(row) 36 | -------------------------------------------------------------------------------- /formiodata/components/tabs.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .layout_base import layoutComponentBase 5 | 6 | 7 | class tabsComponent(layoutComponentBase): 8 | 9 | def load_data(self, data, is_form=False): 10 | self.tabs = [] 11 | 12 | for data_tab in self.raw.get('components', []): 13 | tab = {'tab': data_tab, 'components': []} 14 | 15 | for component in data_tab['components']: 16 | # Only determine and load class if component type. 17 | if 'type' in component: 18 | component_obj = self.builder.get_component_object(component) 19 | component_obj.load( 20 | self.child_component_owner, 21 | parent=self, 22 | data=data, 23 | all_data=self._all_data, 24 | is_form=is_form, 25 | ) 26 | tab['components'].append(component_obj) 27 | 28 | self.tabs.append(tab) 29 | -------------------------------------------------------------------------------- /formiodata/components/textarea.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class textareaComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/textfield.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class textfieldComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/time.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class timeComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/components/url.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from .component import Component 5 | 6 | 7 | class urlComponent(Component): 8 | pass 9 | -------------------------------------------------------------------------------- /formiodata/form.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import json 5 | import logging 6 | import re 7 | 8 | from collections import defaultdict, OrderedDict 9 | 10 | from formiodata.builder import Builder 11 | 12 | 13 | class Form: 14 | 15 | def __init__( 16 | self, 17 | form_json, 18 | builder=None, 19 | builder_schema_json=None, 20 | lang="en", 21 | component_class_mapping={}, 22 | **kwargs 23 | ): 24 | """ 25 | @param form_json 26 | @param builder Builder 27 | @param builder_schema_json 28 | @param lang 29 | """ 30 | if isinstance(form_json, dict): 31 | self.form = form_json 32 | else: 33 | self.form = json.loads(form_json) 34 | 35 | self.builder = builder 36 | self.builder_schema_json = builder_schema_json 37 | self.lang = lang 38 | self.component_class_mapping = component_class_mapping 39 | 40 | if self.builder and self.builder_schema_json: 41 | raise Exception("Constructor accepts either builder or builder_schema_json.") 42 | 43 | if self.builder: 44 | assert isinstance(self.builder, Builder) 45 | elif self.builder_schema_json: 46 | assert isinstance(self.builder_schema_json, str) 47 | else: 48 | raise Exception("Provide either the argument: builder or builder_schema_json.") 49 | 50 | if self.builder is None and self.builder_schema_json: 51 | self.set_builder_by_builder_schema_json() 52 | 53 | # defaults to English (en) date/time format 54 | self.date_format = kwargs.get('date_format', '%m/%d/%Y') 55 | self.time_format = kwargs.get('time_format', '%H:%M:%S') 56 | 57 | self.input_components = {} 58 | 59 | self.components = OrderedDict() 60 | self.component_ids = {} 61 | 62 | self.load_components() 63 | self._input = self._data = FormInput(self) 64 | 65 | def set_builder_by_builder_schema_json(self): 66 | self.builder = Builder( 67 | self.builder_schema_json, 68 | language=self.lang, 69 | component_class_mapping=self.component_class_mapping 70 | ) 71 | 72 | def load_components(self): 73 | for key, component in self.builder.components.items(): 74 | # New object, don't affect the Builder component 75 | component_obj = self.builder.get_component_object(component.raw) 76 | component_obj.load( 77 | component_owner=self, 78 | parent=None, 79 | data=self.form, 80 | all_data=self.form, 81 | is_form=True, 82 | ) 83 | self.components[key] = component_obj 84 | self.component_ids[component_obj.id] = component_obj 85 | 86 | @property 87 | def input(self): 88 | return self._input 89 | 90 | @property 91 | def data(self): 92 | logging.warning('DEPRECATION WARNING: data attr/property shall be deleted in a future version.') 93 | return self._data 94 | 95 | def get_component_by_path(self, component_path): 96 | """ 97 | Get component object by path 98 | 99 | (Eg provided by the Formio.js JS/API). 100 | Especially handy for data Components eg datagridComponent. 101 | 102 | Example path: 103 | dataGrid[0].lastname => lastname in the first row [0] of a datagrid 104 | 105 | # Example path_nodes: 106 | # dataGrid[0].lastname => ['dataGrid[0]', 'lastname'] 107 | 108 | @param component_path: the Formio.js JS/API path 109 | @return component: a Component object 110 | """ 111 | path_nodes = component_path.split('.') 112 | # Example path_nodes: 113 | # dataGrid[0].lastname => ['dataGrid[0]', 'lastname'] 114 | components = self.input_components 115 | for path_node in path_nodes: 116 | # eg: regex search '[0]' in 'dataGrid[0]' 117 | m = re.search(r"\[([A-Za-z0-9_]+)\]", path_node) 118 | if m: 119 | idx_notation = m.group(0) # eg: '[0]', '[1]', etc 120 | idx = int(m.group(1)) # eg: 0, 1, etc 121 | key = path_node.replace(idx_notation, '') 122 | component = components[key] 123 | if hasattr(component, 'rows'): 124 | components = component.rows[idx].input_components 125 | else: 126 | component = components[path_node] 127 | return component 128 | 129 | def validation_errors(self): 130 | """ 131 | @return errors dict: Dictionary where key is component key and 132 | value is a Dictionary with errors. 133 | """ 134 | errors = defaultdict(dict) 135 | for component_key, component in self.input_components.items(): 136 | component_errors = component.validation_errors() 137 | if isinstance(component_errors, dict): 138 | for error_type, val in component_errors.items(): 139 | vals = {error_type: val} 140 | errors[component_key].update(vals) 141 | elif isinstance(component_errors, list): 142 | errors[component_key] = component_errors 143 | return errors 144 | 145 | def render_components(self, force=False): 146 | for key, component in self.input_components.items(): 147 | if force or component.html_component == "": 148 | if component.is_visible: 149 | component.render() 150 | else: 151 | component.html_component = "" 152 | 153 | 154 | class FormInput: 155 | 156 | def __init__(self, form): 157 | self._form = form 158 | 159 | def __getattr__(self, key): 160 | return self._form.input_components.get(key) 161 | -------------------------------------------------------------------------------- /formiodata/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import base64 5 | import requests 6 | import tempfile 7 | import re 8 | 9 | 10 | def base64_encode_url(url): 11 | content = requests.get(url).content 12 | tf = tempfile.TemporaryFile() 13 | tf.write(content) 14 | tf.seek(0) 15 | b64encode = base64.b64encode(tf.read()) 16 | tf.close() 17 | # prefix and decode bytes to str 18 | b64encode = '%s,%s' % ('data:image/png;base64', b64encode.decode()) 19 | return b64encode 20 | 21 | 22 | def decode_resource_template(tmp): 23 | res = re.sub(r"<.*?>", " ", tmp) 24 | strcleaned = re.sub(r'\{{ |\ }}', "", res) 25 | list_kyes = strcleaned.strip().split(".") 26 | return list_kyes[1:] 27 | 28 | 29 | def fetch_dict_get_value(dict_src, list_keys): 30 | if len(list_keys) == 0: 31 | return 32 | node = list_keys[0] 33 | list_keys.remove(node) 34 | nextdict = dict_src.get(node) 35 | if len(list_keys) >= 1: 36 | return fetch_dict_get_value(nextdict, list_keys) 37 | else: 38 | return dict_src.get(node) 39 | -------------------------------------------------------------------------------- /nix/pkgs.nix: -------------------------------------------------------------------------------- 1 | import (builtins.fetchTarball { 2 | url = "https://github.com/NixOS/nixpkgs/archive/2a6732c38dfa8c1a3c8288f2b47a28cbea57a304.tar.gz"; 3 | sha256 = "1s41a8dxf1ci735yjbsq95k3pfi3a21bdf09s22rz2j54yx378qz"; 4 | }) {} 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "formio-data" 3 | version = "2.1.0" 4 | homepage = "https://github.com/novacode-nl/python-formio-data" 5 | description = "formio.js JSON-data API" 6 | readme = "README.md" 7 | authors = ["Bob Leers "] 8 | license = "MIT" 9 | packages = [ { include = "formiodata/**/*.py" } ] 10 | exclude = ["tests/*"] 11 | 12 | [tool.poetry.dependencies] 13 | python = "^3.6" 14 | 15 | python-dateutil = {version = "^2.8.2", python = "<= 3.6" } 16 | requests = "*" 17 | json_logic_qubit = { version = "^0.9.1", optional = true } 18 | 19 | [tool.poetry.extras] 20 | json_logic = ["json_logic_qubit"] 21 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import ./nix/pkgs.nix 3 | }: 4 | 5 | pkgs.mkShell { 6 | name = "xml-jats-converter-shell"; 7 | buildInputs = with pkgs; [ 8 | python38 9 | poetry 10 | ]; 11 | } 12 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import os, sys 5 | sys.path.append(os.path.dirname(os.path.realpath(__file__))) 6 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_json_logic_builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": [ 3 | { 4 | "defaultValue": "", 5 | "id": "edr3y4q", 6 | "inputType": "text", 7 | "dataGridLabel": false, 8 | "refreshOn": "", 9 | "input": true, 10 | "type": "textfield", 11 | "overlay": { 12 | "height": "", 13 | "width": "", 14 | "top": "", 15 | "left": "", 16 | "page": "", 17 | "style": "" 18 | }, 19 | "attributes": { 20 | }, 21 | "logic": [], 22 | "customConditional": "", 23 | "conditional": { 24 | "json": "", 25 | "eq": "", 26 | "when": null, 27 | "show": null 28 | }, 29 | "properties": { 30 | }, 31 | "tags": [], 32 | "key": "username", 33 | "errorLabel": "", 34 | "unique": false, 35 | "validate": { 36 | "unique": false, 37 | "multiple": false, 38 | "strictDateValidation": false, 39 | "maxLength": "", 40 | "minLength": "", 41 | "json": "", 42 | "customPrivate": false, 43 | "custom": "", 44 | "customMessage": "", 45 | "pattern": "", 46 | "required": false 47 | }, 48 | "validateOn": "change", 49 | "allowCalculateOverride": false, 50 | "calculateServer": false, 51 | "calculateValue": "", 52 | "customDefaultValue": "", 53 | "clearOnHide": true, 54 | "redrawOn": "", 55 | "encrypted": false, 56 | "case": "", 57 | "dbIndex": false, 58 | "protected": false, 59 | "inputFormat": "plain", 60 | "persistent": true, 61 | "multiple": false, 62 | "modalEdit": false, 63 | "tableView": true, 64 | "disabled": false, 65 | "spellcheck": true, 66 | "autofocus": false, 67 | "mask": false, 68 | "showCharCount": false, 69 | "showWordCount": false, 70 | "hideLabel": false, 71 | "hidden": false, 72 | "autocomplete": "", 73 | "tabindex": "", 74 | "customClass": "", 75 | "allowMultipleMasks": false, 76 | "inputMask": "", 77 | "widget": { 78 | "type": "input" 79 | }, 80 | "suffix": "", 81 | "prefix": "", 82 | "tooltip": "", 83 | "description": "", 84 | "placeholder": "", 85 | "labelPosition": "top", 86 | "label": "Username" 87 | }, 88 | { 89 | "id": "essijy", 90 | "inputMask": "", 91 | "inputFormat": "plain", 92 | "inputType": "text", 93 | "allowMultipleMasks": false, 94 | "encrypted": false, 95 | "calculateValue": "", 96 | "customDefaultValue": "", 97 | "dbIndex": false, 98 | "dataGridLabel": false, 99 | "refreshOn": "", 100 | "persistent": true, 101 | "unique": false, 102 | "protected": true, 103 | "defaultValue": null, 104 | "multiple": false, 105 | "input": true, 106 | "type": "password", 107 | "overlay": { 108 | "height": "", 109 | "width": "", 110 | "top": "", 111 | "left": "", 112 | "page": "", 113 | "style": "" 114 | }, 115 | "attributes": { 116 | }, 117 | "logic": [], 118 | "customConditional": "", 119 | "conditional": { 120 | "json": "", 121 | "eq": "", 122 | "when": null, 123 | "show": null 124 | }, 125 | "properties": { 126 | }, 127 | "tags": [], 128 | "key": "password", 129 | "errorLabel": "", 130 | "validate": { 131 | "unique": false, 132 | "multiple": false, 133 | "strictDateValidation": false, 134 | "maxLength": "", 135 | "minLength": "", 136 | "json": "", 137 | "customPrivate": false, 138 | "custom": "", 139 | "customMessage": "", 140 | "pattern": "", 141 | "required": false 142 | }, 143 | "validateOn": "change", 144 | "allowCalculateOverride": false, 145 | "calculateServer": false, 146 | "clearOnHide": true, 147 | "redrawOn": "", 148 | "case": "", 149 | "modalEdit": false, 150 | "tableView": false, 151 | "disabled": false, 152 | "spellcheck": true, 153 | "autofocus": false, 154 | "mask": false, 155 | "showCharCount": false, 156 | "showWordCount": false, 157 | "hideLabel": false, 158 | "hidden": false, 159 | "autocomplete": "", 160 | "tabindex": "", 161 | "customClass": "", 162 | "widget": { 163 | "type": "input" 164 | }, 165 | "suffix": "", 166 | "prefix": "", 167 | "tooltip": "", 168 | "description": "", 169 | "placeholder": "", 170 | "labelPosition": "top", 171 | "label": "Password" 172 | }, 173 | { 174 | "defaultValue": "Secret message", 175 | "id": "eyx9rms", 176 | "inputType": "text", 177 | "dataGridLabel": false, 178 | "refreshOn": "", 179 | "input": true, 180 | "type": "textfield", 181 | "overlay": { 182 | "height": "", 183 | "width": "", 184 | "top": "", 185 | "left": "", 186 | "page": "", 187 | "style": "" 188 | }, 189 | "attributes": { 190 | }, 191 | "logic": [], 192 | "customConditional": "", 193 | "conditional": { 194 | "json": { 195 | "and": [ 196 | { 197 | "===": [ 198 | { 199 | "var": "data.username" 200 | }, 201 | "user" 202 | ] 203 | }, 204 | { 205 | "===": [ 206 | { 207 | "var": "data.password" 208 | }, 209 | "secret" 210 | ] 211 | } 212 | ] 213 | }, 214 | "eq": "", 215 | "when": null, 216 | "show": null 217 | }, 218 | "properties": { 219 | }, 220 | "tags": [], 221 | "key": "secret", 222 | "errorLabel": "", 223 | "unique": false, 224 | "validate": { 225 | "unique": false, 226 | "multiple": false, 227 | "strictDateValidation": false, 228 | "maxLength": "", 229 | "minLength": "", 230 | "json": "", 231 | "customPrivate": false, 232 | "custom": "", 233 | "customMessage": "", 234 | "pattern": "", 235 | "required": false 236 | }, 237 | "validateOn": "change", 238 | "allowCalculateOverride": false, 239 | "calculateServer": false, 240 | "calculateValue": "", 241 | "customDefaultValue": "", 242 | "clearOnHide": true, 243 | "redrawOn": "", 244 | "encrypted": false, 245 | "case": "", 246 | "dbIndex": false, 247 | "protected": false, 248 | "inputFormat": "plain", 249 | "persistent": true, 250 | "multiple": false, 251 | "modalEdit": false, 252 | "tableView": true, 253 | "disabled": false, 254 | "spellcheck": true, 255 | "autofocus": false, 256 | "mask": false, 257 | "showCharCount": false, 258 | "showWordCount": false, 259 | "hideLabel": false, 260 | "hidden": false, 261 | "autocomplete": "", 262 | "tabindex": "", 263 | "customClass": "", 264 | "allowMultipleMasks": false, 265 | "inputMask": "", 266 | "widget": { 267 | "type": "input" 268 | }, 269 | "suffix": "", 270 | "prefix": "", 271 | "tooltip": "", 272 | "description": "", 273 | "placeholder": "", 274 | "labelPosition": "top", 275 | "label": "Secret" 276 | }, 277 | { 278 | "id": "eo7g67", 279 | "rightIcon": "", 280 | "leftIcon": "", 281 | "allowMultipleMasks": false, 282 | "properties": { 283 | }, 284 | "showWordCount": false, 285 | "showCharCount": false, 286 | "encrypted": false, 287 | "allowCalculateOverride": false, 288 | "overlay": { 289 | "height": "", 290 | "width": "", 291 | "top": "", 292 | "left": "", 293 | "style": "" 294 | }, 295 | "conditional": { 296 | "eq": "", 297 | "when": null, 298 | "show": null 299 | }, 300 | "validate": { 301 | "unique": false, 302 | "multiple": false, 303 | "strictDateValidation": false, 304 | "customPrivate": false, 305 | "custom": "", 306 | "required": false 307 | }, 308 | "validateOn": "change", 309 | "attributes": { 310 | }, 311 | "widget": { 312 | "type": "input" 313 | }, 314 | "calculateServer": false, 315 | "calculateValue": "", 316 | "customDefaultValue": "", 317 | "dbIndex": false, 318 | "autofocus": false, 319 | "disabled": false, 320 | "tabindex": "", 321 | "hideLabel": false, 322 | "tooltip": "", 323 | "errorLabel": "", 324 | "description": "", 325 | "labelPosition": "top", 326 | "dataGridLabel": true, 327 | "modalEdit": false, 328 | "tableView": false, 329 | "redrawOn": "", 330 | "refreshOn": "", 331 | "clearOnHide": true, 332 | "hidden": false, 333 | "persistent": false, 334 | "unique": false, 335 | "protected": false, 336 | "defaultValue": null, 337 | "multiple": false, 338 | "suffix": "", 339 | "customClass": "", 340 | "prefix": "", 341 | "placeholder": "", 342 | "input": true, 343 | "theme": "primary", 344 | "disableOnInvalid": true, 345 | "action": "submit", 346 | "block": false, 347 | "size": "md", 348 | "key": "submit", 349 | "label": "Submit", 350 | "type": "button" 351 | } 352 | ] 353 | } 354 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_json_logic_hide_secret.json: -------------------------------------------------------------------------------- 1 | {"username": "wrong", "password": "incorrect", "submit": true} 2 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_json_logic_show_secret.json: -------------------------------------------------------------------------------- 1 | {"username": "user", "password": "secret", "submit": true, "secret": "Secret message"} 2 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_nested_json_logic_hide_global_secret_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "wrong", 3 | "password": "incorrect", 4 | "username1": "user", 5 | "password1": "secret", 6 | "username2": "user", 7 | "password2": "secret", 8 | "dataGrid": [ 9 | { 10 | "username3": "user", 11 | "password3": "secret", 12 | "secret3": "Secret message" 13 | }, 14 | { 15 | "username3": "user", 16 | "password3": "secret", 17 | "secret3": "Secret message" 18 | } 19 | ], 20 | "secret1": "Secret message", 21 | "secret2": "Secret message", 22 | "submit": true, 23 | "password3": "", 24 | "username3": "" 25 | } 26 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_nested_json_logic_hide_secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "wrong", 3 | "password": "incorrect", 4 | "username1": "wrong", 5 | "password1": "incorrect", 6 | "username2": "wrong", 7 | "password2": "incorrect", 8 | "dataGrid": [ 9 | { 10 | "username3": "wrong", 11 | "password3": "incorrect" 12 | }, 13 | { 14 | "username3": "wrong", 15 | "password3": "incorrect" 16 | } 17 | ], 18 | "username3": "", 19 | "password3": "", 20 | "submit": true 21 | } 22 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_nested_json_logic_show_global_secret_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "user", 3 | "password": "secret", 4 | "secret": "Secret message", 5 | "username1": "wrong", 6 | "password1": "incorrect", 7 | "username2": "wrong", 8 | "password2": "incorrect", 9 | "dataGrid": [ 10 | { 11 | "username3": "wrong", 12 | "password3": "incorrect", 13 | "globalSecret": "Another secret message" 14 | }, 15 | { 16 | "username3": "wrong", 17 | "password3": "incorrect", 18 | "globalSecret": "Another secret message" 19 | } 20 | ], 21 | "username3": "", 22 | "password3": "", 23 | "submit": true 24 | } 25 | 26 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_nested_json_logic_show_secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "user", 3 | "password": "secret", 4 | "username1": "user", 5 | "password1": "secret", 6 | "username2": "user", 7 | "password2": "secret", 8 | "dataGrid": [ 9 | { 10 | "username3": "user", 11 | "password3": "secret", 12 | "secret3": "Secret message", 13 | "globalSecret": "Another secret message" 14 | }, 15 | { 16 | "username3": "user", 17 | "password3": "secret", 18 | "secret3": "Secret message", 19 | "globalSecret": "Another secret message" 20 | } 21 | ], 22 | "password3": "", 23 | "username3": "", 24 | "secret": "Secret message", 25 | "secret1": "Secret message", 26 | "secret2": "Secret message", 27 | "submit": true 28 | } 29 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_simple_hide_password.json: -------------------------------------------------------------------------------- 1 | { 2 | "submit": true, 3 | "textField": "hide!" 4 | } 5 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_simple_show_selectboxes.json: -------------------------------------------------------------------------------- 1 | { 2 | "textField": "", 3 | "maybePassword": "", 4 | "jobArea": { 5 | "finance": false, 6 | "sales": false, 7 | "technology": true 8 | }, 9 | "submit": true, 10 | "technology": { 11 | "analyst": false, 12 | "developer": true, 13 | "devops": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/data/test_conditional_visibility_simple_show_textfield.json: -------------------------------------------------------------------------------- 1 | { 2 | "maybeTextField": "maybe yes", 3 | "submit": true, 4 | "maybePassword": "hunter2", 5 | "textField": "show!" 6 | } 7 | -------------------------------------------------------------------------------- /tests/data/test_datagrid_in_panel_builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": [ 3 | { 4 | "id": "elmg5xb", 5 | "components": [ 6 | { 7 | "id": "eqou1xj", 8 | "components": [ 9 | { 10 | "defaultValue": null, 11 | "id": "ec0kf000", 12 | "inputType": "text", 13 | "refreshOn": "", 14 | "input": true, 15 | "type": "textfield", 16 | "overlay": { 17 | "height": "", 18 | "width": "", 19 | "top": "", 20 | "left": "", 21 | "page": "", 22 | "style": "" 23 | }, 24 | "attributes": { 25 | }, 26 | "logic": [], 27 | "customConditional": "", 28 | "conditional": { 29 | "json": "", 30 | "eq": "", 31 | "when": null, 32 | "show": null 33 | }, 34 | "properties": { 35 | }, 36 | "tags": [], 37 | "key": "textField", 38 | "errorLabel": "", 39 | "unique": false, 40 | "validate": { 41 | "unique": false, 42 | "multiple": false, 43 | "strictDateValidation": false, 44 | "maxLength": "", 45 | "minLength": "", 46 | "json": "", 47 | "customPrivate": false, 48 | "custom": "", 49 | "customMessage": "", 50 | "pattern": "", 51 | "required": false 52 | }, 53 | "validateOn": "change", 54 | "allowCalculateOverride": false, 55 | "calculateServer": false, 56 | "calculateValue": "", 57 | "customDefaultValue": "", 58 | "clearOnHide": true, 59 | "redrawOn": "", 60 | "encrypted": false, 61 | "case": "", 62 | "dbIndex": false, 63 | "protected": false, 64 | "inputFormat": "plain", 65 | "persistent": true, 66 | "multiple": false, 67 | "modalEdit": false, 68 | "tableView": true, 69 | "disabled": false, 70 | "dataGridLabel": false, 71 | "spellcheck": true, 72 | "autofocus": false, 73 | "mask": false, 74 | "showCharCount": false, 75 | "showWordCount": false, 76 | "hideLabel": false, 77 | "hidden": false, 78 | "autocomplete": "", 79 | "tabindex": "", 80 | "customClass": "", 81 | "allowMultipleMasks": false, 82 | "inputMask": "", 83 | "widget": { 84 | "type": "input" 85 | }, 86 | "suffix": "", 87 | "prefix": "", 88 | "tooltip": "", 89 | "description": "", 90 | "placeholder": "", 91 | "labelPosition": "top", 92 | "label": "Text Field" 93 | } 94 | ], 95 | "tree": true, 96 | "allowMultipleMasks": false, 97 | "showWordCount": false, 98 | "showCharCount": false, 99 | "widget": null, 100 | "dataGridLabel": false, 101 | "refreshOn": "", 102 | "multiple": false, 103 | "suffix": "", 104 | "prefix": "", 105 | "placeholder": "", 106 | "input": true, 107 | "type": "datagrid", 108 | "overlay": { 109 | "height": "", 110 | "width": "", 111 | "top": "", 112 | "left": "", 113 | "page": "", 114 | "style": "" 115 | }, 116 | "attributes": { 117 | }, 118 | "logic": [], 119 | "customConditional": "", 120 | "conditional": { 121 | "json": "", 122 | "eq": "", 123 | "when": null, 124 | "show": null 125 | }, 126 | "properties": { 127 | }, 128 | "tags": [], 129 | "key": "dataGrid", 130 | "errorLabel": "", 131 | "unique": false, 132 | "validate": { 133 | "unique": false, 134 | "multiple": false, 135 | "strictDateValidation": false, 136 | "json": "", 137 | "customPrivate": false, 138 | "custom": "", 139 | "customMessage": "", 140 | "maxLength": "", 141 | "minLength": "", 142 | "required": false 143 | }, 144 | "validateOn": "change", 145 | "allowCalculateOverride": false, 146 | "calculateServer": false, 147 | "calculateValue": "", 148 | "customDefaultValue": "", 149 | "clearOnHide": true, 150 | "redrawOn": "", 151 | "encrypted": false, 152 | "dbIndex": false, 153 | "protected": false, 154 | "persistent": true, 155 | "defaultValue": [ 156 | { 157 | } 158 | ], 159 | "modalEdit": false, 160 | "tableView": false, 161 | "disabled": false, 162 | "autofocus": false, 163 | "hideLabel": false, 164 | "hidden": false, 165 | "tabindex": "", 166 | "customClass": "", 167 | "initEmpty": true, 168 | "enableRowGroups": false, 169 | "layoutFixed": false, 170 | "addAnotherPosition": "bottom", 171 | "addAnother": "", 172 | "reorder": false, 173 | "conditionalAddButton": "", 174 | "disableAddingRemovingRows": false, 175 | "tooltip": "", 176 | "description": "", 177 | "labelPosition": "top", 178 | "label": "Data Grid" 179 | } 180 | ], 181 | "tree": false, 182 | "allowMultipleMasks": false, 183 | "showWordCount": false, 184 | "showCharCount": false, 185 | "encrypted": false, 186 | "allowCalculateOverride": false, 187 | "validate": { 188 | "unique": false, 189 | "multiple": false, 190 | "strictDateValidation": false, 191 | "customPrivate": false, 192 | "custom": "", 193 | "required": false 194 | }, 195 | "validateOn": "change", 196 | "widget": null, 197 | "calculateServer": false, 198 | "calculateValue": "", 199 | "customDefaultValue": "", 200 | "dbIndex": false, 201 | "autofocus": false, 202 | "errorLabel": "", 203 | "description": "", 204 | "labelPosition": "top", 205 | "dataGridLabel": false, 206 | "tableView": false, 207 | "redrawOn": "", 208 | "refreshOn": "", 209 | "clearOnHide": false, 210 | "persistent": false, 211 | "unique": false, 212 | "protected": false, 213 | "defaultValue": null, 214 | "multiple": false, 215 | "suffix": "", 216 | "prefix": "", 217 | "placeholder": "", 218 | "input": false, 219 | "tabindex": "", 220 | "breadcrumb": "default", 221 | "label": "Panel", 222 | "type": "panel", 223 | "overlay": { 224 | "height": "", 225 | "width": "", 226 | "top": "", 227 | "left": "", 228 | "page": "", 229 | "style": "" 230 | }, 231 | "attributes": { 232 | }, 233 | "logic": [], 234 | "conditional": { 235 | "eq": "", 236 | "when": null, 237 | "show": null, 238 | "json": "" 239 | }, 240 | "customConditional": "", 241 | "properties": { 242 | }, 243 | "tags": [], 244 | "key": "panel", 245 | "modalEdit": false, 246 | "disabled": false, 247 | "hideLabel": false, 248 | "hidden": false, 249 | "collapsible": false, 250 | "customClass": "", 251 | "tooltip": "", 252 | "theme": "default", 253 | "title": "Panel" 254 | }, 255 | { 256 | "id": "eqk7hp", 257 | "rightIcon": "", 258 | "leftIcon": "", 259 | "allowMultipleMasks": false, 260 | "properties": { 261 | }, 262 | "showWordCount": false, 263 | "showCharCount": false, 264 | "encrypted": false, 265 | "allowCalculateOverride": false, 266 | "overlay": { 267 | "height": "", 268 | "width": "", 269 | "top": "", 270 | "left": "", 271 | "style": "" 272 | }, 273 | "conditional": { 274 | "eq": "", 275 | "when": null, 276 | "show": null 277 | }, 278 | "validate": { 279 | "unique": false, 280 | "multiple": false, 281 | "strictDateValidation": false, 282 | "customPrivate": false, 283 | "custom": "", 284 | "required": false 285 | }, 286 | "validateOn": "change", 287 | "attributes": { 288 | }, 289 | "widget": { 290 | "type": "input" 291 | }, 292 | "calculateServer": false, 293 | "calculateValue": "", 294 | "customDefaultValue": "", 295 | "dbIndex": false, 296 | "autofocus": false, 297 | "disabled": false, 298 | "tabindex": "", 299 | "hideLabel": false, 300 | "tooltip": "", 301 | "errorLabel": "", 302 | "description": "", 303 | "labelPosition": "top", 304 | "dataGridLabel": true, 305 | "modalEdit": false, 306 | "tableView": false, 307 | "redrawOn": "", 308 | "refreshOn": "", 309 | "clearOnHide": true, 310 | "hidden": false, 311 | "persistent": false, 312 | "unique": false, 313 | "protected": false, 314 | "defaultValue": null, 315 | "multiple": false, 316 | "suffix": "", 317 | "customClass": "", 318 | "prefix": "", 319 | "placeholder": "", 320 | "input": true, 321 | "theme": "primary", 322 | "disableOnInvalid": true, 323 | "action": "submit", 324 | "block": false, 325 | "size": "md", 326 | "key": "submit", 327 | "label": "Submit", 328 | "type": "button" 329 | } 330 | ] 331 | } 332 | -------------------------------------------------------------------------------- /tests/data/test_datagrid_in_panel_one_row_form.json: -------------------------------------------------------------------------------- 1 | {"dataGrid": [{"textField": "testing row 1"}], "submit": true} 2 | -------------------------------------------------------------------------------- /tests/data/test_example_form_check_default.json: -------------------------------------------------------------------------------- 1 | { 2 | "firstName": "Bob", 3 | "signature": "data:image/png;base64,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", 4 | "phoneNumber": "(069) 999-9999", 5 | "survey": { 6 | "overallExperience": "excellent", 7 | "howWasCustomerSupport": "great", 8 | "howWouldYouRateTheFormIoPlatform": "excellent" 9 | }, 10 | "lastName": "Leers", 11 | "favouriteSeason": "autumn", 12 | "favouriteFood": ["mexican", "chinese"], 13 | "cardinalDirection": "south", 14 | "dataGrid": [{"textField": "abc", "checkbox": true}, {"textField": "def", "checkbox": false}], 15 | "uploadBase64": "data:image/gif;base64,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", 16 | "uploadUrl": [{"storage": "url", "name": "31220867-68a92e01-294d-43ae-91b5-5b91f01d9aa1.png", "url": "https://avatars1.githubusercontent.com/u/31220867?s=50&baseUrl=https%3A%2F%2Fapi.form.io&project=&form=/31220867-68a92e01-294d-43ae-91b5-5b91f01d9aa1.png", "size": 847807, "type": "image/png", "data": {"id": 1212, "name": "31220867-68a92e01-294d-43ae-91b5-5b91f01d9aa1.png", "mimetype": "image/png", "file_size": 847807, "access_token": false, "baseUrl": "https://api.form.io", "project": "", "form": ""}, "originalName": "31220867.png"}], 17 | "submit": true 18 | } 19 | -------------------------------------------------------------------------------- /tests/data/test_example_form_empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "firstName": "", 3 | "email": "yourmail@yourlife.io", 4 | "birthdate": "", 5 | "appointmentDateTime": "", 6 | "lastName": "", 7 | "phoneNumber": "", 8 | "cardinalDirection": "", 9 | "favouriteSeason": "", 10 | "favouriteFood": [], 11 | "monthDayYear": "00/00/0000", 12 | "monthYear": "00/00/0000", 13 | "dayMonthYear": "00/00/0000", 14 | "dayMonth": "00/00/0000", 15 | "day": "00/00/0000", 16 | "month": "00/00/0000", 17 | "year": "00/00/0000", 18 | "survey": null, 19 | "signature": "", 20 | "dataGrid": [ 21 | { 22 | "textField": "", 23 | "checkbox": false 24 | } 25 | ], 26 | "uploadBase64": [], 27 | "uploadUrl": [], 28 | "submit": true 29 | } 30 | -------------------------------------------------------------------------------- /tests/data/test_example_form_validation_errors.json: -------------------------------------------------------------------------------- 1 | { 2 | "firstName": "", 3 | "email": "yourmail@yourlife.io", 4 | "birthdate": "", 5 | "appointmentDateTime": "", 6 | "lastName": "", 7 | "phoneNumber": "", 8 | "cardinalDirection": "", 9 | "favouriteSeason": "", 10 | "favouriteFood": [], 11 | "monthDayYear": "00/00/0000", 12 | "monthYear": "00/00/0000", 13 | "dayMonthYear": "00/00/0000", 14 | "dayMonth": "00/00/0000", 15 | "day": "00/00/0000", 16 | "month": "00/00/0000", 17 | "year": "00/00/0000", 18 | "survey": null, 19 | "signature": "", 20 | "dataGrid": [ 21 | { 22 | "textField": "", 23 | "checkbox": false 24 | }, 25 | { 26 | "textField": "Some #1 text here", 27 | "checkbox": false 28 | }, 29 | { 30 | "textField": "", 31 | "checkbox": false 32 | }, 33 | { 34 | "textField": "Some #2 text here", 35 | "checkbox": false 36 | } 37 | ], 38 | "uploadBase64": [], 39 | "uploadUrl": [], 40 | "submit": true 41 | } 42 | -------------------------------------------------------------------------------- /tests/data/test_resources_submission.json: -------------------------------------------------------------------------------- 1 | { 2 | "60034d19942c745a300b32aa": [ 3 | { 4 | "_id": { 5 | "$oid": "601d5419d88e677933e55029" 6 | }, 7 | "data": { 8 | "name": "ResA", 9 | "description": "DescA" 10 | } 11 | }, 12 | { 13 | "_id": { 14 | "$oid": "60034ec3942c74ca500b32b1" 15 | }, 16 | "data": { 17 | "name": "ResB", 18 | "description": "DescB" 19 | } 20 | }, 21 | { 22 | "_id": { 23 | "$oid": "60034eb1942c7410180b32af" 24 | }, 25 | "data": { 26 | "name": "ResC", 27 | "description": "DescC" 28 | } 29 | }, 30 | { 31 | "_id": { 32 | "$oid": "60034e72942c74a3500b32ad" 33 | }, 34 | "data": { 35 | "name": "ResD", 36 | "description": "DescD" 37 | } 38 | } 39 | ], 40 | "60034d19942c745a300b32ac": [ 41 | { 42 | "data": { 43 | "name": "ResA-1", 44 | "description": "DescA" 45 | } 46 | }, 47 | { 48 | "data": { 49 | "name": "ResB-1", 50 | "description": "DescB" 51 | } 52 | }, 53 | { 54 | "data": { 55 | "name": "ResC-1", 56 | "description": "DescC" 57 | } 58 | }, 59 | { 60 | "data": { 61 | "name": "ResD-1", 62 | "description": "DescD" 63 | } 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /tests/test_builder.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_common import CommonTestCase 5 | from formiodata.builder import Builder 6 | 7 | 8 | class BuilderTestCase(CommonTestCase): 9 | 10 | def _builder(self): 11 | return Builder(self.builder_json) 12 | 13 | def test_builder(self): 14 | Builder(self.builder_json) 15 | 16 | def test_components(self): 17 | builder = self._builder() 18 | # NOTE: submit button is not considered a form component 19 | keys = ('firstName', 'email', 'lastName', 'phoneNumber', 'survey', 'signature') 20 | for k in keys: 21 | self.assertIn(k, builder.input_components.keys()) 22 | -------------------------------------------------------------------------------- /tests/test_common.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import json 5 | import unittest 6 | import sys 7 | 8 | from tests.utils import readfile 9 | from formiodata.builder import Builder 10 | from formiodata.form import Form 11 | 12 | 13 | class CommonTestCase(unittest.TestCase): 14 | 15 | def setUp(self): 16 | super(CommonTestCase, self).setUp() 17 | 18 | # test_example_builder.json 19 | # - shown: https://formio.github.io/formio.js/ 20 | # - source: https://examples.form.io/example 21 | self.builder_json = readfile('data', 'test_example_builder.json') 22 | self.builder_resource = readfile('data', 'test_example_builder_with_resource.json') 23 | self.builder_with_resource = readfile('data', 'test_example_builder_with_resource.json') 24 | self.form_json = readfile('data', 'test_example_form.json') 25 | self.form_empty_json = readfile('data', 'test_example_form_empty.json') 26 | self.form_json_check_default = readfile('data', 'test_example_form_check_default.json') 27 | self.form_with_resource = readfile("data", "test_example_form_with_resource.json") 28 | self.builder_json_resource = readfile('data', 'test_resources_submission.json') 29 | 30 | # self.builder = Builder(self.builder_json) 31 | # self.form = Form(self.form_json, None, self.builder_json) 32 | -------------------------------------------------------------------------------- /tests/test_component.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import json 5 | 6 | from requests.exceptions import ConnectionError 7 | 8 | from tests.utils import log_unittest 9 | from tests.test_common import CommonTestCase 10 | 11 | from formiodata.builder import Builder 12 | from formiodata.form import Form 13 | 14 | 15 | class ComponentTestCase(CommonTestCase): 16 | 17 | def setUp(self): 18 | super(ComponentTestCase, self).setUp() 19 | self.builder = Builder(self.builder_json) 20 | self.form = Form(self.form_json, self.builder) 21 | self.form_empty = Form(self.form_empty_json, self.builder) 22 | 23 | self.builder_i18n_nl = Builder(self.builder_json, language='nl', i18n=self._i18n()) 24 | self.form_i18n_nl = Form(self.form_json, self.builder_i18n_nl) 25 | self.form_empty_i18n_nl = Form(self.form_empty_json, self.builder_i18n_nl) 26 | 27 | def _i18n(self): 28 | return { 29 | 'nl': { 30 | 'First Name': 'Voornaam', 31 | 'Last Name': 'Achternaam', 32 | 'Appointment Date / Time': 'Afspraak Tijdstip', 33 | 'Delivery Address': 'Afleveradres', 34 | 'Survey': 'Enquête', 35 | 'excellent': 'uitstekend', 36 | 'great': 'supergoed', 37 | 'My Favourites': 'Mijn favorieten', 38 | 'Favourite Season': 'Favoriete seizoen', 39 | 'Autumn': 'Herfst', 40 | 'Favourite Food': 'Lievelingseten', 41 | 'Cardinal Direction': 'Kardinale richting', 42 | 'North': 'Noord', 43 | 'East': 'Oost', 44 | 'South': 'Zuid', 45 | 'West': 'West', 46 | 'Select Boxes': 'Select aanvink opties', 47 | 'Month Day Year': 'Maand dag jaar', 48 | 'Day Month Year': 'Dag maand jaar', 49 | 'May': 'Mei', 50 | 'Text Field': 'Tekstveld', 51 | 'Upload Base64': 'Upload binair naar ASCII', 52 | 'Upload Url': 'Upload naar locatie', 53 | '{{field}} is required': '{{field}} is verplicht' 54 | } 55 | } 56 | 57 | def test_schema_dict(self): 58 | builder_json = json.loads(self.builder_json) 59 | form_json = json.loads(self.form_json) 60 | self.builder = Builder(builder_json) 61 | self.form = Form(form_json, self.builder) 62 | 63 | self.builder_i18n_nl = Builder(builder_json, language='nl', i18n=self._i18n()) 64 | self.form_i18n_nl = Form(form_json, self.builder_i18n_nl) 65 | 66 | def assertUrlBase64(self, component, expected_base64, log_level='warning'): 67 | try: 68 | self.assertEqual(component.base64, expected_base64) 69 | except ConnectionError as e: 70 | msg = 'Internet access is required, %s...\n%s' % (e.__class__.__name__, e) 71 | log_unittest(self, msg, log_level) 72 | -------------------------------------------------------------------------------- /tests/test_component_address.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.address import addressComponent 6 | 7 | 8 | class addressComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | address = self.builder.input_components['deliveryAddress'] 12 | self.assertIsInstance(address, addressComponent) 13 | 14 | # Not addressComponent 15 | email = self.builder.input_components['email'] 16 | self.assertNotIsInstance(email, addressComponent) 17 | 18 | def test_get_form_empty_address(self): 19 | address = self.form_empty.input_components['deliveryAddress'] 20 | self.assertEqual(address.type, 'address') 21 | self.assertEqual(address.label, 'Delivery Address') 22 | self.assertIsInstance(address.value, dict) 23 | self.assertEqual(address.value, {}) 24 | 25 | # parts 26 | # TODO lat, lon (coordinates) 27 | self.assertIsNone(address.postal_code) 28 | self.assertIsNone(address.street_name) 29 | self.assertIsNone(address.street_number) 30 | self.assertIsNone(address.city) 31 | self.assertIsNone(address.country) 32 | 33 | def test_get_form_address(self): 34 | address = self.form.input_components['deliveryAddress'] 35 | self.assertEqual(address.type, 'address') 36 | self.assertEqual(address.label, 'Delivery Address') 37 | self.assertIsNotNone(address.value) 38 | 39 | # parts 40 | # TODO lat, lon (coordinates) 41 | self.assertEqual(address.postal_code, '1017 CT') 42 | self.assertEqual(address.street_name, 'Rembrandtplein') 43 | self.assertEqual(address.street_number, '33') 44 | self.assertEqual(address.city, 'Amsterdam') 45 | self.assertEqual(address.country, 'Netherlands') 46 | self.assertEqual(address.country_code, 'NL') 47 | 48 | # i18n translations 49 | def test_get_label_i18n_nl(self): 50 | address = self.builder_i18n_nl.input_components['deliveryAddress'] 51 | self.assertEqual(address.label, 'Afleveradres') 52 | 53 | def test_get_form_data_i18n_nl(self): 54 | self.assertEqual(self.form_i18n_nl.input.deliveryAddress.label, 'Afleveradres') 55 | -------------------------------------------------------------------------------- /tests/test_component_class_mapping.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import json 5 | 6 | from test_common import CommonTestCase 7 | from formiodata.builder import Builder 8 | from formiodata.components.editgrid import editgridComponent 9 | 10 | 11 | class ComponentClassMappingTestCase(CommonTestCase): 12 | 13 | def setUp(self): 14 | super().setUp() 15 | 16 | schema_dict = json.loads(self.builder_json) 17 | for comp in schema_dict['components']: 18 | if comp['key'] == 'editGrid': 19 | comp['type'] = 'custom_editgrid' 20 | 21 | self.schema_json_component_class_mapping = json.dumps(schema_dict) 22 | 23 | def test_component_class_mapping_with_class(self): 24 | component_class_mapping = {'custom_editgrid': editgridComponent} 25 | builder = Builder( 26 | self.schema_json_component_class_mapping, 27 | component_class_mapping=component_class_mapping, 28 | ) 29 | custom_editgrid = builder.components['editGrid'] 30 | self.assertIsInstance(custom_editgrid, editgridComponent) 31 | self.assertEqual(custom_editgrid.type, 'custom_editgrid') 32 | 33 | def test_component_class_mapping_with_string(self): 34 | component_class_mapping = {'custom_editgrid': 'editgrid'} 35 | builder = Builder( 36 | self.schema_json_component_class_mapping, 37 | component_class_mapping=component_class_mapping, 38 | ) 39 | custom_editgrid = builder.components['editGrid'] 40 | self.assertIsInstance(custom_editgrid, editgridComponent) 41 | self.assertEqual(custom_editgrid.type, 'custom_editgrid') 42 | -------------------------------------------------------------------------------- /tests/test_component_datagrid.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.datagrid import datagridComponent 6 | 7 | 8 | class datagridComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | dataGrid = self.builder.components['dataGrid'] 12 | self.assertIsInstance(dataGrid, datagridComponent) 13 | email = self.builder.input_components['email'] 14 | self.assertNotIsInstance(email, datagridComponent) 15 | 16 | def test_get_key(self): 17 | dataGrid = self.builder.components['dataGrid'] 18 | self.assertEqual(dataGrid.key, 'dataGrid') 19 | 20 | def test_get_type(self): 21 | dataGrid = self.builder.components['dataGrid'] 22 | self.assertEqual(dataGrid.type, 'datagrid') 23 | 24 | def test_get_label(self): 25 | dataGrid = self.builder.components['dataGrid'] 26 | self.assertEqual(dataGrid.label, 'Data Grid') 27 | 28 | def test_paths(self): 29 | dataGrid = self.builder.components['dataGrid'] 30 | # datagrid 31 | self.assertEqual(dataGrid.builder_path_key, ['dataGrid']) 32 | self.assertEqual(dataGrid.builder_path_label, ['Data Grid']) 33 | self.assertEqual(dataGrid.builder_input_path_key, ['dataGrid']) 34 | self.assertEqual(dataGrid.builder_input_path_label, ['Data Grid']) 35 | # datagrid inputs 36 | for pos, row_with_components in enumerate(dataGrid.rows): 37 | for component in row_with_components.input_components.values(): 38 | if component.key == 'textField': 39 | self.assertEqual(component.builder_path_key, ['dataGrid', 'textField']) 40 | self.assertEqual(component.builder_path_label, ['Data Grid', 'Text Field']) 41 | self.assertEqual(component.builder_input_path_key, ['dataGrid', 'textField']) 42 | self.assertEqual(component.builder_input_path_label, ['Data Grid', 'Text Field']) 43 | if component.key == 'checkbox': 44 | self.assertEqual(component.builder_path_key, ['dataGrid', 'checkbox']) 45 | self.assertEqual(component.builder_path_label, ['Data Grid', 'Checkbox']) 46 | self.assertEqual(component.builder_input_path_key, ['dataGrid', 'checkbox']) 47 | self.assertEqual(component.builder_input_path_label, ['Data Grid', 'Checkbox']) 48 | 49 | def test_get_row_labels(self): 50 | builder_dataGrid = self.builder.components['dataGrid'] 51 | dataGrid = self.form.input_components[builder_dataGrid.key] 52 | 53 | self.assertEqual(len(dataGrid.rows), 2) 54 | 55 | labels = ['Text Field', 'Checkbox'] 56 | for key, label in dataGrid.labels.items(): 57 | self.assertIn(label , labels) 58 | 59 | def test_get_rows_values(self): 60 | builder_dataGrid = self.builder.components['dataGrid'] 61 | dataGrid = self.form.input_components[builder_dataGrid.key] 62 | 63 | self.assertEqual(len(dataGrid.rows), 2) 64 | 65 | textField_values = ['abc', 'def'] 66 | checkbox_values = [True, False] 67 | for pos, row_with_components in enumerate(dataGrid.rows): 68 | for component in row_with_components.input_components.values(): 69 | if component.key == 'textField': 70 | self.assertEqual(textField_values[pos], component.value) 71 | if component.key == 'checkbox': 72 | self.assertEqual(checkbox_values[pos], component.value) 73 | -------------------------------------------------------------------------------- /tests/test_component_datetime.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from datetime import datetime, date 5 | 6 | from test_component import ComponentTestCase 7 | from formiodata.components.datetime import datetimeComponent 8 | 9 | 10 | class datetimeComponentTestCase(ComponentTestCase): 11 | 12 | def test_object(self): 13 | # datetimeComponent 14 | birthdate = self.builder.input_components['birthdate'] 15 | self.assertIsInstance(birthdate, datetimeComponent) 16 | 17 | appointmentDateTime = self.builder.input_components['appointmentDateTime'] 18 | self.assertIsInstance(appointmentDateTime, datetimeComponent) 19 | 20 | # Not datetimeComponent 21 | email = self.builder.input_components['email'] 22 | self.assertNotIsInstance(email, datetimeComponent) 23 | 24 | def test_get_key(self): 25 | birthdate = self.builder.input_components['birthdate'] 26 | self.assertEqual(birthdate.key, 'birthdate') 27 | 28 | appointmentDateTime = self.builder.input_components['appointmentDateTime'] 29 | self.assertEqual(appointmentDateTime.key, 'appointmentDateTime') 30 | 31 | def test_get_type(self): 32 | birthdate = self.builder.input_components['birthdate'] 33 | self.assertEqual(birthdate.type, 'datetime') 34 | 35 | appointmentDateTime = self.builder.input_components['appointmentDateTime'] 36 | self.assertEqual(appointmentDateTime.type, 'datetime') 37 | 38 | def test_get_label(self): 39 | birthdate = self.builder.input_components['birthdate'] 40 | self.assertEqual(birthdate.label, 'Birthdate') 41 | 42 | appointmentDateTime = self.builder.input_components['appointmentDateTime'] 43 | self.assertEqual(appointmentDateTime.label, 'Appointment Date / Time') 44 | 45 | def test_set_label(self): 46 | birthdate = self.builder.input_components['birthdate'] 47 | self.assertEqual(birthdate.label, 'Birthdate') 48 | birthdate.label = 'Born On' 49 | self.assertEqual(birthdate.label, 'Born On') 50 | 51 | appointmentDateTime = self.builder.input_components['appointmentDateTime'] 52 | self.assertEqual(appointmentDateTime.label, 'Appointment Date / Time') 53 | appointmentDateTime.label = 'Appointment On' 54 | self.assertEqual(appointmentDateTime.label, 'Appointment On') 55 | 56 | def test_get_form(self): 57 | birthdate = self.form.input_components['birthdate'] 58 | self.assertEqual(birthdate.label, 'Birthdate') 59 | self.assertEqual(birthdate.value, '1999-12-31T12:00:00+01:00') 60 | self.assertEqual(birthdate.type, 'datetime') 61 | 62 | appointmentDateTime = self.form.input_components['appointmentDateTime'] 63 | self.assertEqual(appointmentDateTime.label, 'Appointment Date / Time') 64 | self.assertEqual(appointmentDateTime.value, '2021-02-26T12:30:00+01:00') 65 | self.assertEqual(appointmentDateTime.type, 'datetime') 66 | 67 | def test_get_form_data(self): 68 | birthdate = self.form.input.birthdate 69 | self.assertEqual(birthdate.label, 'Birthdate') 70 | self.assertEqual(birthdate.value, '1999-12-31T12:00:00+01:00') 71 | self.assertEqual(birthdate.type, 'datetime') 72 | 73 | appointmentDateTime = self.form.input.appointmentDateTime 74 | self.assertEqual(appointmentDateTime.label, 'Appointment Date / Time') 75 | self.assertEqual(appointmentDateTime.value, '2021-02-26T12:30:00+01:00') 76 | self.assertEqual(appointmentDateTime.type, 'datetime') 77 | 78 | def test_to_datetime(self): 79 | birthdate = self.form.input.birthdate 80 | self.assertIsInstance(birthdate.to_datetime(), datetime) 81 | self.assertIsInstance(birthdate.to_datetime().date(), date) 82 | 83 | appointmentDateTime = self.form.input.appointmentDateTime 84 | self.assertIsInstance(appointmentDateTime.to_datetime(), datetime) 85 | 86 | # i18n translations 87 | def test_get_label_i18n_nl(self): 88 | appointmentDateTime = self.builder_i18n_nl.input_components['appointmentDateTime'] 89 | self.assertEqual(appointmentDateTime.label, 'Afspraak Tijdstip') 90 | 91 | def test_get_form_data_i18n_nl(self): 92 | self.assertEqual(self.form_i18n_nl.input.appointmentDateTime.label, 'Afspraak Tijdstip') 93 | -------------------------------------------------------------------------------- /tests/test_component_day.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from collections import OrderedDict 5 | 6 | from test_component import ComponentTestCase 7 | from formiodata.components.day import dayComponent 8 | 9 | 10 | class dayComponentTestCase(ComponentTestCase): 11 | 12 | def test_object(self): 13 | # dayComponent 14 | monthDayYear = self.builder.input_components['monthDayYear'] 15 | self.assertIsInstance(monthDayYear, dayComponent) 16 | 17 | monthYear = self.builder.input_components['monthYear'] 18 | self.assertIsInstance(monthYear, dayComponent) 19 | 20 | dayMonthYear = self.builder.input_components['dayMonthYear'] 21 | self.assertIsInstance(dayMonthYear, dayComponent) 22 | 23 | dayMonth = self.builder.input_components['dayMonth'] 24 | self.assertIsInstance(dayMonth, dayComponent) 25 | 26 | day = self.builder.input_components['day'] 27 | self.assertIsInstance(day, dayComponent) 28 | 29 | month = self.builder.input_components['month'] 30 | self.assertIsInstance(month, dayComponent) 31 | 32 | year = self.builder.input_components['year'] 33 | self.assertIsInstance(year, dayComponent) 34 | 35 | # Not dayComponent 36 | email = self.builder.input_components['email'] 37 | self.assertNotIsInstance(email, dayComponent) 38 | 39 | def test_get_form_empty_monthDayYear(self): 40 | monthDayYear = self.form_empty.input_components['monthDayYear'] 41 | self.assertEqual(monthDayYear.type, 'day') 42 | self.assertEqual(monthDayYear.label, 'Month Day Year') 43 | self.assertIsInstance(monthDayYear.value, OrderedDict) 44 | self.assertEqual(monthDayYear.value, {'month': None, 'day': None, 'year': None}) 45 | # parts 46 | self.assertIsNone(monthDayYear.day) 47 | self.assertIsNone(monthDayYear.month) 48 | self.assertIsNone(monthDayYear.year) 49 | 50 | def test_get_form_monthDayYear(self): 51 | monthDayYear = self.form.input_components['monthDayYear'] 52 | self.assertEqual(monthDayYear.type, 'day') 53 | self.assertEqual(monthDayYear.label, 'Month Day Year') 54 | self.assertEqual(monthDayYear.value, {'month': 5, 'day': 16, 'year': 2021}) 55 | # parts 56 | self.assertEqual(monthDayYear.day, 16) 57 | self.assertEqual(monthDayYear.month, 5) 58 | self.assertEqual(monthDayYear.month_name, 'May') 59 | self.assertEqual(monthDayYear.year, 2021) 60 | # extra test 61 | self.assertIsInstance(monthDayYear.value, OrderedDict) 62 | 63 | def test_get_form_monthYear(self): 64 | monthYear = self.form.input_components['monthYear'] 65 | self.assertEqual(monthYear.type, 'day') 66 | self.assertEqual(monthYear.label, 'Month Year') 67 | self.assertEqual(monthYear.value, {'month': 5, 'month': 5, 'year': 2021}) 68 | # parts 69 | self.assertIsNone(monthYear.day) 70 | self.assertEqual(monthYear.month, 5) 71 | self.assertEqual(monthYear.month_name, 'May') 72 | self.assertEqual(monthYear.year, 2021) 73 | 74 | def test_get_form_dayMonthYear(self): 75 | dayMonthYear = self.form.input_components['dayMonthYear'] 76 | self.assertEqual(dayMonthYear.type, 'day') 77 | self.assertEqual(dayMonthYear.label, 'Day Month Year') 78 | self.assertEqual(dayMonthYear.value, {'day': 16, 'month': 5, 'year': 2021}) 79 | # parts 80 | self.assertEqual(dayMonthYear.day, 16) 81 | self.assertEqual(dayMonthYear.month, 5) 82 | self.assertEqual(dayMonthYear.month_name, 'May') 83 | self.assertEqual(dayMonthYear.year, 2021) 84 | 85 | def test_get_form_dayMonth(self): 86 | dayMonth = self.form.input_components['dayMonth'] 87 | self.assertEqual(dayMonth.type, 'day') 88 | self.assertEqual(dayMonth.label, 'Day Month') 89 | self.assertEqual(dayMonth.value, {'day': 16, 'month': 5}) 90 | # parts 91 | self.assertEqual(dayMonth.day, 16) 92 | self.assertEqual(dayMonth.month, 5) 93 | self.assertEqual(dayMonth.month_name, 'May') 94 | self.assertIsNone(dayMonth.year) 95 | 96 | def test_get_form_day(self): 97 | day = self.form.input_components['day'] 98 | self.assertEqual(day.type, 'day') 99 | self.assertEqual(day.label, 'Day') 100 | self.assertEqual(day.value, {'day': 16}) 101 | # parts 102 | self.assertEqual(day.day, 16) 103 | self.assertIsNone(day.month) 104 | self.assertIsNone(day.year) 105 | 106 | def test_get_form_month(self): 107 | month = self.form.input_components['month'] 108 | self.assertEqual(month.type, 'day') 109 | self.assertEqual(month.label, 'Month') 110 | self.assertEqual(month.value, {'month': 5}) 111 | # parts 112 | self.assertIsNone(month.day) 113 | self.assertEqual(month.month, 5) 114 | self.assertEqual(month.month_name, 'May') 115 | self.assertIsNone(month.year) 116 | 117 | def test_get_form_year(self): 118 | year = self.form.input_components['year'] 119 | self.assertEqual(year.type, 'day') 120 | self.assertEqual(year.label, 'Year') 121 | self.assertEqual(year.value, {'year': 2021}) 122 | # parts 123 | self.assertIsNone(year.day) 124 | self.assertIsNone(year.month) 125 | self.assertEqual(year.year, 2021) 126 | 127 | # i18n translations 128 | def test_get_label_i18n_nl(self): 129 | # We won't test all here 130 | monthDayYear = self.builder_i18n_nl.input_components['monthDayYear'] 131 | self.assertEqual(monthDayYear.label, 'Maand dag jaar') 132 | 133 | dayMonthYear = self.builder_i18n_nl.input_components['dayMonthYear'] 134 | self.assertEqual(dayMonthYear.label, 'Dag maand jaar') 135 | 136 | def test_get_form_data_i18n_nl(self): 137 | self.assertEqual(self.form_i18n_nl.input.dayMonthYear.label, 'Dag maand jaar') 138 | 139 | monthDayYear = self.form_i18n_nl.input_components['monthDayYear'] 140 | self.assertEqual(monthDayYear.month_name, 'Mei') 141 | 142 | dayMonthYear = self.form_i18n_nl.input_components['dayMonthYear'] 143 | self.assertEqual(dayMonthYear.month_name, 'Mei') 144 | 145 | dayMonth = self.form_i18n_nl.input_components['dayMonth'] 146 | self.assertEqual(dayMonth.month_name, 'Mei') 147 | 148 | monthYear = self.form_i18n_nl.input_components['monthYear'] 149 | self.assertEqual(monthYear.month_name, 'Mei') 150 | 151 | month = self.form_i18n_nl.input_components['month'] 152 | self.assertEqual(month.month_name, 'Mei') 153 | -------------------------------------------------------------------------------- /tests/test_component_editgrid.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.editgrid import editgridComponent 6 | 7 | 8 | class editgridComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | editgrid = self.builder.components['editGrid'] 12 | self.assertIsInstance(editgrid, editgridComponent) 13 | email = self.builder.input_components['email'] 14 | self.assertNotIsInstance(email, editgridComponent) 15 | 16 | def test_get_key(self): 17 | editGrid = self.builder.components['editGrid'] 18 | self.assertEqual(editGrid.key, 'editGrid') 19 | 20 | def test_get_type(self): 21 | editGrid = self.builder.components['editGrid'] 22 | self.assertEqual(editGrid.type, 'editgrid') 23 | 24 | def test_get_label(self): 25 | editGrid = self.builder.components['editGrid'] 26 | self.assertEqual(editGrid.label, 'Edit Grid') 27 | 28 | def test_paths(self): 29 | editGrid = self.builder.components['editGrid'] 30 | # editgrid 31 | self.assertEqual(editGrid.builder_path_key, ['editGrid']) 32 | self.assertEqual(editGrid.builder_path_label, ['Edit Grid']) 33 | self.assertEqual(editGrid.builder_input_path_key, ['editGrid']) 34 | self.assertEqual(editGrid.builder_input_path_label, ['Edit Grid']) 35 | # editgrid inputs 36 | for pos, row_with_components in enumerate(editGrid.rows): 37 | for component in row_with_components.input_components.values(): 38 | if component.key == 'textfield': 39 | self.assertEqual(component.builder_path_key, ['editGrid', 'textField']) 40 | self.assertEqual(component.builder_path_label, ['Edit Grid', 'Text Field']) 41 | self.assertEqual(component.builder_input_path_key, ['editGrid', 'textField']) 42 | self.assertEqual(component.builder_input_path_label, ['Edit Grid', 'Text Field']) 43 | if component.key == 'date': 44 | self.assertEqual(component.builder_path_key, ['editGrid', 'datetime']) 45 | self.assertEqual(component.builder_path_label, ['Edit Grid', 'Date']) 46 | self.assertEqual(component.builder_input_path_key, ['editGrid', 'datetime']) 47 | self.assertEqual(component.builder_input_path_label, ['Edit Grid', 'Date']) 48 | if component.key == 'textArea': 49 | self.assertEqual(component.builder_path_key, ['editGrid', 'textArea']) 50 | self.assertEqual(component.builder_path_label, ['Edit Grid', 'Text Area']) 51 | self.assertEqual(component.builder_input_path_key, ['editGrid', 'textArea']) 52 | self.assertEqual(component.builder_input_path_label, ['Edit Grid', 'Text Area']) 53 | if component.key == 'number': 54 | self.assertEqual(component.builder_path_key, ['editGrid', 'number']) 55 | self.assertEqual(component.builder_path_label, ['Edit Grid', 'Number']) 56 | self.assertEqual(component.builder_input_path_key, ['editGrid', 'number']) 57 | self.assertEqual(component.builder_input_path_label, ['Edit Grid', 'Number']) 58 | if component.key == 'checkbox': 59 | self.assertEqual(component.builder_path_key, ['editGrid', 'checkbox']) 60 | self.assertEqual(component.builder_path_label, ['Edit Grid', 'Checkbox']) 61 | self.assertEqual(component.builder_input_path_key, ['editGrid', 'checkbox']) 62 | self.assertEqual(component.builder_input_path_label, ['Edit Grid', 'Checkbox']) 63 | 64 | def test_get_row_labels(self): 65 | builder_editGrid = self.builder.components['editGrid'] 66 | editGrid = self.form.input_components[builder_editGrid.key] 67 | 68 | self.assertEqual(len(editGrid.rows), 3) 69 | 70 | labels = ['Text Field', 'Text Area', 'Date', 'Select', 'Number', 'Checkbox'] 71 | for key, label in editGrid.labels.items(): 72 | self.assertIn(label , labels) 73 | 74 | def test_get_rows_values(self): 75 | builder_editGrid = self.builder.components['editGrid'] 76 | editGrid = self.form.input_components[builder_editGrid.key] 77 | 78 | self.assertEqual(len(editGrid.rows), 3) 79 | 80 | textField_values = ['This is the FIRST row', 'This is the SECOND row', 'This is the THIRD row'] 81 | date_values = ['2023-01-02', '2023-05-26', '2023-07-03'] 82 | number_values = [123, 789, 456] 83 | checkbox_values = [True, False, False] 84 | textArea_values = ['The first textarea.\nSuch nice!', '', ''] 85 | for pos, row_with_components in enumerate(editGrid.rows): 86 | for component in row_with_components.input_components.values(): 87 | if component.key == 'textField': 88 | self.assertEqual(textField_values[pos], component.value) 89 | if component.key == 'date': 90 | self.assertIn(date_values[pos], component.value) 91 | if component.key == 'textArea': 92 | self.assertIn(textArea_values[pos], component.value) 93 | if component.key == 'number': 94 | self.assertIn(number_values[pos], component.value) 95 | if component.key == 'checkbox': 96 | self.assertIn(checkbox_values[pos], component.value) 97 | -------------------------------------------------------------------------------- /tests/test_component_email.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.email import emailComponent 6 | 7 | 8 | class emailComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # EmailComponent 12 | email = self.builder.input_components['email'] 13 | self.assertIsInstance(email, emailComponent) 14 | 15 | # Not EmailComponent 16 | firstName = self.builder.input_components['firstName'] 17 | self.assertNotIsInstance(firstName, emailComponent) 18 | 19 | def test_get_key(self): 20 | email = self.builder.input_components['email'] 21 | self.assertEqual(email.key, 'email') 22 | 23 | def test_get_type(self): 24 | email = self.builder.input_components['email'] 25 | self.assertEqual(email.type, 'email') 26 | 27 | def test_get_label(self): 28 | email = self.builder.input_components['email'] 29 | self.assertEqual(email.label, 'Email') 30 | 31 | def test_set_label(self): 32 | email = self.builder.input_components['email'] 33 | self.assertEqual(email.label, 'Email') 34 | email.label = 'Foobar' 35 | self.assertEqual(email.label, 'Foobar') 36 | 37 | def test_get_form(self): 38 | email = self.form.input_components['email'] 39 | self.assertEqual(email.label, 'Email') 40 | self.assertEqual(email.value, 'bob@novacode.nl') 41 | self.assertEqual(email.type, 'email') 42 | 43 | def test_get_form_data(self): 44 | email = self.form.input.email 45 | self.assertEqual(email.label, 'Email') 46 | self.assertEqual(email.value, 'bob@novacode.nl') 47 | self.assertEqual(email.type, 'email') 48 | -------------------------------------------------------------------------------- /tests/test_component_file_storage_base64.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.file import fileComponent 6 | 7 | 8 | class fileComponentStorageBase64TestCase(ComponentTestCase): 9 | 10 | def setUp(self): 11 | super(fileComponentStorageBase64TestCase, self).setUp() 12 | 13 | self.image_value_base64 = "data:image/gif;base64,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" 14 | 15 | def test_object(self): 16 | uploadBase64 = self.builder.input_components['uploadBase64'] 17 | self.assertIsInstance(uploadBase64, fileComponent) 18 | 19 | # Not fileComponent 20 | email = self.builder.input_components['email'] 21 | self.assertNotIsInstance(email, fileComponent) 22 | 23 | def test_get_key(self): 24 | uploadBase64 = self.builder.input_components['uploadBase64'] 25 | self.assertEqual(uploadBase64.key, 'uploadBase64') 26 | 27 | def test_get_type(self): 28 | uploadBase64 = self.builder.input_components['uploadBase64'] 29 | self.assertEqual(uploadBase64.type, 'file') 30 | 31 | def test_get_label(self): 32 | uploadBase64 = self.builder.input_components['uploadBase64'] 33 | self.assertEqual(uploadBase64.label, 'Upload Base64') 34 | 35 | def test_set_label(self): 36 | uploadBase64 = self.builder.input_components['uploadBase64'] 37 | self.assertEqual(uploadBase64.label, 'Upload Base64') 38 | uploadBase64.label = 'Foobar' 39 | self.assertEqual(uploadBase64.label, 'Foobar') 40 | 41 | def test_get_form(self): 42 | uploadBase64 = self.form.components['uploadBase64'] 43 | self.assertEqual(uploadBase64.label, 'Upload Base64') 44 | self.assertEqual(uploadBase64.type, 'file') 45 | self.assertEqual(uploadBase64.storage, 'base64') 46 | self.assertEqual(uploadBase64.value, self.image_value_base64) 47 | 48 | def test_get_form_data(self): 49 | uploadBase64 = self.form.input.uploadBase64 50 | self.assertEqual(uploadBase64.label, 'Upload Base64') 51 | self.assertEqual(uploadBase64.type, 'file') 52 | self.assertEqual(uploadBase64.storage, 'base64') 53 | self.assertEqual(uploadBase64.value, self.image_value_base64) 54 | 55 | # i18n translations 56 | def test_get_label_i18n_nl(self): 57 | uploadBase64 = self.builder_i18n_nl.input_components['uploadBase64'] 58 | self.assertEqual(uploadBase64.label, 'Upload binair naar ASCII') 59 | 60 | def test_get_form_data_i18n_nl(self): 61 | self.assertEqual(self.form_i18n_nl.input.uploadBase64.label, 'Upload binair naar ASCII') 62 | -------------------------------------------------------------------------------- /tests/test_component_file_storage_url.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.file import fileComponent 6 | 7 | 8 | class fileComponentStorageUrlTestCase(ComponentTestCase): 9 | 10 | def setUp(self): 11 | super(fileComponentStorageUrlTestCase, self).setUp() 12 | 13 | self.image_url = 'https://avatars1.githubusercontent.com/u/31220867?s=50' 14 | self.image_value_base64 = 'data:image/png;base64,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' 15 | 16 | def test_object(self): 17 | uploadUrl = self.builder.input_components['uploadUrl'] 18 | self.assertIsInstance(uploadUrl, fileComponent) 19 | 20 | # Not fileComponent 21 | email = self.builder.input_components['email'] 22 | self.assertNotIsInstance(email, fileComponent) 23 | 24 | def test_get_key(self): 25 | uploadUrl = self.builder.input_components['uploadUrl'] 26 | self.assertEqual(uploadUrl.key, 'uploadUrl') 27 | 28 | def test_get_type(self): 29 | uploadUrl = self.builder.input_components['uploadUrl'] 30 | self.assertEqual(uploadUrl.type, 'file') 31 | 32 | def test_get_label(self): 33 | uploadUrl = self.builder.input_components['uploadUrl'] 34 | self.assertEqual(uploadUrl.label, 'Upload Url') 35 | 36 | def test_set_label(self): 37 | uploadUrl = self.builder.input_components['uploadUrl'] 38 | self.assertEqual(uploadUrl.label, 'Upload Url') 39 | uploadUrl.label = 'RAW Image' 40 | self.assertEqual(uploadUrl.label, 'RAW Image') 41 | 42 | def test_get_form(self): 43 | uploadUrl = self.form.components['uploadUrl'] 44 | self.assertEqual(uploadUrl.label, 'Upload Url') 45 | self.assertEqual(uploadUrl.type, 'file') 46 | self.assertEqual(uploadUrl.storage, 'url') 47 | self.assertUrlBase64(uploadUrl, self.image_value_base64) 48 | 49 | 50 | def test_get_form_data(self): 51 | uploadUrl = self.form.input.uploadUrl 52 | self.assertEqual(uploadUrl.label, 'Upload Url') 53 | self.assertEqual(uploadUrl.type, 'file') 54 | self.assertEqual(uploadUrl.storage, 'url') 55 | self.assertUrlBase64(uploadUrl, self.image_value_base64) 56 | 57 | # i18n translations 58 | def test_get_label_i18n_nl(self): 59 | uploadUrl = self.builder_i18n_nl.input_components['uploadUrl'] 60 | self.assertEqual(uploadUrl.label, 'Upload naar locatie') 61 | 62 | def test_get_form_data_i18n_nl(self): 63 | self.assertEqual(self.form_i18n_nl.input.uploadUrl.label, 'Upload naar locatie') 64 | -------------------------------------------------------------------------------- /tests/test_component_number.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.number import numberComponent 6 | 7 | 8 | class numberComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | howManySeats = self.builder.input_components['howManySeats'] 12 | self.assertIsInstance(howManySeats, numberComponent) 13 | 14 | # Not numberComponent 15 | firstName = self.builder.input_components['firstName'] 16 | self.assertNotIsInstance(firstName, numberComponent) 17 | 18 | def test_get_key(self): 19 | howManySeats = self.builder.input_components['howManySeats'] 20 | self.assertEqual(howManySeats.key, 'howManySeats') 21 | 22 | def test_get_type(self): 23 | howManySeats = self.builder.input_components['howManySeats'] 24 | self.assertEqual(howManySeats.type, 'number') 25 | 26 | def test_get_label(self): 27 | howManySeats = self.builder.input_components['howManySeats'] 28 | self.assertEqual(howManySeats.label, 'How Many Seats?') 29 | 30 | def test_set_label(self): 31 | howManySeats = self.builder.input_components['howManySeats'] 32 | self.assertEqual(howManySeats.label, 'How Many Seats?') 33 | howManySeats.label = 'Foobar?' 34 | self.assertEqual(howManySeats.label, 'Foobar?') 35 | 36 | def test_get_form(self): 37 | howManySeats = self.form.input_components['howManySeats'] 38 | self.assertEqual(howManySeats.label, 'How Many Seats?') 39 | self.assertEqual(howManySeats.value, 4) 40 | self.assertEqual(howManySeats.type, 'number') 41 | 42 | def test_get_form_data(self): 43 | howManySeats = self.form.input.howManySeats 44 | self.assertEqual(howManySeats.label, 'How Many Seats?') 45 | self.assertEqual(howManySeats.value, 4) 46 | self.assertEqual(howManySeats.type, 'number') 47 | -------------------------------------------------------------------------------- /tests/test_component_panel.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.panel import panelComponent 6 | 7 | 8 | class panelComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # TextfieldComponent 12 | panel = self.builder.components['panel'] 13 | self.assertIsInstance(panel, panelComponent) 14 | 15 | # Not TextfieldComponent 16 | email = self.builder.input_components['email'] 17 | self.assertNotIsInstance(email, panelComponent) 18 | 19 | def test_get_key(self): 20 | panel = self.builder.components['panel'] 21 | self.assertEqual(panel.key, 'panel') 22 | 23 | def test_get_type(self): 24 | panel = self.builder.components['panel'] 25 | self.assertEqual(panel.type, 'panel') 26 | 27 | def test_get_label(self): 28 | panel = self.builder.components['panel'] 29 | self.assertEqual(panel.label, 'Panel') 30 | 31 | def test_get_title(self): 32 | panel = self.builder.components['panel'] 33 | self.assertEqual(panel.title, 'My Favourites') 34 | 35 | # i18n translations 36 | def test_get_title_i18n_nl(self): 37 | panel = self.builder_i18n_nl.components['panel'] 38 | self.assertEqual(panel.title, 'Mijn favorieten') 39 | -------------------------------------------------------------------------------- /tests/test_component_phoneNumber.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.phoneNumber import phoneNumberComponent 6 | 7 | 8 | class phoneNumberComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # phoneNumberComponent 12 | phoneNumber = self.builder.input_components['phoneNumber'] 13 | self.assertIsInstance(phoneNumber, phoneNumberComponent) 14 | 15 | # Not phoneNumberComponent 16 | firstName = self.builder.input_components['firstName'] 17 | self.assertNotIsInstance(firstName, phoneNumberComponent) 18 | 19 | def test_get_key(self): 20 | phoneNumber = self.builder.input_components['phoneNumber'] 21 | self.assertEqual(phoneNumber.key, 'phoneNumber') 22 | 23 | def test_get_type(self): 24 | phoneNumber = self.builder.input_components['phoneNumber'] 25 | self.assertEqual(phoneNumber.type, 'phoneNumber') 26 | 27 | def test_get_label(self): 28 | phoneNumber = self.builder.input_components['phoneNumber'] 29 | self.assertEqual(phoneNumber.label, 'Phone Number') 30 | 31 | def test_set_label(self): 32 | phoneNumber = self.builder.input_components['phoneNumber'] 33 | self.assertEqual(phoneNumber.label, 'Phone Number') 34 | phoneNumber.label = 'Foobar' 35 | self.assertEqual(phoneNumber.label, 'Foobar') 36 | 37 | def test_get_form(self): 38 | phoneNumber = self.form.input_components['phoneNumber'] 39 | self.assertEqual(phoneNumber.label, 'Phone Number') 40 | self.assertEqual(phoneNumber.value, '(069) 999-9999') 41 | self.assertEqual(phoneNumber.type, 'phoneNumber') 42 | 43 | def test_get_form_data(self): 44 | phoneNumber = self.form.input.phoneNumber 45 | self.assertEqual(phoneNumber.label, 'Phone Number') 46 | self.assertEqual(phoneNumber.value, '(069) 999-9999') 47 | self.assertEqual(phoneNumber.type, 'phoneNumber') 48 | -------------------------------------------------------------------------------- /tests/test_component_radio.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.radio import radioComponent 6 | 7 | 8 | class radioComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | cd = self.builder.input_components['cardinalDirection'] 12 | self.assertIsInstance(cd, radioComponent) 13 | 14 | # Not radioComponent 15 | email = self.builder.input_components['email'] 16 | self.assertNotIsInstance(email, radioComponent) 17 | 18 | def test_get_key(self): 19 | cd = self.builder.input_components['cardinalDirection'] 20 | self.assertEqual(cd.key, 'cardinalDirection') 21 | 22 | def test_get_type(self): 23 | cd = self.builder.input_components['cardinalDirection'] 24 | self.assertEqual(cd.type, 'radio') 25 | 26 | def test_get_label(self): 27 | cd = self.builder.input_components['cardinalDirection'] 28 | self.assertEqual(cd.label, 'Cardinal Direction') 29 | 30 | def test_set_label(self): 31 | cd = self.builder.input_components['cardinalDirection'] 32 | self.assertEqual(cd.label, 'Cardinal Direction') 33 | cd.label = 'Compass Direction' 34 | self.assertEqual(cd.label, 'Compass Direction') 35 | 36 | def test_get_form(self): 37 | cd = self.form.components['cardinalDirection'] 38 | self.assertEqual(cd.label, 'Cardinal Direction') 39 | 40 | #self.assertEqual(cd.values_labels, 'south') 41 | self.assertEqual(cd.value, 'south') 42 | self.assertEqual(cd.value_label, 'South') 43 | self.assertEqual(cd.type, 'radio') 44 | 45 | def test_get_form_data(self): 46 | cd = self.form.input.cardinalDirection 47 | self.assertEqual(cd.label, 'Cardinal Direction') 48 | self.assertEqual(cd.value, 'south') 49 | self.assertEqual(cd.value_label, 'South') 50 | self.assertEqual(cd.type, 'radio') 51 | 52 | # i18n translations 53 | def test_get_label_i18n_nl(self): 54 | cd = self.builder_i18n_nl.input_components['cardinalDirection'] 55 | self.assertEqual(cd.label, 'Kardinale richting') 56 | 57 | def test_get_form_data_i18n_nl(self): 58 | self.assertEqual(self.form_i18n_nl.input.cardinalDirection.label, 'Kardinale richting') 59 | self.assertEqual(self.form_i18n_nl.input.cardinalDirection.value, 'south') 60 | self.assertEqual(self.form_i18n_nl.input.cardinalDirection.value_label, 'Zuid') 61 | -------------------------------------------------------------------------------- /tests/test_component_resource.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from formiodata.builder import Builder 5 | from formiodata.components.resource import resourceComponent 6 | from formiodata.form import Form 7 | from tests.test_component import ComponentTestCase 8 | 9 | 10 | class resourceComponentTestCase(ComponentTestCase): 11 | 12 | def setUp(self): 13 | super(resourceComponentTestCase, self).setUp() 14 | self.builder_res = Builder(self.builder_with_resource, resources=self.builder_json_resource) 15 | self.form_res = Form(self.form_with_resource, self.builder_res) 16 | 17 | def test_object(self): 18 | # TextfieldComponent 19 | res = self.builder_res.input_components['resourceObj'] 20 | self.assertIsInstance(res, resourceComponent) 21 | 22 | def test_get_key(self): 23 | res = self.builder_res.input_components['resourceObj'] 24 | self.assertEqual(res.key, 'resourceObj') 25 | 26 | def test_get_type(self): 27 | res = self.builder_res.input_components['resourceObj'] 28 | self.assertEqual(res.type, 'resource') 29 | 30 | def test_values(self): 31 | res = self.builder_res.input_components['resourceObj'] 32 | self.assertEqual(len(res.values), 4) 33 | 34 | def test_first_value(self): 35 | res = self.builder_res.input_components['resourceObj'] 36 | self.assertEqual(res.values[1], {'label': 'ResB', 'value': '60034ec3942c74ca500b32b1'}) 37 | -------------------------------------------------------------------------------- /tests/test_component_select_multiple.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.select import selectComponent 6 | 7 | 8 | class selectMultipleComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # selectComponent 12 | food = self.builder.input_components['favouriteFood'] 13 | self.assertIsInstance(food, selectComponent) 14 | 15 | # Not selectComponent 16 | email = self.builder.input_components['email'] 17 | self.assertNotIsInstance(email, selectComponent) 18 | 19 | def test_get_key(self): 20 | food = self.builder.input_components['favouriteFood'] 21 | self.assertEqual(food.key, 'favouriteFood') 22 | 23 | def test_get_type(self): 24 | food = self.builder.input_components['favouriteFood'] 25 | self.assertEqual(food.type, 'select') 26 | 27 | def test_get_label(self): 28 | food = self.builder.input_components['favouriteFood'] 29 | self.assertEqual(food.label, 'Favourite Food') 30 | 31 | def test_set_label(self): 32 | food = self.builder.input_components['favouriteFood'] 33 | self.assertEqual(food.label, 'Favourite Food') 34 | food.label = 'Gimme which Food' 35 | self.assertEqual(food.label, 'Gimme which Food') 36 | 37 | def test_get_form(self): 38 | food = self.form.input_components['favouriteFood'] 39 | self.assertEqual(food.label, 'Favourite Food') 40 | self.assertEqual(food.value, ['mexican', 'chinese']) 41 | self.assertEqual(food.value_labels, ['Mexican', 'Chinese']) 42 | self.assertEqual(food.value_label, None) 43 | self.assertEqual(food.type, 'select') 44 | 45 | def test_get_form_data(self): 46 | food = self.form.input.favouriteFood 47 | self.assertEqual(food.label, 'Favourite Food') 48 | self.assertEqual(food.value, ['mexican', 'chinese']) 49 | self.assertEqual(food.value_labels, ['Mexican', 'Chinese']) 50 | self.assertEqual(food.value_label, None) 51 | self.assertEqual(food.type, 'select') 52 | 53 | # i18n translations 54 | def test_get_label_i18n_nl(self): 55 | food = self.builder_i18n_nl.input_components['favouriteFood'] 56 | self.assertEqual(food.label, 'Lievelingseten') 57 | 58 | def test_get_form_data_i18n_nl(self): 59 | self.assertEqual(self.form_i18n_nl.input.favouriteFood.label, 'Lievelingseten') 60 | self.assertEqual(self.form_i18n_nl.input.favouriteFood.value, ['mexican', 'chinese']) 61 | self.assertEqual(self.form_i18n_nl.input.favouriteFood.value_labels, ['Mexican', 'Chinese']) 62 | -------------------------------------------------------------------------------- /tests/test_component_select_one.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.select import selectComponent 6 | 7 | 8 | class selectOneComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # selectComponent 12 | season = self.builder.input_components['favouriteSeason'] 13 | self.assertIsInstance(season, selectComponent) 14 | 15 | # Not selectComponent 16 | email = self.builder.input_components['email'] 17 | self.assertNotIsInstance(email, selectComponent) 18 | 19 | def test_get_key(self): 20 | season = self.builder.input_components['favouriteSeason'] 21 | self.assertEqual(season.key, 'favouriteSeason') 22 | 23 | def test_get_type(self): 24 | season = self.builder.input_components['favouriteSeason'] 25 | self.assertEqual(season.type, 'select') 26 | 27 | def test_get_label(self): 28 | season = self.builder.input_components['favouriteSeason'] 29 | self.assertEqual(season.label, 'Favourite Season') 30 | 31 | def test_set_label(self): 32 | season = self.builder.input_components['favouriteSeason'] 33 | self.assertEqual(season.label, 'Favourite Season') 34 | season.label = 'Which Season' 35 | self.assertEqual(season.label, 'Which Season') 36 | 37 | def test_get_form(self): 38 | season = self.form.input_components['favouriteSeason'] 39 | self.assertEqual(season.label, 'Favourite Season') 40 | self.assertEqual(season.value, 'autumn') 41 | self.assertEqual(season.value_label, 'Autumn') 42 | self.assertEqual(season.type, 'select') 43 | 44 | def test_get_form_data(self): 45 | season = self.form.input.favouriteSeason 46 | self.assertEqual(season.label, 'Favourite Season') 47 | self.assertEqual(season.value, 'autumn') 48 | self.assertEqual(season.value_label, 'Autumn') 49 | self.assertEqual(season.type, 'select') 50 | 51 | def test_empty_value(self): 52 | season = self.form.input_components['favouriteSeason'] 53 | self.assertEqual(season.label, 'Favourite Season') 54 | self.assertEqual(season.value, 'autumn') 55 | season.value = None 56 | self.assertEqual(season.value, None) 57 | self.assertEqual(season.value_label, None) 58 | self.assertEqual(season.type, 'select') 59 | 60 | # i18n translations 61 | def test_get_label_i18n_nl(self): 62 | season = self.builder_i18n_nl.input_components['favouriteSeason'] 63 | self.assertEqual(season.label, 'Favoriete seizoen') 64 | 65 | def test_get_form_data_i18n_nl(self): 66 | self.assertEqual(self.form_i18n_nl.input.favouriteSeason.label, 'Favoriete seizoen') 67 | self.assertEqual(self.form_i18n_nl.input.favouriteSeason.value, 'autumn') 68 | self.assertEqual(self.form_i18n_nl.input.favouriteSeason.value_label, 'Herfst') 69 | -------------------------------------------------------------------------------- /tests/test_component_selectboxes.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.selectboxes import selectboxesComponent 6 | 7 | 8 | class selectboxesComponentTestCase(ComponentTestCase): 9 | 10 | def setUp(self): 11 | super(selectboxesComponentTestCase, self).setUp() 12 | self.selectboxes_value = { 13 | "north": True, 14 | "east": False, 15 | "south": True, 16 | "west": False, 17 | } 18 | self.selectboxes_values_labels = { 19 | "north": {"key": "north", "label": "North", "value": True}, 20 | "east": {"key": "east", "label": "East", "value": False}, 21 | "south": {"key": "south", "label": "South", "value": True}, 22 | "west": {"key": "west", "label": "West", "value": False}, 23 | } 24 | self.selectboxes_values_labels_i18n_nl = { 25 | "north": {"key": "north", "label": "Noord", "value": True}, 26 | "east": {"key": "east", "label": "Oost", "value": False}, 27 | "south": {"key": "south", "label": "Zuid", "value": True}, 28 | "west": {"key": "west", "label": "West", "value": False}, 29 | } 30 | 31 | def test_object(self): 32 | # selectboxesComponent 33 | selectBoxes = self.builder.input_components['selectBoxes'] 34 | self.assertIsInstance(selectBoxes, selectboxesComponent) 35 | 36 | # Not selectboxesComponent 37 | email = self.builder.input_components['email'] 38 | self.assertNotIsInstance(email, selectboxesComponent) 39 | 40 | def test_get_key(self): 41 | selectBoxes = self.builder.input_components['selectBoxes'] 42 | self.assertEqual(selectBoxes.key, 'selectBoxes') 43 | 44 | def test_get_type(self): 45 | selectBoxes = self.builder.input_components['selectBoxes'] 46 | self.assertEqual(selectBoxes.type, 'selectboxes') 47 | 48 | def test_get_label(self): 49 | selectBoxes = self.builder.input_components['selectBoxes'] 50 | self.assertEqual(selectBoxes.label, 'Select Boxes') 51 | 52 | def test_set_label(self): 53 | selectBoxes = self.builder.input_components['selectBoxes'] 54 | self.assertEqual(selectBoxes.label, 'Select Boxes') 55 | selectBoxes.label = 'Other Select Boxes' 56 | self.assertEqual(selectBoxes.label, 'Other Select Boxes') 57 | 58 | def test_get_form(self): 59 | selectBoxes = self.form.input_components['selectBoxes'] 60 | self.assertEqual(selectBoxes.label, 'Select Boxes') 61 | self.assertEqual(selectBoxes.value, self.selectboxes_value) 62 | self.assertEqual(selectBoxes.values_labels, self.selectboxes_values_labels) 63 | self.assertEqual(selectBoxes.type, 'selectboxes') 64 | 65 | def test_get_form_data(self): 66 | selectBoxes = self.form.input.selectBoxes 67 | self.assertEqual(selectBoxes.label, 'Select Boxes') 68 | self.assertEqual(selectBoxes.value, self.selectboxes_value) 69 | self.assertEqual(selectBoxes.values_labels, self.selectboxes_values_labels) 70 | self.assertEqual(selectBoxes.type, 'selectboxes') 71 | 72 | # i18n translations 73 | def test_get_label_i18n_nl(self): 74 | food = self.builder_i18n_nl.input_components['selectBoxes'] 75 | self.assertEqual(food.label, 'Select aanvink opties') 76 | 77 | def test_get_form_data_i18n_nl(self): 78 | self.assertEqual(self.form_i18n_nl.input.selectBoxes.label, 'Select aanvink opties') 79 | self.assertEqual(self.form_i18n_nl.input.selectBoxes.value, self.selectboxes_value) 80 | self.assertEqual( 81 | self.form_i18n_nl.input.selectBoxes.values_labels, 82 | self.selectboxes_values_labels_i18n_nl, 83 | ) 84 | -------------------------------------------------------------------------------- /tests/test_component_survey.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.survey import surveyComponent 6 | 7 | 8 | class surveyComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # surveyComponent 12 | survey = self.builder.input_components['survey'] 13 | self.assertIsInstance(survey, surveyComponent) 14 | 15 | # Not surveyComponent 16 | firstName = self.builder.input_components['firstName'] 17 | self.assertNotIsInstance(firstName, surveyComponent) 18 | 19 | def test_get_key(self): 20 | survey = self.builder.input_components['survey'] 21 | self.assertEqual(survey.key, 'survey') 22 | 23 | def test_get_type(self): 24 | survey = self.builder.input_components['survey'] 25 | self.assertEqual(survey.type, 'survey') 26 | 27 | def test_get_label(self): 28 | survey = self.builder.input_components['survey'] 29 | self.assertEqual(survey.label, 'Survey') 30 | 31 | def test_set_label(self): 32 | survey = self.builder.input_components['survey'] 33 | self.assertEqual(survey.label, 'Survey') 34 | survey.label = 'Foobar' 35 | self.assertEqual(survey.label, 'Foobar') 36 | 37 | def test_get_form(self): 38 | survey = self.form.components['survey'] 39 | self.assertEqual(survey.label, 'Survey') 40 | self.assertEqual(survey.value['overallExperience'], 'excellent') 41 | self.assertEqual(survey.value['howWasCustomerSupport'], 'great') 42 | self.assertEqual(survey.value['howWouldYouRateTheFormIoPlatform'], 'excellent') 43 | self.assertEqual(survey.type, 'survey') 44 | 45 | def test_get_form_data(self): 46 | survey = self.form.input.survey 47 | self.assertEqual(survey.label, 'Survey') 48 | self.assertEqual(survey.value['overallExperience'], 'excellent') 49 | self.assertEqual(survey.value['howWasCustomerSupport'], 'great') 50 | self.assertEqual(survey.value['howWouldYouRateTheFormIoPlatform'], 'excellent') 51 | self.assertEqual(survey.type, 'survey') 52 | 53 | # i18n translations 54 | def test_get_label_i18n_nl(self): 55 | survey = self.builder_i18n_nl.input_components['survey'] 56 | self.assertEqual(survey.label, 'Enquête') 57 | 58 | def test_get_form_data_i18n_nl(self): 59 | survey = self.form_i18n_nl.input.survey 60 | self.assertEqual(survey.label, 'Enquête') 61 | # TODO Labels for questions and values 62 | # self.assertEqual(survey.value['overallExperience'], 'uitstekend') 63 | # self.assertEqual(survey.value['howWasCustomerSupport'], 'super goed') 64 | # self.assertEqual(survey.value['howWouldYouRateTheFormIoPlatform'], 'uitstekend') 65 | -------------------------------------------------------------------------------- /tests/test_component_table.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.table import tableComponent 6 | 7 | 8 | class tableComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # tableComponent 12 | table = self.builder.components['table'] 13 | self.assertIsInstance(table, tableComponent) 14 | 15 | # Not tableComponent 16 | email = self.builder.input_components['email'] 17 | self.assertNotIsInstance(email, tableComponent) 18 | 19 | def test_get_key(self): 20 | table = self.builder.components['table'] 21 | self.assertEqual(table.key, 'table') 22 | 23 | def test_get_type(self): 24 | table = self.builder.components['table'] 25 | self.assertEqual(table.type, 'table') 26 | 27 | def test_get_label(self): 28 | table = self.builder.components['table'] 29 | self.assertEqual(table.label, 'Table') 30 | 31 | def test_get_row_labels(self): 32 | builder_table = self.builder.components['table'] 33 | table = self.form.components[builder_table.key] 34 | 35 | self.assertEqual(len(table.rows), 2) 36 | 37 | labels = ['Text Field', 'Checkbox'] 38 | for row in table.rows: 39 | for col in row: 40 | for comp in col['components']: 41 | self.assertIn(comp.label , labels) 42 | 43 | def test_get_rows_values(self): 44 | builder_table = self.builder.components['table'] 45 | table = self.form.components[builder_table.key] 46 | 47 | self.assertEqual(len(table.rows), 2) 48 | 49 | # Accessing directly... 50 | self.assertEqual(table.components['textField'].value, 'Elephant') 51 | self.assertEqual(table.components['checkbox'].value, True) 52 | self.assertEqual(table.components['textField1'].value, 'Lion') 53 | self.assertEqual(table.components['checkbox1'].value, False) 54 | 55 | # Or through rows/cols: 56 | self.assertEqual(table.rows[0][0]['components'][0], table.components['textField']) 57 | self.assertEqual(table.rows[0][1]['components'][0], table.components['checkbox']) 58 | self.assertEqual(table.rows[1][0]['components'][0], table.components['textField1']) 59 | self.assertEqual(table.rows[1][1]['components'][0], table.components['checkbox1']) 60 | -------------------------------------------------------------------------------- /tests/test_component_tabs.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.tabs import tabsComponent 6 | from formiodata.components.textfield import textfieldComponent 7 | from formiodata.components.number import numberComponent 8 | 9 | 10 | class tabsComponentTestCase(ComponentTestCase): 11 | 12 | def test_object(self): 13 | tabs = self.builder.components['tabs'] 14 | self.assertIsInstance(tabs, tabsComponent) 15 | 16 | # Not tabsComponent 17 | email = self.builder.input_components['email'] 18 | self.assertNotIsInstance(email, tabsComponent) 19 | 20 | def test_get_key(self): 21 | tabs = self.builder.components['tabs'] 22 | self.assertEqual(tabs.key, 'tabs') 23 | 24 | def test_get_type(self): 25 | tabs = self.builder.components['tabs'] 26 | self.assertEqual(tabs.type, 'tabs') 27 | 28 | def test_get_label(self): 29 | tabs = self.builder.components['tabs'] 30 | self.assertEqual(tabs.label, 'Tabs') 31 | 32 | def test_get_tabs(self): 33 | builder_tabs_component = self.builder.components['tabs'] 34 | tabs_component = self.form.components[builder_tabs_component.key] 35 | 36 | self.assertEqual(len(tabs_component.tabs), 2) 37 | 38 | for tab in tabs_component.tabs: 39 | if tab['tab']['key'] == 'tab1': 40 | self.assertEqual(tab['tab']['label'], 'Tab 1') 41 | # components in tab 42 | self.assertEqual(len(tab['components']), 1) 43 | textfieldTab1 = tab['components'][0] 44 | self.assertIsInstance(textfieldTab1, textfieldComponent) 45 | self.assertEqual(textfieldTab1.value, 'text in tab 1') 46 | if tab['tab']['key'] == 'tab2': 47 | self.assertEqual(tab['tab']['label'], 'Tab 2') 48 | # components in tab 49 | self.assertEqual(len(tab['components']), 1) 50 | numberTab2 = tab['components'][0] 51 | self.assertIsInstance(numberTab2, numberComponent) 52 | self.assertEqual(numberTab2.value, 2) 53 | -------------------------------------------------------------------------------- /tests/test_component_textfield.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_component import ComponentTestCase 5 | from formiodata.components.textfield import textfieldComponent 6 | 7 | 8 | class textfieldComponentTestCase(ComponentTestCase): 9 | 10 | def test_object(self): 11 | # TextfieldComponent 12 | firstName = self.builder.input_components['firstName'] 13 | self.assertIsInstance(firstName, textfieldComponent) 14 | 15 | lastName = self.builder.input_components['lastName'] 16 | self.assertIsInstance(lastName, textfieldComponent) 17 | 18 | # Not TextfieldComponent 19 | email = self.builder.input_components['email'] 20 | self.assertNotIsInstance(email, textfieldComponent) 21 | 22 | def test_get_key(self): 23 | firstName = self.builder.input_components['firstName'] 24 | self.assertEqual(firstName.key, 'firstName') 25 | 26 | def test_get_type(self): 27 | firstName = self.builder.input_components['firstName'] 28 | self.assertEqual(firstName.type, 'textfield') 29 | 30 | def test_get_label(self): 31 | firstName = self.builder.input_components['firstName'] 32 | self.assertEqual(firstName.label, 'First Name') 33 | 34 | def test_set_label(self): 35 | firstName = self.builder.input_components['firstName'] 36 | self.assertEqual(firstName.label, 'First Name') 37 | firstName.label = 'Foobar' 38 | self.assertEqual(firstName.label, 'Foobar') 39 | 40 | def test_get_form(self): 41 | firstName = self.form.input_components['firstName'] 42 | self.assertEqual(firstName.label, 'First Name') 43 | self.assertEqual(firstName.value, 'Bob') 44 | self.assertEqual(firstName.type, 'textfield') 45 | 46 | def test_get_form_data(self): 47 | firstName = self.form.input.firstName 48 | self.assertEqual(firstName.label, 'First Name') 49 | self.assertEqual(firstName.value, 'Bob') 50 | self.assertEqual(firstName.type, 'textfield') 51 | 52 | # i18n translations 53 | def test_get_label_i18n_nl(self): 54 | firstName = self.builder_i18n_nl.input_components['firstName'] 55 | self.assertEqual(firstName.label, 'Voornaam') 56 | lastName = self.builder_i18n_nl.input_components['lastName'] 57 | self.assertEqual(lastName.label, 'Achternaam') 58 | 59 | def test_get_form_data_i18n_nl(self): 60 | self.assertEqual(self.form_i18n_nl.input.firstName.label, 'Voornaam') 61 | self.assertEqual(self.form_i18n_nl.input.lastName.label, 'Achternaam') 62 | -------------------------------------------------------------------------------- /tests/test_conditional_visibility_json_logic.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import unittest 5 | 6 | from tests.utils import readfile, ConditionalVisibilityTestHelpers 7 | from formiodata.builder import Builder 8 | from formiodata.form import Form 9 | 10 | 11 | class ConditionalVisibilityJsonLogicTestCase(ConditionalVisibilityTestHelpers, unittest.TestCase): 12 | def setUp(self): 13 | super(ConditionalVisibilityJsonLogicTestCase, self).setUp() 14 | 15 | self.builder_json = readfile('data', 'test_conditional_visibility_json_logic_builder.json') 16 | self.hide_secret_form_json = readfile('data', 'test_conditional_visibility_json_logic_hide_secret.json') 17 | self.show_secret_form_json = readfile('data', 'test_conditional_visibility_json_logic_show_secret.json') 18 | 19 | def test_conditionally_shown_components_have_default_state_in_builder(self): 20 | builder = Builder(self.builder_json) 21 | 22 | self.assertVisible(builder.input_components['username']) 23 | self.assertVisible(builder.input_components['password']) 24 | self.assertNotVisible(builder.input_components['secret']) 25 | 26 | def test_conditionally_shown_components_toggle_on_condition_being_met(self): 27 | builder = Builder(self.builder_json) 28 | 29 | hide_secret_form = Form(self.hide_secret_form_json, builder) 30 | self.assertVisible(hide_secret_form.input_components['username']) 31 | self.assertVisible(hide_secret_form.input_components['password']) 32 | self.assertNotVisible(hide_secret_form.input_components['secret']) 33 | 34 | show_secret_form = Form(self.show_secret_form_json, builder) 35 | self.assertVisible(show_secret_form.input_components['username']) 36 | self.assertVisible(show_secret_form.input_components['password']) 37 | self.assertVisible(show_secret_form.input_components['secret']) 38 | 39 | def test_conditionally_shown_components_do_not_render_when_hidden(self): 40 | builder = Builder(self.builder_json) 41 | 42 | hide_secret_form = Form(self.hide_secret_form_json, builder) 43 | hide_secret_form.render_components() 44 | self.assertEqual('

wrong

', hide_secret_form.input_components['username'].html_component) 45 | self.assertEqual('

incorrect

', hide_secret_form.input_components['password'].html_component) 46 | self.assertEqual('', hide_secret_form.input_components['secret'].html_component) 47 | 48 | show_secret_form = Form(self.show_secret_form_json, builder) 49 | show_secret_form.render_components() 50 | self.assertEqual('

user

', show_secret_form.input_components['username'].html_component) 51 | self.assertEqual('

secret

', show_secret_form.input_components['password'].html_component) 52 | self.assertEqual('

Secret message

', show_secret_form.input_components['secret'].html_component) 53 | -------------------------------------------------------------------------------- /tests/test_conditional_visibility_simple.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import unittest 5 | 6 | from tests.utils import readfile, ConditionalVisibilityTestHelpers 7 | from formiodata.builder import Builder 8 | from formiodata.form import Form 9 | 10 | 11 | class ConditionalVisibilitySimpleTestCase(ConditionalVisibilityTestHelpers, unittest.TestCase): 12 | def setUp(self): 13 | super(ConditionalVisibilitySimpleTestCase, self).setUp() 14 | 15 | self.builder_json = readfile('data', 'test_conditional_visibility_simple_builder.json') 16 | self.hide_password_form_json = readfile('data', 'test_conditional_visibility_simple_hide_password.json') 17 | self.show_selectboxes_form_json = readfile('data', 'test_conditional_visibility_simple_show_selectboxes.json') 18 | self.show_textfield_form_json = readfile('data', 'test_conditional_visibility_simple_show_textfield.json') 19 | 20 | def test_conditionally_shown_components_have_default_state_in_builder(self): 21 | builder = Builder(self.builder_json) 22 | 23 | self.assertVisible(builder.input_components['textField']) 24 | self.assertNotVisible(builder.input_components['maybeTextField']) 25 | self.assertVisible(builder.input_components['maybePassword']) 26 | self.assertNotVisible(builder.input_components['sales']) 27 | self.assertNotVisible(builder.input_components['technology']) 28 | 29 | def test_conditionally_shown_components_toggle_on_condition_being_met(self): 30 | builder = Builder(self.builder_json) 31 | 32 | hide_password_form = Form(self.hide_password_form_json, builder) 33 | self.assertVisible(hide_password_form.input_components['textField']) 34 | self.assertNotVisible(hide_password_form.input_components['maybeTextField']) 35 | self.assertNotVisible(hide_password_form.input_components['maybePassword']) 36 | 37 | show_textfield_form = Form(self.show_textfield_form_json, builder) 38 | self.assertVisible(show_textfield_form.input_components['textField']) 39 | self.assertVisible(show_textfield_form.input_components['maybeTextField']) 40 | self.assertVisible(show_textfield_form.input_components['maybePassword']) 41 | 42 | show_selectboxes_form = Form(self.show_selectboxes_form_json, builder) 43 | self.assertVisible(show_selectboxes_form.input_components['jobArea']) 44 | self.assertVisible(show_selectboxes_form.input_components['technology']) 45 | self.assertNotVisible(show_selectboxes_form.input_components['sales']) 46 | 47 | def test_conditionally_shown_components_do_not_render_when_hidden(self): 48 | builder = Builder(self.builder_json) 49 | 50 | hide_password_form = Form(self.hide_password_form_json, builder) 51 | hide_password_form.render_components() 52 | self.assertEqual('

hide!

', hide_password_form.input_components['textField'].html_component) 53 | self.assertEqual('', hide_password_form.input_components['maybeTextField'].html_component) 54 | self.assertEqual('', hide_password_form.input_components['maybePassword'].html_component) 55 | 56 | show_textfield_form = Form(self.show_textfield_form_json, builder) 57 | show_textfield_form.render_components() 58 | self.assertEqual('

show!

', show_textfield_form.input_components['textField'].html_component) 59 | self.assertEqual('

maybe yes

', show_textfield_form.input_components['maybeTextField'].html_component) 60 | self.assertEqual('

hunter2

', show_textfield_form.input_components['maybePassword'].html_component) 61 | -------------------------------------------------------------------------------- /tests/test_datagrid_in_panel.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import unittest 5 | 6 | from tests.utils import readfile 7 | 8 | from formiodata.builder import Builder 9 | from formiodata.form import Form 10 | 11 | 12 | # Regression test for #17 13 | class DatagridInPanelTestcase(unittest.TestCase): 14 | def setUp(self): 15 | super(DatagridInPanelTestcase, self).setUp() 16 | 17 | self.builder_json = readfile('data', 'test_datagrid_in_panel_builder.json') 18 | self.one_row_form_json = readfile('data', 'test_datagrid_in_panel_one_row_form.json') 19 | 20 | def test_default_state_in_builder_has_one_row(self): 21 | builder = Builder(self.builder_json) 22 | self.assertEqual({'panel', 'submit'}, set(builder.components.keys())) 23 | 24 | panel = builder.components['panel'] 25 | self.assertEqual({'dataGrid'}, set(panel.components.keys())) 26 | 27 | datagrid = builder.components['panel'].components['dataGrid'] 28 | self.assertEqual({'textField'}, set(datagrid.components.keys())) 29 | 30 | # datagrid will have no visible rows when initialized 31 | self.assertTrue(datagrid.initEmpty) 32 | self.assertEqual(len(datagrid.rows), 0) 33 | 34 | 35 | def test_form_with_one_row_has_the_one_row_created_by_submission(self): 36 | builder = Builder(self.builder_json) 37 | 38 | form = Form(self.one_row_form_json, builder) 39 | self.assertEqual({'panel', 'submit'}, set(form.components.keys())) 40 | 41 | panel = form.components['panel'] 42 | self.assertEqual({'dataGrid'}, set(panel.components.keys())) 43 | 44 | datagrid = form.components['panel'].components['dataGrid'] 45 | self.assertEqual({'textField'}, set(datagrid.components.keys())) 46 | self.assertEqual(len(datagrid.rows), 1) 47 | -------------------------------------------------------------------------------- /tests/test_default_value_component.py: -------------------------------------------------------------------------------- 1 | from formiodata.form import Form 2 | from test_component import ComponentTestCase 3 | 4 | 5 | class valueDefaultEmailComponent(ComponentTestCase): 6 | 7 | def test_default_value(self): 8 | self.form_check_default = Form(self.form_json_check_default, self.builder) 9 | # EmailComponent 10 | email = self.form_check_default.input_components['email'] 11 | self.assertEqual(email.value, 'yourmail@yourlife.io') 12 | -------------------------------------------------------------------------------- /tests/test_performance_nested_components.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import logging 5 | import time 6 | import unittest 7 | 8 | from tests.utils import readfile 9 | from formiodata.builder import Builder 10 | 11 | 12 | class PerformanceNestedTestCase(unittest.TestCase): 13 | logger = logging.getLogger(__name__) 14 | logging.basicConfig(format='\n%(message)s', level=logging.INFO) 15 | 16 | def setUp(self): 17 | super(PerformanceNestedTestCase, self).setUp() 18 | self.builder_json = readfile('data', 'test_nested_components_builder.json') 19 | self.form_json = readfile('data', 'test_nested_components_form.json') 20 | 21 | def load_builders_range(self, range_num, load_path_objects): 22 | start = time.time() 23 | builders = {} 24 | for n in range(range_num): 25 | builders[n] = Builder(self.builder_json, load_path_objects=load_path_objects) 26 | end = time.time() 27 | msg_lines = [ 28 | '----------------------------------------', 29 | 'Load Builders range: %s' % range_num, 30 | 'Duration: %s' % str(end - start), 31 | '----------------------------------------' 32 | ] 33 | self.logger.info('\n'.join(msg_lines)) 34 | # self.logger.info(end - start) 35 | 36 | def test_Builder_component_with_path_objects(self): 37 | """ Builder: component path objects """ 38 | 39 | msg_lines = [ 40 | '========================================', 41 | 'Load Builder WITH path objects', 42 | '========================================', 43 | ] 44 | self.logger.info('\n'.join(msg_lines)) 45 | self.load_builders_range(10, load_path_objects=True) 46 | self.load_builders_range(100, load_path_objects=True) 47 | self.load_builders_range(1000, load_path_objects=True) 48 | 49 | def test_Builder_component_no_path_objects(self): 50 | """ Builder: component NO path objects """ 51 | 52 | msg_lines = [ 53 | '========================================', 54 | 'Load Builder NO path objects', 55 | '========================================', 56 | ] 57 | self.logger.info('\n'.join(msg_lines)) 58 | self.load_builders_range(10, load_path_objects=False) 59 | self.load_builders_range(100, load_path_objects=False) 60 | self.load_builders_range(1000, load_path_objects=False) 61 | -------------------------------------------------------------------------------- /tests/test_submission.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from test_common import CommonTestCase 5 | from formiodata.form import Form 6 | 7 | 8 | class FormTestCase(CommonTestCase): 9 | 10 | def x_test_constructor_validation_ok(self): 11 | sub = Form(self.form_json, None, self.builder_json) 12 | self.assertIsInstance(sub, Form) 13 | 14 | sub = Form(self.form_json, self.builder) 15 | self.assertIsInstance(sub, Form) 16 | # self.assertIsInstance(self.form.store, FormStore) 17 | 18 | def x_test_constructor_validation_fails(self): 19 | with self.assertRaisesRegexp(Exception, "Provide either the argument: builder or builder_schema_json."): 20 | Form(self.form_json) 21 | 22 | with self.assertRaisesRegexp(Exception, "Constructor accepts either builder or builder_schema_json."): 23 | Form(self.form_json, self.builder, self.builder_schema_json) 24 | -------------------------------------------------------------------------------- /tests/test_validation_error_simple.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | from tests.utils import readfile 5 | from test_component import ComponentTestCase 6 | 7 | from formiodata.form import Form 8 | 9 | 10 | class ValidationErrorSimpleTestCase(ComponentTestCase): 11 | 12 | def setUp(self): 13 | super(ValidationErrorSimpleTestCase, self).setUp() 14 | self.form_validation_errors_json = readfile('data', 'test_example_form_validation_errors.json') 15 | self.form_validation_errors = Form(self.form_validation_errors_json, self.builder) 16 | self.form_validation_errors_i18n_nl = Form(self.form_validation_errors_json, self.builder_i18n_nl) 17 | 18 | def test_required_components_in_builder(self): 19 | firstName = self.builder.input_components['firstName'] 20 | self.assertTrue(firstName.required) 21 | 22 | lastName = self.builder.input_components['lastName'] 23 | self.assertTrue(lastName.required) 24 | 25 | email = self.builder.input_components['email'] 26 | self.assertFalse(email.required) 27 | 28 | def test_required_components_form_validation_errors(self): 29 | errors = self.form_validation_errors.validation_errors() 30 | 31 | self.assertEqual( 32 | errors['firstName']['required'], 33 | 'First Name is required' 34 | ) 35 | self.assertEqual( 36 | errors['lastName']['required'], 37 | 'Last Name is required' 38 | ) 39 | self.assertEqual( 40 | errors['dataGrid'][0]['textField']['required'], 41 | 'Text Field is required' 42 | ) 43 | self.assertEqual( 44 | errors['dataGrid'][1], 45 | {} 46 | ) 47 | self.assertEqual( 48 | errors['dataGrid'][2]['textField']['required'], 49 | 'Text Field is required' 50 | ) 51 | self.assertEqual( 52 | errors['dataGrid'][3], 53 | {} 54 | ) 55 | 56 | def test_required_components_form_validation_errors_i18n_nl(self): 57 | errors = self.form_validation_errors_i18n_nl.validation_errors() 58 | 59 | self.assertEqual( 60 | errors['firstName']['required'], 61 | 'Voornaam is verplicht' 62 | ) 63 | self.assertEqual( 64 | errors['lastName']['required'], 65 | 'Achternaam is verplicht' 66 | ) 67 | self.assertEqual( 68 | errors['dataGrid'][0]['textField']['required'], 69 | 'Tekstveld is verplicht' 70 | ) 71 | self.assertEqual( 72 | errors['dataGrid'][1], 73 | {} 74 | ) 75 | self.assertEqual( 76 | errors['dataGrid'][2]['textField']['required'], 77 | 'Tekstveld is verplicht' 78 | ) 79 | self.assertEqual( 80 | errors['dataGrid'][3], 81 | {} 82 | ) 83 | 84 | def test_not_required_components_form(self): 85 | errors = self.form_validation_errors_i18n_nl.validation_errors() 86 | self.assertNotIn('email', errors) 87 | -------------------------------------------------------------------------------- /tests/test_view_render_component.py: -------------------------------------------------------------------------------- 1 | from formiodata.form import Form 2 | from test_component import ComponentTestCase 3 | 4 | 5 | class viewRenderEmailComponent(ComponentTestCase): 6 | 7 | def test_view_render(self): 8 | self.form_check_default = Form(self.form_json_check_default, self.builder) 9 | self.form_check_default.render_components() 10 | # EmailComponent 11 | email = self.form_check_default.input_components['email'] 12 | self.assertEqual(email.html_component, '

yourmail@yourlife.io

') 13 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright Nova Code (http://www.novacode.nl) 2 | # See LICENSE file for full licensing details. 3 | 4 | import logging 5 | import os 6 | 7 | def readfile(dir_path, filename): 8 | cwd = os.path.dirname(os.path.realpath(__file__)) 9 | path = '%s/%s/%s' % (cwd, dir_path, filename) 10 | with open(path, "r") as fp: 11 | return fp.read() 12 | 13 | def log_unittest(unittest_obj, msg, log_level='info'): 14 | log = '%s %s' % (log_level.upper(), unittest_obj.id()) 15 | if unittest_obj.shortDescription(): 16 | log += ' -- %s' % unittest_obj.shortDescription() 17 | log += ' - %s' % msg 18 | getattr(logging, log_level)(log) 19 | 20 | 21 | class ConditionalVisibilityTestHelpers: 22 | def setUp(self): 23 | super(ConditionalVisibilityTestHelpers, self).setUp() 24 | try: 25 | from json_logic import jsonLogic 26 | self._have_json_logic = True 27 | except ImportError: 28 | self._have_json_logic = False 29 | 30 | def assertVisible(self, component): 31 | self.assertTrue(component.conditionally_visible) 32 | 33 | def assertNotVisible(self, component): 34 | if component.raw['conditional'].get('json') and not self._have_json_logic: 35 | # Without json_logic, all components with json conditionals 36 | # are considered always visible 37 | self.assertTrue(component.conditionally_visible) 38 | else: 39 | self.assertFalse(component.conditionally_visible) 40 | --------------------------------------------------------------------------------