YAFOWIL targets rendering form widgets and extracting/validating the data send by the browser per widget.
YAFOWIL widgets are just configuration. It provides a factory which can produce widget instances from blueprints.
There is a library of existing blueprints ready to be extended on demand.
YAFOWIL provides blueprints for all HTML standard inputs, lots of helper
blueprints for buidling complex widgets and a bunch of add-ons (usally in
namespace yafowil.widget.*
).
Tired of inventing widgets again and again after switching the Python framework YAFOWIL is intentionally written framework-independent. By just feeding it with configuration it can be used and extended in most of existing python web frameworks. Zope, Pyramid, Django, Flask, CherryPy and similar are candidates.
Another common problem with form libs is a non-unique look and feel of the available widget collection. YAFOWIL tries to provide some useful addon widgets which tries to take care of a unified user experience.
YAFOWIL aims to have no dependencies to any web framework. It utilizes the node package. YAFOWIL does not know about data-storage, but offers a hook to add processing callback handler functions.
YAFOWIL currently integrates with the following packages:
For details read the chapter integrations
.
For the impatient, code says more than 1000 words: A simple example form works like so:
import yafowil.loader
from yafowil.base import factory
from yafowil.controller import Controller
Produce a form.:
form = factory('form', name='myform', props={
'action': 'http://www.domain.tld/someform',
})
form['someinput'] = factory('label:text', props={
'label': 'Your Text',
})
def formaction(widget, data):
data.printtree()
def formnext(request):
return 'http://www.domain.tld/result'
form['submit'] = factory('submit', props={
'handler': formaction,
'next': formnext,
'action': True,
})
Render empty form by calling the form object:
rendered = form()
This results in:
<form action="http://www.domain.tld/someform"
enctype="multipart/form-data"
id="form-myform"
method="post">
<label for="input-myform-someinput">Your Text</label>
<input id="input-myform-someinput"
name="myform.someinput"
type="text"/>
<input id="input-myform-submit"
name="action.myform.submit"
type="submit"
value="submit" />
</form>
Get form data from of request (request is expected dict-like):
request = {'myform.someinput': 'Hello World',
'action.myform.submit': 'submit'}
controller = Controller(form, request)
controller.data
<RuntimeData myform, value=None, extracted=None at ...>
<RuntimeData myform.someinput, value=None, extracted='Hello World',
attrs={'input_field_type': 'text'} at ...>
<RuntimeData myform.submit, value=None, extracted=<UNSET> at ...>
A widget is an instance of a blueprint created by the factory. Factory is a singleton and operates also as a registry for blueprints.
By calling the factory a widget is created, here a naked text input field from
the blueprint text
:
widget = factory('text')
Blueprints can be chained by colon separated names or given as list:
widget = factory('field:label:text')
This causes the created widget to chain the registered renderers, extractors,
and other parts of the blueprints field
, label
and text
in order.
Blueprint chains can be organised using as macros to reduce the complexity of factory calls (details below). I.e.:
widget = factory('#field:text')
expands the macro #field
to field:label:error
and appends :text
so
the result is field:label:error:text
.
Any HTML form can be visualized as a tree: The <form>
is the root,
<input>
elements are its children. Also a <fieldset>
grouping a bunch of
<input>
is a compound with children. In YAFOWIL the form is organized as a
tree which is like a ordered python dict
with each value a dict again. Widget
instances are dict-like
objects.
Thus, a widget is either a compound node (containing children) or a leaf node in this tree.
Building widget trees is as simple as using python dicts:
form = factory('form', 'UNIQUENAME', props={
'action': 'someurl',
})
form['somefield'] = factory('field:label:text', props={
'label': 'Some Field',
})
form['somefieldset'] = factory('fieldset', props={
'legend': 'A Fieldset',
})
form['somefieldset']['innerfield'] = factory('field:label:text', props={
'label': 'Inner Field',
})
The way a widget is rendered is controlled by the mode. Every widget may given
a mode
keyword argument to the factory as a string or a callable accepting
two parameters widget
and ``data``returning a string.
These modes are supported:
edit
edit_renderers
.display
display_renders
.skip
In YAFOWIL validation and extraction happens at the same time. Extraction means to get a meaningful value out of the request. Validation means to check constraints, i.e if a number is positive or an e-mail-adress is valid.
Invariants are implemented as extractors on compounds. Usally they are put as a custom blueprint (see below) with one extractor on some parent of the elements to validate.
Here is a short example (extension of the hello world
example) for a custom
invariant extractor which checks if one or the other field is filled, but never
both or none (XOR):
from yafowil.base import ExtractionError
# ... see helloworld example whats missing here
def myinvariant_extractor(widget, data):
if not (bool(data['hello']) != bool(data['world']):
error = ExtractionError(
'provide hello or world, not both or none')
data['hello'].error.append(error)
data['world'].error.append(error)
return data.extracted
def application(environ, start_response):
# ... see helloworld example whats missing here
form = factory(
u'*myinvariant:form',
name='helloworld',
props={
'action': url,
},
custom={
'myinvariant': dict(extractors=[myinvariant_extractor]),
}
)
form['hello'] = factory('field:label:error:text', props={
'label': 'Enter some text here',
'value': '',
})
form['world'] = factory('field:label:error:text', props={
'label': 'OR Enter some text here',
'value': '',
})
# ... see helloworld example whats missing here
General behaviours (rendering, extracting, etc...) can be registered as blueprint in the factory:
factory.register(
'myblueprint',
extractors=[myvalidator],
edit_renderers=[],
display_renderers=[],
preprocessors=[],
builders=[])
and then used as regular blueprint when calling the factory:
widget = factory('field:label:myblueprint:text', props={
'label': 'Inner Field',
})
It’s possible to inject custom behaviour by marking a part of the blueprint
chain with the asterisk *
character. Behaviours are one or a combination
of a
extractor
edit_renderer
display_renderer
builder
preprocessor
def myvalidator(widget, data):
# validate the data, raise ExtractionError if somethings wrong
if data.extracted != 'something:'
raise ExtractionError("only 'something' is allowed as input.")
return data.extracted
widget = factory(
'field:label:*myvalidation:text',
props={
'label': 'Inner Field',
},
custom={
'myvalidation': dict(extractor=[myvalidator]),
}
)
YAFOWIL addon widgets are shipped with related Javascript and Stylesheet resources. These resources are registered to the factory with additional information like delivery order and resources group.
To help the integrator delivering these resources through the used web
framework, the helper object yafowil.resources.YafowilResources
is supposed
to be used.
The function configure_resource_directory
should be overwritten on deriving
class which is responsible to make the given physical resource directory
somehow available to the web.
The object can be instanciated with js_skip
and css_skip
keyword
arguments, which contain iterable resource group names to skip when calculating
resources. This is useful if basic or dependency resources are already shipped
in another way.
The following example shows how to integrate YAFOWIL resources in a pyramid application.
from pyramid.static import static_view
from yafowil.resources import YafowilResources
import mypackage.views
class Resources(YafowilResources):
def __init__(self, js_skip=[], css_skip=[], config=None):
self.config = config
super(Resources, self).__init__(js_skip=js_skip, css_skip=css_skip)
def configure_resource_directory(self, plugin_name, resourc_edir):
# instanciate static view
resources_view = static_view(resourc_edir, use_subpath=True)
# attach resources view to package
view_name = '%s_resources' % plugin_name.replace('.', '_')
setattr(mypackage.views, view_name, resources_view)
# register view via config
view_path = 'mypackage.views.%s' % view_name
resource_base = '++resource++%s' % plugin_name
self.config.add_view(view_path, name=resource_base)
return resource_base
def includeme(config):
# resources object gets instanciated only once
resources = Resources(config=config)
# sorted JS resources URL's. Supposed to be rendered to HTML
resources.js_resources
# sorted CSS resources URL's. Supposed to be rendered to HTML
resources.css_resources