Yota: Flexible forms with asynchronous validation¶
Yota is a Python form generation library with the following unique features:
- Easy integration of realtime validation. Trigger a server side form validation with any JavaScript event on your input fields. (Client side in planning)
- Dynamic form structures allow for complex forms with on the fly changes. Inject different input fields or validation methods into a specific instance of your Form where needed.
- Default themed with Bootstrap, allowing you to quickly throw together useful forms that look nice.
In addition to these features, Yota also includes most of the features that you would see with other form libraries.
- Simple declarative syntax for defining form validation and layout
- Customizable template driven schemas
- Ability to operate with almost any framework and use any rendering engine. (Default is jinja2)
Philosophically Yota aims to have a ton of flexibility, since designing powerful webforms is infrequently a cookie cutter operation. This was the main problem the designers had with other libraries is that they ended up getting in the way if they wanted to do anything abnormal. At the same time however it is important that sensible default be easy to use and implement, making the creation of common forms trivial and lowering the inital learning curve.
Overall Architecture¶
Yota allows you to create Forms quickly by declaring a class that is made up of Nodes and Checks. Nodes drive the rendering of your form while Checks drive validation of user input. Yotas power is derived from its integration of server side and client side components, and a growing set of quality default Nodes and Validators.
Contents¶
Using Forms¶
A Simple Form¶
This is the core of Yota’s functionality. To create a Form with Yota you must inherit from the From superclass like in the following example.
from yota import Form
from yota.nodes import *
class PersonalForm(Form):
first = EntryNode()
last = EntryNode()
address = EntryNode()
submit = SubmitNode(title="Submit")
Forms are simply a collection of Nodes and Checks. The Checks drive validation of the Form and will be talked about next, while the Nodes drive rendering. Conceptually Nodes can be thought of as a single input section in your form, but it can actually be anything that is destined to generate some HTML or Javascript in your Form. For example you may wish to place a header at the beginning to the Form even though it isn’t used for any data entry. Most keyword arguments passed to a Node are passed directly to their rendering context, and thus their use is completely up to user choice. More information on Nodes can be found in the Nodes documentation section. Your new Form class inherits lots of functionality for common tasks such as rendering and validation.
To render our Form we can call the Form.render()
function on an instance of our Form object:
>>> personal = PersonalForm()
>>> personal.render()
'<form method="post">
...
</form>'
As talked about in the Node documentation, each Node by default has an
associated template that is used to render it. The render function essentially
passes the list of Nodes in the Form onto the Renderer. Most renderers will
render each each Node’s template and append them all together. In addition to
the Nodes that you have defined in your subclass, a Node for the beginning and
end of your Form will automatically be injected. The is a convenience that can
be disabled by setting the Form.auto_start_close
to False. We
can see this functionality in action in the below example:
>>> form = PersonalForm()
>>> for node in form._node_list:
... print node._attr_name
...
start
first
last
address
submit
close
Even though ‘first’ was our first element in the Form and ‘submit’ was our last,
the Nodes ‘start’ and ‘close’ have been prepended and appended respectively. By
default these Nodes load from templates ‘form_open.html’ and ‘form_close.html’,
however these values can be easily overridden, as can the entire start and
close Nodes. For more information see the Form.auto_start_close
,
Form.start_template
, Form.close_template
. Passing in a ‘start’
or ‘close’ attribute, either through keywords or subclass attributes, will
override the default generated Nodes, but it will still place them at the
beginning and end.
Validation Intro¶
To add some validation to our Form we need to create a Check
. Checks are just containers for Validators and hold information about how the Validator should be executed. The below code will add a Check for the ‘first’ Node
to ensure a minimum length of 5 characters.
from yota import Form
from yota.nodes import *
from yota.validation import
class PersonalForm(Form):
first = EntryNode()
_first_valid = Check(MinLengthValidator(5), 'first')
last = EntryNode()
address = EntryNode()
submit = SubmitNode(title="Submit")
The constructor prototype may help provide some reference for the explaination:
When you define a Check object you are essentially specifying a Validator that needs to be run when the Form data is validated, and the information that needs to be passed to said Validator. Attr_args and attr_kwargs should be strings that define what data will get passed into the Validator at validation time. For instance in the above example that data that was entered for the ‘first’ Node will get passed to the validator. More information on Checks and Validators can be found on the Validators and Checks page.
Dynamic Forms¶
One of the key features of Yota is the ability to make changes to the Form schema at runtime with little effort. For example, say you wanted to make a Form that allowed the user to enter a list of names, and the form included a button that added another field with JavaScript. Or perhaps you would like to create a Form that is slightly different depending on session data. With a dynamic Form schema managing these situations can be much easier.
Since the Form object that is used to run validation after a submission needs to match the Form object that was used to originally render the Form there are some considerations that need to be made. There are of course many ways to try and solve this synchronization problem, but here is a straightforward solution that should apply to most situations.
This section currently needs expansion, however a thoroughly commented example can be found in the yota_examples github repository.
Form API¶
-
class
yota.
Form
(**kwargs)[source]¶ This is the base class that all user defined forms should inherit from, and as such it is the main way to access functionality in Yota. It provides the core functionality involved with setting up and rendering the form.
Parameters: - context – This is a context specifically for the special form open and form close nodes, canonically called start and close.
- g_context – This is a global context that will be passed to all nodes in rendering thorugh their rendering context as ‘g’ variable.
- start_template – The template used when automatically
injecting a start Node. See
yota.Form.auto_start_close
for more information. - close_template – The template used when automatically
injecting a close Node. See
yota.Form.auto_start_close
for more information. - auto_start_close – Dictates whether or not start and close Nodes will be automatically appended/prepended to your form. Note that this must be set via __init__ or your class definition since it must be set before __init__ for the Form is run.
- hidden – A dictionary of hidden key/value pairs to be injected into the form. This is frequently used to pass dynamic form parameters into the validator.
-
_event_lists
= {}¶
-
_gen_validate
(data, piecewise='auto', internal=False, resolver=None)¶ Runs all the validators associated with the
Form
.Returns: Whether validation was successful
-
_node_list
= []¶
-
_parse_shorthand_validator
(node)[source]¶ Loops thorugh all the Nodes and checks for shorthand validators. After inserting their checks into the form obj they are removed from the node. This is because a validation may be called multiple times on a single form instance.
-
_processor
¶ This is a class that performs post processing on whatever is passed in as data during validation. The intended purpose of this was to write processors that translated submitted form data from the format of the web framework being used to a format that Yota expects. It also allows things like filtering stripping characters or encoding all data that enters a validator.
alias of
FlaskPostProcessor
-
_renderer
¶ This is a class object that is used to perform the actual rendering steps, allowing different rendering engines to be swapped out. More about this in the section
Renderer
alias of
JinjaRenderer
-
_reserved_attr_names
= ('context', 'hidden', 'g_context', 'start_template', 'close_template', 'auto_start_close', '_renderer', '_processor', 'name')¶
-
_setup_node
(node)[source]¶ An internal function performs some safety checks, sets attribute, and set_identifiers
-
_submit_action
= False¶ Tracks whether you’re submitting the form, or just validating it for later json serialization
-
_success_data
= None¶ Hold information that will be serialized into success return values for render_json
-
_validation_list
= []¶
-
auto_start_close
= True¶
-
close_template
= 'form_close'¶
-
context
= {}¶
-
data_by_attr
()[source]¶ Returns a dictionary of currently stored
Node.data
attributes keyed byNode._attr_name
. Used for returning data after its been processed by validators.
-
data_by_name
()[source]¶ Returns a dictionary of currently stored
Node.data
attributes keyed byNode.name
. Used for returning data after its been processed by validators.
-
error_header_generate
(msgs)[source]¶ This function is generally used to generate a header on the start Node automatically when there is an error in validation. For instance, you might want to say “Please fix the errors below” or something similar. While it could actually be used for anything post-validation failure, it is better practice to create a listener that subscribes to “validation_failure” event, as this function is called at the same time.
Parameters: msgs (list) – This will be a list of all other Nodes that have messages, with the idea that you might want to list the errors that occurred.
-
g_context
= {}¶
-
get_by_attr
(name)[source]¶ Safe accessor for looking up a node by
Node._attr_name
-
insert_listener
(listener)[source]¶ Attaches a
Listener
to an event type. These Listener will be executed when trigger event is called.
-
insert_node
(position, new_node_list)[source]¶ Inserts a
Node
object or a list of objects at the specified position into theForm._node_list
of the form. Index -1 is an alias for the end of the list. After insertion theNode.set_identifiers()
will be called to generate identification for theNode
. For this to function,Form._attr_name
must be specified for the node prior to insertion.
-
insert_node_after
(prev_attr_name, new_node_list)[source]¶ Runs through the internal node structure attempting to find a
Node
object whosNode._attr_name
is prev_attr_name and inserts the passed node after it. If prev_attr_name cannot be matched it will be inserted at the end. Internally callsForm.insert()
and has the same requirements of theNode
.Parameters:
-
insert_validator
(new_validators)[source]¶ Inserts a validator to the validator list.
Parameters: validator (Check) – The Check
to be inserted.
-
name
= None¶
-
pysistor_adapter
= None¶
-
pysistor_backend
= None¶ This defines an adapter object that will be made availible to the Pysistor backend for use in storing the data. For instance, access to sessions frequently requires access to the request object and an adapter can carry that information. More information on this behaviour can be gotten in the pysistor documentation
-
render
()[source]¶ Runs the renderer to parse templates of nodes and generate the form HTML.
Returns: A string containing the generated output.
-
render_error
= False¶
-
render_json
(invalid=None, success=None, raw=False)[source]¶ This function takes the state that is stored internally and serializes it into a form that the yota JS library is designed to recieve.
-
render_success
= False¶
-
resolve_all
(data)[source]¶ This is a utility method that runs resolve_data on all nodes with the provided data dictionary.
-
set_json_success
(**kwargs)[source]¶ As opposed to using generate_json_success to pass information to the js success function you can use add_success in your view code.
-
start_template
= 'form_open'¶
-
success_json_generate
()[source]¶ Please see the documentation for
Form.error_header_generate()
as it covers this function as well as itself.
-
title
= None¶
-
type_class_map
= {'info': 'alert alert-info', 'warn': 'alert alert-warn', 'success': 'alert alert-success', 'error': 'alert alert-error'}¶ A mapping of error types to their respective class values. Used to render messages to the user from validation. Changing it to render messages differently could be performed as follows:
class MyForm(yota.Form): first = EntryNode(title='First name', validators=Check(MinLengthValidator(5))) last = EntryNode(title='Last name', validators=MinLengthValidator(5) # Override the default type_class_map with our own type_class_map = {'error': 'alert alert-error my-special-class', # Add an additional class 'info': 'alert alert-info', 'success': 'alert alert-success', 'warn': 'alert alert-warn'}
-
validate
(data, piecewise='auto', internal=False, resolver=None)[source]¶ Runs all the validators associated with the
Form
.Returns: Whether validation was successful
-
validate_json
(data, piecewise='auto', raw=False)[source]¶ The same as
Form.validate_render()
except the messges are loaded into a JSON string to be passed back as a query result. This output is designed to be used by the Yota Javascript library.Parameters: - piecewise – This parameter is deprecated. Piecewise is automatically detected from g_context.
- raw (boolean) – If set to True then the second return parameter will be a Python dictionary instead of a JSON string
Returns: A boolean whether or not the form submission is valid and the json string (or raw dictionary) to pass back to the javascript side. The boolean is an anding of submission (whether the submit button was actually pressed) and the block parameter (whether or not any blocking validators passed)
-
validate_render
(data)[source]¶ Runs all the validators on the data that is passed in and returns a re-render of the
Form
if there are validation message, otherwise it returns True representing a successful submission. Since validators are designed to pass error information in through theNode.msgs
attribute then this error information is in turn availible through the rendering context.Parameters: data (dictionary) – The data to be passed through the Form._processor. If the data is in the form of a dictionary where the key is the ‘name’ of the form field and the data is a string then no post-processing is neccessary. Returns: Whether the validators are blocking submission and a re-render of the form with the validation data passed in.
Nodes¶
Nodes drive the actual rendering of your Form
. Internally a
Form
keeps track of a list of Node
‘s and then passes them off
to the Renderer when a render of the Form
is requested. Lets
look at a simple example Form as shown in the introduction:
from yota import Form
from yota.nodes import *
class PersonalForm(Form):
first = EntryNode()
last = EntryNode()
address = EntryNode()
submit = SubmitNode(title="Submit")
All of the attributes defined in the above class are Node
instances.
Internally there is some trickery that preservers the order of these
attributes, but this is not important to understand for using them. Just
realize that unlike a regular object in Python, the order of these attributes
effects the output of your Form.
Note
Some attribute names are reserved and trying to overwrite them with
Node attributes will break things. Ensure that the names you select for your
Node attributes do not collide with parameters to Form
or keyword
attributes that you pass to your Form.
The canonical Node is just a reference to some kind of rendering template (by default, Jinaj2 templates) and some associated metadata that will control how the template is rendered.
A Simple Node¶
Let’s examine one of the builtin Nodes availible in Yota, and some of the
things we can do with it. Let us look at the nodes.EntryNode
. It has the
following template:
{% extends base %}
{% block control %}
<input data-piecewise="{{ piecewise_trigger }}"
type="text"
id="{{ id }}"
value="{{ data }}"
name="{{ name }}"
placeholder="{{ placeholder }}">
{% endblock %}
Above we see what looks vaugly like HTML. If you’re not familiar with Jinja it would be a good idea to give their documentation a cursory glance before proceeding much further. However, the jist is that the sections enclosed in double curly-braces {{ }} will be replaced with variables, while the {% %} enclosed areas represent some sort of control structure. The meat of the above template is the input field. You can see that most of its attributes are replaced by variables.
Now take a look at the extends portion on the first line of our template. This is actually importing another template which is used as the base for many different builtin Nodes in Yota. We can see that template here:
<div class="control-group">
{% block error %}
{% if errors %}
<div class="alert alert-error">
{{ errors[0]['message'] }}
</div>
{% endif %}
{% endblock %}
{% if label %}
{% block label %}
<label class="control-label" for="{{ name }}">{{ title }}</label>
{% endblock %}
{% endif %}
<div class="controls">
{% block control %}
{% endblock %}
</div>
</div>
This template is just the default horizontal form layout for Bootstrap. Up top you can see a section reserved for displaying errors and in the middle a section to display a label. At the bottom is where the other template gets injected through the magic of blocks. Again, refer to the Jinja2 documentation for more information on this.
The actual Node definition is basically nothing:
class EntryNode(BaseNode):
template = 'entry'
Notice that the template is just entry, not entry.html. This is because the renderer auto-appends the suffix so Nodes can be used across different templating engines.
To understand more of what’s going on under the covers, here’s some explaination about how the variables used in the above templates are generated.
Identifiers¶
Some of the values, such as id and name will get automatically generated by
Node.set_identifiers()
, and will be based off of what you name the
attribute in you class definition.
Data¶
The data attribute is automatically populated when validation is run. This is
performed by Node.resolve_data()
and talked about in the Custom Node
section below.
Errors¶
This attriubte is a list of errors generated by validator callables. More about this in validation.
Other¶
The remained of variables in the above template are just plain old attributes with defaults. Keep in mind that attriubtes/arguments in Yota do not behave quite like they do normally in Python. Learn more about this in Attribute and Argument behaviour in the Form page.
The majority of Node attributes may be overridden either through initialization of the function, like so:
my_node = EntryNode(name="Something else", template="custom_entry")
Or by setting it as a class attribute in your Node definition like so:
class EntryNode(BaseNode):
template = 'entry'
_ignores = ['template']
However, keep in mind that attributes that are auto-generated, such as name, id, and title should not be set as class attributes since they will get overriden when they are generated. By default, the following attributes are reserved:
- name
- id
- title
- errors
- data
- _attr_name
- _ignores
- _requires
- _create_counter
Custom Nodes¶
Most Node definitions are quite simple, with the majority simply changing the
template being used. More complex Node semantics are availible by overriding
some of their built in methods, such as Node.resolve_data()
or
Node.set_identifiers()
. These are all described in the API documentation,
but some examples will be given here of how you might wish to use these
methods.
Changing data resolution¶
The default Node implementation assumes that your Node only contains one input,
and as such its data output is assumed to be tied directly to this single input.
The Node.set_identifiers()
method defines a defualt implementation for
naming your input field that looks something like this:
try:
self.data = data[self.name]
except KeyError:
self.data = self._null_val
You can see above that the Node’s name is used to pick out the data that is associated with this Node. But say your Node includes multiple input fields, perhaps you have a date picker. A simple template may look like this:
Month: <input type="text" name="{ name }_month" placeholder="Month" /><br />
Day: <input type="text" name="{ name }_day" placeholder="Day" /><br />
Year: <input type="text" name="{ name }_year" placeholder="Year" /><br />
Now of course the Node.resolve_data()
will fail to find anything associated
with “name” since it doesn’t exist, and instead an implementation may look
something like this.
def resolve_data(self, data):
try:
day = data[self.name + '_day']
month = data[self.name + '_month']
year = data[self.name + '_year']
except KeyError:
self.data = self._null_val
# set data to a tuple of values for validation
self.data = (year, month, day)
Aside from our crappy looking form, and some lack of bounds checking everything is good. Now say we wanted to make this form work with AJAX, and we wanted to make the border of each of the form elements red when there was an error. Well this is a problem, because our JavaScript doesn’t implicity know how to find the elements. You could modify your render_error method to manually catch this case, but this wouldn’t be a very resiliant option. Instead, we can make our default functions aware of these extra elements. This is done through the json_identifiers method.
Modifying AJAX rendering¶
Node.json_identifiers()
is executed by validation methods when sending erros back to
the client side via JSON. It is used to give the client side inoformation about
where the error data should be placed in the DOM. Essentially your render_error
and render_success methods are passed an ‘ids’ object, and this is a direct
serialization of the return from this function. The default render_error and
render_success methods expect the following keys:
- ‘error_id’: This should be an id value of a DOM element that you would like to place your error ‘message’ in. This is not actually used by default, but is implemented by all builtin Nodes. It corresponds to the DOM element that renders regular errors.
- ‘elements’: This supplies a list of all ids of form elements in the Node. Error tooltips point to the first element.
set_identifiers¶
When the Node is added to a Form the set_identifiers method is called to setup some unique names to be used in the template and possibly AJAX. Perhaps you’d like a different semantic for automatically titling your date pickers? Overriding this function may also be wanted if you’re writing a Node with multiple form elements in it. This all depends on your preference.
def set_identifiers(self, parent_name):
super(MySuperSpecialNode, self).set_identifiers(parent_name)
if not hasattr(self, 'title'):
self.title = self._attr_name.capitalize() + " Very Special"
Builtin Nodes¶
Node API¶
-
class
yota.
Node
(**kwargs)[source]¶ Nodes are holders of context for rendering and displaying validating for a portion of your
Form
. This default base Node is designed to provide a template along with specific context information to a templating engine such as Jinja2. For validation a Node acts as an information source or an error sink. Essentially Nodes can be used to source data for use in aCheck
, and they can then be delivered some sort of validation error via a the internalerrors
attribute.Note
By default all keyword attributes passed to a Node’s init function are passed onto the rendering context. To override this, use the
Node._ignores
attribute.Parameters: - _attr_name (string) – This is how the Node is identified in the Form. If populated automatically if the Node is defined in an a Form class definition, however if the Node is added dynamically it will need to be defined before adding it to the Form.
- _ignores (list) – A List of attribute names to explicity not include in the rendering context. Mostly a niceity for keeping the rendering context clutter free.
- _requires (list) – A List of attributes that will be required at render time. An exception will be thrown if these attributes are not present. Useful for things like lists that require certain data to render properly.
- template (string) – String name of the template to be parsed upon rendering. This is passed into the Form._renderer so it needs to be whatever that is designed to accept. Jinja2 is looking for a filename like ‘node’ that occurs in it’s search path.
- validators – An optional attribute that specifies a
Check
object, or list of Check objects to be associated with the Node. This is automatically at render time. - _null_val – When form submission data is passed in for validation and
the
Node.resolve_data()
method cannot identify anything, the data attribute will be set to this value. Defaults to “”.
The default Node init method accepts any keyword arguments and adds them to the Node’s rendering context. In addition any class attributes may be added to custom Nodes and these attributes will be copied at instantiation time and passed into the rendering context.
-
_attr_name
= None¶
-
_create_counter
= 0¶ Allows tracking the order of Node creation
-
_ignores
= ['template', 'validator']¶
-
_null_val
= ''¶
-
_requires
= []¶
-
add_msg
(*args, **kwargs)[source]¶ This method serves mostly as a wrapper alowing for different message ordering semantics, or possibly error post-processing. Messages from validation methods should be added in this way allowing them to be pre-processed if needed. More information about what gets passed in in the Validators and Checks section.
-
data
= ''¶
-
get_context
(g_context)[source]¶ Builds our rendering context for the Node at render time. By default all attributes of the Node are added to the global namespace and the global rendering context is passed in under the variable ‘g’. This function is designed to be overridden for customization. :param g_context: The global rendering context passed in from the rendering method.
Parameters: g_context – This is the global context passed in from the parent Form object. By default it’s included under the ‘g’ key, similar to Flask’s globals.
-
get_list_names
()[source]¶ As the title suggests this needs to return an iterable of names. These should be names corresponding to form elements that the Node will generate. This list is uesed by piecewise validation to determine if a Node has been visisted based on a list of names that have been visited, bridging Nodes to elements.
-
json_identifiers
()[source]¶ Allows passing arbitrary identification information to your JSON error rendering callback. For instance, a common use case is the display an error message in a pre-defined div with a specific id. Well you may perhaps pass in an ‘error_div_id’ attribute to the JSON callback to use when trying to render this error. The default for Yota builtin nodes is to pass ‘error_id’ indicating the id of the error container in addition to a list containing all input elements in the Node’s ids.
-
label
= True¶
-
msgs
= []¶
-
piecewise_trigger
= 'blur'¶
-
resolve_data
(data)[source]¶ This method links data from input data into the Nodes. By default HTML form data is represented by a dictionary that is keyed by the ‘name’ attribute of the form element. Since most Nodes only render a single form element, and the default set_identifiers generates a single ‘name’ attribute for the Node then this function attempts to find data by linking the two together. However, if you were to change that semantic this would need to change. Look at the CheckGroupNode for a reference impplementation of this behaviour, or the Docs under “Custom Nodes”. This method should operate by setting its own data attribute, as this is how Validators conventionally look for data.
Parameters: data – The dictionary of data that is passed to your validation method call.
-
set_identifiers
(parent_name)[source]¶ This function gets called by the parent Form when it is initialized or inserted. It is designed to set various unique identifiers. By default it generates an id for the Node that is {parent_name}_{_attr_id}, a title for the Node that is the _attr_name capitalized, and a name for the element that is just the _attr_name. All of these attributes are then passed onto the rendering context of the Node by default. By default all of these attributes will yield to attributes passed into the __init__ method.
Parameters: parent_name (string) – The name of the parent form. Useful in ensuring unique identifiers on your element names.
-
template
= None¶
-
validators
= []¶
Validators and Checks¶
Validators allow you to provide users feedback on their input through structured, reusable callables. Validators can be supplied an arbitrary number of inputs as well as dispatch information (errors, warnings, etc) to an arbitrary number of output Nodes.
Using Validators In Your Form¶
Validators are generally added into your Form schema in a way similar to adding Nodes; that is, by declaring attributes in your Form definition. There is a long syntax that is more explicit as well as a shorthand that can add convenience for simple validators. The explicit declaration can be seen below through the definition of a Check.
class MyForm(yota.Form):
# This syntax shortens up the above explicit syntax for simple
# validators
first = EntryNode(title='First name')
_first_valid = Check(MinLengthValidator(5), 'first')
The syntax above defines a single yota.nodes.EntryNode
and an associated validator that
ensures the entered value is at least 5 characters long. This is done through
the declaration of a yota.Check
object. The Check accepts the actual
validator as its first argument, followed by the names of Nodes that you will
be validating. The above example binds our yota.validators.MinLengthValidator
to a Node with
the attribute name ‘first’. Later when we try to validate the Form the string
‘first’ will be used to lookup our Node and supply the appropriate information
to the validator method. Nodes in Yota are identified by their attribute name
as given in the class declaration. However, If you later add a Node
dynamically it will need to specify the _attr_name attribute upon declaration
explicitly. More on this in Dynamic Forms.
The above syntax gives us some nice power. We can supply that validation method with as many Nodes as we would like in a clear way. But what if we want to write a bunch of validators that only validate a single Node? Then the above is quite verbose, and below shows an implicit declaration that is a nice option for simple validators, and is just syntactic sugar for the above syntax.
class MyForm(yota.Form):
# This syntax shortens up the above explicit syntax for simple
# validators. An arg of 'first' will automatically be added to the
# Check object for you.
first = EntryNode(title='First name',
validators=Check(MinLengthValidator(5)))
# This even more brief syntax will automatically build the Check
# object for you since it's just boilerplate at this point
last = EntryNode(title='Last name', validator=MinLengthValidator(5)
# This syntax however is just like above. Be aware that your
# attribute name will not be automatically added since your
# explicitly defining args
address = EntryNode(validators=
Check(MinLengthValidator(9), 'address'))
# In addition, you can specify a list of validators, or a tuple
addr = EntryNode(title='Address', validators=[MinLengthValidator(5),
MaxLengthValidator(25)])
Note
If neither kwargs or args are specified and cannot be implicitly determined an exception will be thrown.
Validator Execution¶
With the regular form validation method Form.validate_render()
the error
values after validation are maintained in errors and passed into the rendering
context. In your Node
template, the error can then be used for
anything related to rendering and will contain exactly what was returned by
your validator.
With either the piecewise JSON validation method or the regular JSON validation method the data will get translated into JSON. This JSON string is designed to be passed back via an AJAX request and fed into Yota’s JacaScript jQuery plugin, although it could be used in other ways. Details about this functionality are in the AJAX documentation section.
To continue our example series above, we may now try and execute a validation action on our Form. For this example we will use a Flask view, although the concepts should be fairly obvious and transfer to most frameworks easily.
class MyForm(yota.Form):
# Our same form definition as above but stripped of the now un-needed
# comments
first = EntryNode(title='First name',
validators=Check(MinLengthValidator(5)))
last = EntryNode(title='Last name', validators=MinLengthValidator(5)
address = EntryNode(validators=
Check(MinLengthValidator(9), 'address'))
# In Flask routes are declared with annotations. Basically mapping a URL to
# this method
@app.route("/ourform", methods=['GET', 'POST'])
def basic():
# Create an instance of our Form class
form = MyForm()
# When the form is submitted to this URL (by default forms submit to
# themselves)
if request.method == 'POST':
# Run our convenience method designed for regular forms
# 'success' if validation passed, 'out' is the re-rendering of the form
success, out = form.validate_render(request.form)
# if validation passed, we should be doing something
if success:
# Load up our validated data
data = form.get_by_attribute()
# Create a pretend SQLAlchemy object. Basically, we want to try
# and save the data somehow...
res = User(first=data['first'],
last=data['last'],
address=data['address'])
# Attempt to save our changes
try:
DBSession.add(new_user)
DBSession.commit()
except (sqlalchemy.exc, sqlalchemy.orm.exc) as e:
# An error with our query has occurred, change the message
# and update our rendered output
out = form.update_success(
{'message': ('An error with our database occurred!')})
else:
# By default we just render an empty form
out = form.render()
def success_header_generate(self):
return {'message': 'Thanks for your submission!'}
return render_template('basic.html',
form=out)
Making Custom Validators¶
A validator should be a Python callable. The callable will be accessed through
a Check
object that provides context on how you would like your
validator to be executed in this given instance. Checks are what provide your
validation callable with the data it is going to validate. Essentially they are
context resolvers, which is part of what allows Yota to be so dynamic.
When the validation callable is run it is supplied with a reference to a
Node
. The submitted data that is associated with that Node
will be loaded into the data attribute automatically. At this point, perhaps an
example will help clarify.
import yota
def MyValidator(node_in):
if len(node_in.data) > 5:
node_in.add_msg({'message': "You're text is too long!"})
class MyForm(yota.Form):
test_node = yota.nodes.EntryNode()
_test_check = yota.validators.Check(MyValidator, 'test_node')
In the above exmaple we made a simple validator that throws an error if your input value is longer than 5 characters. You can see the creation of the Check instance in the Form declaration supplies the string ‘test_node’. This is indicating the name of the Node that you would like to supply to the Validator as input.
Note
In Yota, all Nodes are uniquely identified by an attribute _attr_name. This gets automatically set to the value of the attribute you assigned the Node to in your Form declaration.
Later when the validator is to be called the string is replaced by a refernce
to a Node
with the specified Node._attr_name
. The method
behind this maddness is that it allows for dynamically adding Nodes at and up
until vaildation time, as well as dynamic injection of validation rules
themselves. In addition your validation methods can now request as much data
as you’d like, and subsequently can disperse errors to any Nodes they are
supplied with.
Return Semantics¶
Validators need not return anything explicitly, but instead provide output by
appending error information to one of their supplied Node’s errors list
attribute via the method Node.add_msg()
. This method is simply a
wrapper around appending to a list so that different ordering or filtering
semantics may be used if desired. The data can be put into this list is fairly
flexible, although a dictionary is recommended. If you are running a JSON based
validation method the data must by serializable, otherwise it may be anything
since it is merely passed into the rendering context of your templates.
That said, the builtin templtes are setup to recieve specific things. The default templates are setup to look two keys: a dictionary with a single key ‘message’ which will be printed, and ‘type’ to denote the alert style. If a type is ommitted then type will default to an error for rendering purposes. Looking at a builtin validator should provide additional clarity.
class IntegerValidator(object):
""" Checks if the value is an integer and converts it to one if it is
:param message: (optional) The message to present to the user upon failure.
:type message: string
"""
# A minor optimization that is borderline silly
__slots__ = ["message"]
def __init__(self, message=None):
self.message = message if message else "Value must only contain numbers"
super(IntegerValidator, self).__init__()
def __call__(self, target):
# This provides a conversion as well as a validation
try:
target.data = int(target.data)
except ValueError:
# Type can be safely ommitted because this is an error
target.add_msg({'message': self.message})
For rendering errors you may notice the _type_class key being looked for in the
error.html template. This is generated internally from what you enter as ‘type’
in your return dictionary. This is resolved by the Form.type_class_map
,
which maps types in the key to classes to be applied in the value. An example
usage might be that you’d like to add your own class to the error display.
Note
If you wish to make use of Special Key Values you will be required to use dictionaries to return errors.
Special Key Values¶
Builtin Validators¶
The default pattern for builtin Validators in Yota is to return a dictionary
with a key ‘message’ containing the error. This is also the pattern that the
builtin Node
‘s except when rendering errors, and therefore is the
recommended format when building your own validators.
Check API¶
-
class
yota.
Check
(callable, *args, **kwargs)[source]¶ This object wraps a validator callable and is intended to be used in your Form subclass definition.
Parameters: - validator (callable) – This is required to be a callable object that will carry out the actual validation. Many generic validators exist, or you can roll your own.
- args (list) – A list of strings, or a single string, representing that _attr_name of the Node you would like passed into the validator. Once a validator is called this string will get resolved into the Node object
- kwargs (dict) – Same as args above except it allows passing in node information as keyword arguments to the validator callable.
Check objects are designed to be declared in your form subclass.
Renderers¶
Custom Templates¶
Most people end up needing to design templates different from the ones built in at some point. Because of this Yota is setup for specifying a search path for custom templates. By default Yota will only look at its own template directory. It is typical to add a search path that points somewhere within your project. Yota will take the first template it finds that matches, so a simple way to ensure your custom templates are prioritized is to insert your path first in the list. A typical example might look something like this:
import os
from yota.renderers import JinjaRenderer
JinjaRenderer.search_path.insert(0, os.path.dirname(os.path.realpath(__file__)) +
"/assets/yota/templates/")
Switching Template Sets¶
Yota provides potential for multiple default template sets. The default template
set is designed for use with Bootstrap 2.3.2, but a Bootstrap 3.0 implementation
can used by modifying the JinjaRenderer attribute templ_type to ‘bs3’. More can
be read at renderers.JinjaRenderer.templ_type
.
Rendering Engines¶
By default Jinja2 is the renderer for Yota, however support for other renderes
is possible by setting the Form._renderer
to a different class that
implements the proper interface. Currently the default and only option is
renderers.JinjaRenderer
, however other implementations should be easy to write.
The default Nodes Node.template
property lacks a file extension and
expects the renderer to auto-append this before calling the template, thus
allowing the Node to work accross different renderers.
Renderers are invoked when a render method of a Form
is executed.
currently these include Form.render()
and Form.validate_render()
.
renderers were designed mainly to allow the interchange of template engines and
context gathering semantics.
Renderer Interface¶
As of now only one method must be implemented by a Renderer: the render method. It accepts two parameters, a list of Nodes to be rendererd in order and a dictionary that contains the global context to include in every template context. Looking at the source for JinjaRenderer will provide some guidence on how you might write your own Renderer.
Switching Renderers¶
A standard pattern would be to set the Form class object Form._renderer
attribute allowing the attribute change to be effectively global. This would
normally be done in whatever setup function your web framework provides.
JinjaRenderer API¶
-
class
yota.renderers.
JinjaRenderer
[source]¶ -
env
¶ Simple lazy loader for the Jinja2 enviroment
-
render
(nodes, g_context)[source]¶ Loop over each Node passed in by nodes and render it into a big blob of a string. Passes g_context to each nodes
Node.get_context()
.
-
search_path
= []¶ The list of paths that Jinja will look for templates in. It scans sequentially, so inserting custom template paths at the beginning is an easy way to override default templates without touching Yota. The default path is appended to the end of this list the first time render is called.
-
suffix
= '.html'¶ The default template suffix
-
templ_type
= 'bs2'¶ Allows you to switch the default template set being used. Default templates for JinjaRenderer are stored in /templates/jinja/{templ_type}/. Below code being run before your render method will change templates to Bootstrap 3.0. This is usually run when setting up web framework configs to take effect globally. See the flask example for more information.
import os from yota.renderers import JinjaRenderer JinjaRenderer.templ_type = "bs3"
Note
In order to display errors correctly the
Form.type_class_map
must be overriden so alert-error can be changed to alert-danger for Bootstrap 3.
-
AJAX Validation¶
Yota provides a JavaScript architecture in addition to specializatied validation methods to enable easy construction of AJAX based Form validation. Yota supports two main modes of AJAX based validation: piecewise and on-submit. Piecewise validation attempts to validate a portion of the Form (only the portions that the user has visited) on some kind of trigger event, such as the user moving to the next form element, or keydown on a specific element. On-submit is just what it sounds like: when the form is submitted an asynchronous server call is made and the validation results are rendererd or a success action is executed. These features allow you to give your user faster feedback on what mistakes they made to ease the form filling process.
Note
The AJAX validator examples below rely on using the id attribute
for a Node. This value is set by the default in Node.set_identifiers()
.
Server side implementation for AJAX validation is designed to be used with
Form.json_validate()
. When a submission is detected (usually by detecting
a POST request), the method can be run to return the json
encoded validation results as the response. This response is then in turn
parsed by Yota’s JavaScript library which can then execute callback functions
that you can design. To a user of the library, the implementation differeneces
of on-submit and piecewise are minor.
Note
Yota’s JavaScript library is desinged as a jQuery plugin, and as such jQuery is also required to use these features.
As is classic for jQuery plugins configuration information is passed to Yota’s library through an options object.
-
options.
render_error
¶
This attribute should be a function. It is called whenever new information is recieved about a Node. The status attribute dictates what action should be performed.
param string status: This dictates the type of new information that was recieved. The first state is “error”, and this means the Node is recieving an error for the first time. Common actions would be to un-hide an error div, or something similar. The second state is “update”. This means that an error is currently registered with a Node, however we’ve recieved another batch of error for that Node. The errors are not necessarily different than the current errors. Finally, “no_error” indicates that there is no longer an error at this Node, and error messages should be removed. This will only be called if there is currently an error registered at the Node. param object ids: This is the return information from the Node.json_identifiers()
function for the Node with which the error is being registered. It was intented to connect the rendering context that generates the DOM to your JavaScript that will be injecting into the DOM.param object data: This is the json encoded Node.errors
that should be populated by your validators. More about this can be found in the Node documentation, or the Validation documentation.
-
options.
render_success
¶
This attribute should be a function. It is called when the form submission succeeds, or rather it doesn’t block. More information on blocking can be found in the Validators section.
param object data: This is information directly generated from your Form.success_header_generate()
function. It is setup to display some sort of message that applies to the entire form such as an error working with the database, or proper submission of the data. In addition, some special key values can be set to trigger the execution of builtin convenience methods, such as redirection of the browser. More on this in Success Actions.param object ids: This is the return information from the Node.json_identifiers()
function for the start Node. It was intented to connect the rendering context that generates the DOM to your JavaScript that will be injecting into the DOM.
-
options.
piecewise
¶
Whether or not this form should be processed in a piecewise fashion. The default Node teamplte form_open will automatically populate this option when you put ‘piecewise’ in your global context.
Success Actions¶
As was touched on in the JavaScript render_success function above, the method
Form.success_header_generate()
can be overriden to perform common post
submission actions, or to pass information that you may want to use in your
render_success function. The default render_success method will look for a
‘message’ key in the return value and display this in a Bootstrap success
alert, or do nothing if this key is not present. All actions are performed upon
successful submission, but prior to the render_success
method being called.
A simple example is shown below.
class MyForm(yota.Form):
first = EntryNode(title='First name',
validator=Check(MinLengthValidator(5)))
def success_header_generate(self):
return {'message': 'Thanks for your submission!'}
Or if we wanted to redirect the user after submitting the form:
class MyForm(yota.Form):
first = EntryNode(title='First name',
validator=Check(MinLengthValidator(5)))
def success_header_generate(self):
return {'redirect': 'http://google.com/'}
Information on the avilible special post-submission actions are below.
Redirection¶
Include the key ‘redirect’ in your return dictionary and the browser will
be sent to the url specified via method window.location.replace
.
Google Analytics Logging¶
Under the key ‘ga_run’ return a list or tuple of four values, matching the four
values used in Google Analytics API function ga
. More information can be
found at the URL below.
https://developers.google.com/analytics/devguides/collection/analyticsjs/events
Clear Form Elements¶
Pass the key ‘clear_element’ equal to True and upon submission all input fields in the form will be reset.
Custom Action¶
Include the key ‘custom_success’ as a string of valid JavaScript and it will be evaled for you.
On-Submit Validation¶
A simple on submit validation should be very simple if you’re sticking with the default Nodes. These Nodes are already setup to pass the required error div ids and element ids to the client using the default render_error function in Yota’s JavaScript library, so all you really need to do is set the global context key ‘ajax’ to equal True. This activates the JavaScript library.
Piecewise Validation¶
On-Submit validation only gives the user feedback when he has submitted the Form, but what if we want to provide more instant feedback? Piecewise validation allows us to fire off a server request to validate the form as we’re filling it out based on any JavaScript based trigger.
The server side of this implementation is almost identical to On-Submit validation except that you want to pass the key ‘piecewise’ to the g_context. Again, this simply triggers the JavaScript library to behave slightly different. All builtin Nodes are designed to work out of the box with the default AJAX callback functions.
Validation Tiggers¶
A per-Node attribute ‘piecewise_trigger’ allows you to set when you would like the Form to be submitted for incremental validation. This can be any JavaScript event type that your input field supports, and defaults to “blur”. Common values may be click, change, dblclick, keyup or keydown.
These event triggers are activated when the Yota jQuery plugin is initially called. It scans all input fields in your Form and attaches an AJAX submit action to the input element based on the value of the attribute “data-piecewise”. In the default Nodes this is set by the attribute “piecewise_trigger” as can be seen in the code of the entry.html default template.
{% extends base %}
{% block control %}
<input data-piecewise="{{ piecewise_trigger }}"
type="text"
id="{{ id }}"
value="{{ data }}"
name="{{ name }}"
placeholder="{{ placeholder }}">
{% endblock %}