Giter VIP home page Giter VIP logo

python-formio-data's Introduction

formio-data (Python)

formio.js (JSON Form Builder) data API for Python.

For information about the formio.js project, see https://github.com/formio/formio.js

Introduction

python-formio-data is a Python package, which loads and transforms formio.js Builder JSON and Form JSON into usable Python objects.
It's main aim is to provide easy access to a Form its components/fields, also captured as Python objects, which makes this API very versatile and usable.

Notes about terms:

  • Builder: The Form Builder which is the design/blueprint of a Form.
  • Form: A filled-in Form, aka Form submission.
  • Component: Input (field) or layout component in the Form Builder and Form.

Features

  • Compatible with Python 3.6 and later
  • Constructor of the Builder and Form class, only requires the JSON and an optional language code for translations.
  • Get a Form object its Components as a usable object e.g. datetime, boolean, dict (for select component) etc.
  • Open source (MIT License)

Installation

The source code is currently hosted on GitHub at: https://github.com/novacode-nl/python-formio-data

PyPI - Python Package Index

Binary installers for the latest released version are available at the Python Package Index

pip(3) install formio-data

Optional dependencies

To support conditional visibility using JSON logic, you can install the json-logic-qubit package (the json-logic package it is forked off of is currently unmaintained). It's also possible to install it via the pip feature json_logic like so:

pip(3) install -U formio-data[json_logic]

Source Install with Poetry (recommended)

Convenient for developers. Also useful for running the (unit)tests.

git clone [email protected]:novacode-nl/python-formio-data.git
poetry install

Optional dependencies

When working in the project itself, use

poetry install -E json_logic

Source Install with pip

Optional dependencies need to be installed separately.

pip(3) install -U -e python-formio-data

Using direnv

You can use nixpkgs to run a self-contained Python environment without any additional setup. Once you've installed nixpkgs, switch into the directory and type "nix-shell" to get a shell from which the correct Python with packages is available.

If you're using direnv, use direnv allow after changing into the project directory and you're good to go. Also consider nix-direnv to speed up the experience (it can re-use a cached local installation).

License

MIT

Contributing

All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome.

Usage examples

For more examples of usage, see the unit-tests.

>> from formiodata import Builder, Form
>>
# builder_json is a formio.js Builder JSON document (text/string)
# form_json is a formio.js Form JSON document (text/string)
>>
>> builder = Builder(builder_json)
>> form = Form(builder, form_json)

##################
# input components
##################

# textfield label
>> print(form.input_components['firstname'].label)
'First Name'

# textfield value
>> print(form.input_components['firstname'].value)
'Bob'

# datetime label
>> print(form.input_components['birthday'].label)
'Birthday'

# datetime value
>> print(form.input_components['birthday'].value)
'2009-10-16'

>> print(form.input_components['birthday'].to_date())
datetime.date(2009 10 16)

# datagrid (rows property)
>> print(form.input_components['datagridMeasurements'].rows)
[
  {'measurementDatetime': <datetimeComponent>, 'measurementFahrenheit': <numberComponent>}, 
  {'measurementDatetime': <datetimeComponent>, 'measurementFahrenheit': <numberComponent>}
]

>> for row in form.input_components['datagridMeasurements'].rows:
>>    dtime = row['measurementDatetime']
>>    fahrenheit = row['measurementFahrenheit']
>>    print(%s: %s, %s: %s' % (dt.label, dt.to_datetime(), fahrenheit.label, fahrenheit.value))

Datetime: datetime.datetime(2021, 5, 8, 11, 39, 0, 296487), Fahrenheit: 122
Datetime: datetime.datetime(2021, 5, 8, 11, 41, 5, 919943), Fahrenheit: 131

# alternative example, by getattr
>> print(form.data.firstname.label)
'First Name'

>> print(form.data.firstname.value)
'Bob'

###################
# validation errors
###################

>> print(form.validation_errors())
{
    'companyName': 'Company Name is required',
    'editgridActivities': [
        {'description': 'Description is required'},
        {},  # no validation error (row 2)
        {},  # no validation error (row 3)
        {'description': 'Description is required', 'startDate': 'Start Date is required'}
    ]
}

#############################
# component path (properties)
#############################

# datagrid input
>> datagridMeasurements = builder.components['datagridMeasurements']

# builder_path
>> [
>>     print(row.input_components['measurementDatetime'].builder_path)
>>     for row in datagridMeasurements.rows
>> ]
[<panelComponent>, <columnsComponent>, <datagridComponent>, <datetimeComponent>]

# builder_path_key
>> [
>>     print(row.input_components['measurementDatetime'].builder_path_key)
>>     for row in datagridMeasurements.rows
>> ]
['pageMeasurements', 'columnsExternal', 'datagridMeasurements', 'measurementDatetime']

# builder_path_labels
>> [
>>     print(row.input_components['measurementDatetime'].builder_path_labels)
>>     for row in datagridMeasurements.rows
>> ]
['Page Measurements', 'Columns External', 'Data Grid Measurements', 'Measurement Datetime']

# builder_input_path
>> [
>>     print(row.input_components['measurementDatetime'].builder_input_path)
>>     for row in datagridMeasurements.rows
>> ]
[<datagridComponent>, <datetimeComponent>]

# builder_input_path_key
>> [
>>     print(row.input_components['measurementDatetime'].builder_input_path_key)
>>     for row in datagridMeasurements.rows
>> ]
['datagridMeasurements', 'measurementDatetime']

# builder_input_path_labels
>> [
>>     print(row.input_components['measurementDatetime'].builder_input_path_labels)
>>     for row in datagridMeasurements.rows
>> ]
['Data Grid Measurements', 'Measurement Datetime']

#################################
# components (layout, input etc.)
#################################

# columns
>> print(form.components['addressColumns'])
<columnsComponent>

>> print(form.components['addressColumns'].rows)
[ 
  {'firstName': <textfieldComponent>, 'lastName: <textfieldComponent>}, 
  {'email': <emailComponent>, 'companyName: <textfieldComponent>}
]

##########################
# components class mapping
##########################

# Below an example which verbosely shows the feature:
# - First set a custom component type 'custom_editgrid' in the Builder JSON schema.
# - Check (assert) whether the component object is an instance of the mapped editgridComponent.
# This code is also present in the unittest (file): tests/test_component_class_mapping.py

schema_dict = json.loads(self.builder_json)

# change 'editgrid' type to 'custom_editgrid'
for comp in schema_dict['components']:
    if comp['key'] == 'editGrid':
        comp['type'] = 'custom_editgrid'

component_class_mapping = {'custom_editgrid': editgridComponent}
builder = Builder(
    schema_json,
    component_class_mapping=component_class_mapping,
)

custom_editgrid = builder.components['editGrid']
self.assertIsInstance(custom_editgrid, editgridComponent)
self.assertEqual(custom_editgrid.type, 'custom_editgrid')

Unit tests

Note:

Internet access is recommended for running the fileStorageUrlComponentTestCase, because this also tests the URL Storage (type).
If no internet access, this test won't fail and a WARNING shall be logged regarding a ConnectionError.

Run all unittests

From toplevel directory:

poetry install -E json_logic  # if you haven't already
poetry run python -m unittest

Run component unittests

All Components, from toplevel directory:

poetry run python -m unittest tests/test_component_*.py

Nested components (complexity), from toplevel directory:

poetry run python -m unittest tests/test_nested_components.py

Run specific component unittest

poetry run python -m unittest tests.test_component_day.dayComponentTestCase.test_get_form_dayMonthYear

python-formio-data's People

Contributors

archetipo avatar bobslee avatar kristroma avatar sjamaan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

python-formio-data's Issues

[BUG] DateTime - Invalid isoformat string

The following issue pops up by printing a form with DateTime component and applies to latest version of python-formio-data (4.0):

ValueError: Invalid isoformat string: '2022-04-25T19:29:58.000Z'

Error to render compiling AST
ValueError: Invalid isoformat string: '2022-04-25T19:29:58.000Z'
Template: formio_report_qweb.report_formio_form_template
Path: /t/div/div[2]/t
Node: <t t-foreach="o._formio.components.items()" t-as="component">
                    <t t-set="component" t-value="component[1]"/>
                    <t t-call="formio_report_qweb.component"/>
                </t>

Date Format String in Form Builder Component:

dd/MM/yyyy HH:mm

timeComponent: float representation (function)

Example:

  opening_start_time = [int(part) for part in row['openingTime'].value.split(':')]
  opening_start_time_obj = datetime.time(opening_start_time[0], opening_start_time[1])
  opening_hours_start = opening_start_time_obj.hour + (opening_start_time_obj.minute / 60.0

Conditional component visibility: JSONLogic

1. Usage examples

To get the idea, see:

2. (formio.js) Field Logic Schema

https://github.com/formio/formio.js/wiki/Field-Logic-Schema

3. Implementation suggestions

(1) Maybe useful library

https://github.com/nadirizr/json-logic-py

(2) Dict to variables in scope

https://stackoverflow.com/questions/18090672/convert-dictionary-entries-into-variables-python

Also used in EXAMPLE below.

>>> from types import SimpleNamespace
>>> d = {'a': 123, 'b': 456}
>>> n = SimpleNamespace(**d)
>>> n.a
123

EXAMPLE code

class Component:

    def __init__():
        self.show = True

    def _set_show(self): 
        form_data = SimpleNamespace(**self.form)
        ...

    def load(self, parent=None, data=None, renderer=None):
            self.load_data(data)
            # NEW set_show
            self._set_show()

4. Form Builder (config)

image

Pluggable components

Implement a simple way to load pluggable components (parsers). By kwargs in Builder and Submission?

Unit test: datagridComponent

Add unit test for the datagridComponent.

In the datagrid add sub-component types:

  • text
  • number
  • date
  • currency

Test (ensure) the sub-component objects get loaded properly.

Nested components: panel => datagrid

Seems there's a bug with nested components having a hierarchy: panel => datagrid (=> columns).

Datagrid rows property holds an extra (unwanted) empty row with its components.

Add a unittest (with data fixtures) for this case.

Conditional component visibility: Javascript

1. Usage examples

To get the idea, see:

2. (formio.js) Field Logic Schema

https://github.com/formio/formio.js/wiki/Field-Logic-Schema

3. Implementation suggestions

(1) Maybe useful library

https://github.com/PiotrDabkowski/Js2Py

(2) Dict to variables in scope

https://stackoverflow.com/questions/18090672/convert-dictionary-entries-into-variables-python

Also used in EXAMPLE below.

>>> from types import SimpleNamespace
>>> d = {'a': 123, 'b': 456}
>>> n = SimpleNamespace(**d)
>>> n.a
123

EXAMPLE code

class Component:

    def __init__():
        self.show = True

    def _set_show(self): 
        form_data = SimpleNamespace(**self.form)
        ...

    def load(self, parent=None, data=None, renderer=None):
            self.load_data(data)
            # NEW set_show
            self._set_show()

4. Form Builder (config)

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.