Callbacks and forms¶
Principle¶
With Nagare, you directly associate a standard Python methods or callbacks to the links and to the form elements, without explicitly naming them and without manual analysis of the requests received.
When the callbacks are processed, you can freely modified the components graph of the application.
In this example, a callback is associated to a link, to count how many times this link is clicked:
from nagare import presentation
from nagare.namespaces import xhtml
class ClickCounter(object):
def __init__(self):
self.nb = 0
def clic(self):
self.nb += 1
@presentation.render_for(ClickCounter)
def render(self, h, *args):
return h.p(
'You have ', h.a('clicked').action(self.clic),
' ', self.nb, ' times.'
)
To test this snippet, launched it with:
<NAGARE_HOME>/bin/nagare-admin serve-module <path/to/the/example/file.py>:ClickCounter clic
This simple GuessBook example associates a callback to a textarea element, that append the new messages to the list of the existing ones:
from nagare import presentation, var
from nagare.namespaces import xhtml
class GuestBook(object):
def __init__(self):
self.msgs = []
def add_message(self, msg):
self.msgs.append(msg)
@presentation.render_for(GuestBook)
def render(self, h, *args):
with h.div:
for msg in self.msgs:
h << h.blockquote(msg) << h.hr
with h.form:
h << 'New message:' << h.br
h << h.textarea.action(self.add_message) << h.br
h << h.input(type='submit', value='Send')
return h.root
You can test it with:
<NAGARE_HOME>/bin/nagare-admin serve-module <path/to/the/example/file.py>:GuestBook book
Form elements¶
Callbacks registration¶
Element | Callback registration | Callback parameters |
---|---|---|
h.a |
.action(callback) |
no parameter |
h.form |
.pre_action(callback) |
no parameter |
h.form |
.post_action(callback) |
no parameter |
h.input |
.action(callback) |
the text received |
h.input(type='text) |
.action(callback) |
the text received |
h.input(type='password') |
.action(callback) |
the text received |
h.input(type='radio') |
.action(callback) |
no parameter |
h.input(type='checkbox') |
.action(callback) |
the value of the value attribute (on if no
value attribute is set) |
h.input(type='submit') |
.action(callback) |
no parameter |
h.input(type='hidden') |
.action(callback) |
the value of the value attribute (empty string
if no value attribut is set) |
h.input(type='file') |
.action(callback) |
an empty string if no file was uploaded, else
a cgi.FieldStorage object |
h.input(type='image') |
.action(callback) |
the callback is called twice:
|
h.textarea |
.action(callback) |
the text received |
h.select |
.action(callback) |
the selected option value |
h.select(multiple=1) |
.action(callback) |
a tuple of the selected options value (even if only one option is selected) |
h.img |
.action(callback) |
no parameter
The callback is called when the browser requests
the image. The callback is called with the response
object as parameter and must return the image data.
If the content_type attribut of the response
object is not set by the callback, Nagare will try
to guess the image type. |
Note
Nagare is a full unicode framework: the text values are passed to the callbacks as unicode strings.
Callback order¶
The callbacks are processed in this order:
- the
form
pre_action()
callback - the
select
,textarea
andinput
of typetext
,password
,radio
,checkbox
,hidden
andfile
action()
callback - the
form
post_action()
callback - the
a
andinput(type='submit')
action()
callback - the
input(type='image')
action()
callback - the
img
action()
callback
Warning
Only modify the components graph in the a
, input(type='submit')
or
input(type='image')
callbacks.
Elements selection¶
Some form elements have an helper .selected()
method to select it:
Element | .selected() parameter |
---|---|
h.input(type='radio') |
a boolean to select or unselect the radio button |
h.input(type='checkbox') |
a boolean to select or unselect the checkbox |
h.option |
a list of values of the options to select |
Checkboxes reset¶
The callbacks of the h.input(type='checkbox')
are only called for the
selected checkboxes. So, you need to reset the attributes that keep the current
selected checkboxes before to process the callbacks. This reset method is
typically the `pre_action
callback of the h.form
:
from nagare import presentation
class ColorSelection(object):
def __init__(self):
self.reset_colors()
def reset_colors(self):
self.colors = []
def set_color(self, color):
self.colors.append(color)
@presentation.render_for(ColorSelection)
def render(self, h, *args):
h << 'Selected colors: ' << ', '.join(self.colors) << h.hr
with h.form.pre_action(self.reset_colors):
h << h.input(type='checkbox', value='blue').action(self.set_color).selected(u'blue' in self.colors) << 'blue' << h.br
h << h.input(type='checkbox', value='white').action(self.set_color).selected(u'white' in self.colors) << 'white' << h.br
h << h.input(type='checkbox', value='red').action(self.set_color).selected(u'red' in self.colors) << 'red' << h.br
h << h.br << h.input(type='submit', value='Send')
return h.root
or, with a more compact view:
@presentation.render_for(ColorSelection)
def render(self, h, *args):
h << 'Selected colors: ' << ', '.join(self.colors) << h.hr
with h.form.pre_action(self.reset_colors):
for color in (u'blue', u'white', u'red'):
h << h.input(type='checkbox', value=color).action(self.set_color).selected(color in self.colors)
h << color << h.br
h << h.br << h.input(type='submit', value='Send')
return h.root
The var.Var()
objects¶
The instances of the Var
class in nagare.var.Var
are
variable-like objects that provide a “functional” interface:
- calling the object with a value, set the variable value and return the value
- calling the object without a value, return the variable value
They can be usefull to directly get / set variables in a lambda expression callback.
For example, the first snippet can be rewrote as:
from nagare import presentation, var
from nagare.namespaces import xhtml
class ClickCounter:
def __init__(self):
self.nb = var.Var(0)
@presentation.render_for(ClickCounter)
def render(self, h, *args):
return h.p(
'You have ',
h.a('clicked').action(lambda: self.nb(self.nb() + 1)),
' ', self.nb, ' times.'
)
Form validation¶
Erroneous element¶
Calling the .error(msg)
method on an element wrap it into styled div
if msg
is not None
:
>>> from nagare.namespaces import xhtml
>>> h = xhtml.Renderer()
>>> tree = h.input
>>> print tree.write_xmlstring(pretty_print=True)
<input/>
>>> tree = h.input.error(None)
>>> print tree.write_xmlstring(pretty_print=True)
<input/>
>>> tree = h.input.error('Erroneous field')
>>> print tree.write_xmlstring(pretty_print=True)
<div class="nagare-error-field">
<div class="nagare-error-input">
<input/>
</div>
<div class="nagare-error-message">Erroneous field</div>
</div>
So your application can defined the nagare-error-field
, nagare-error-input
and nagare-error-message
css classes to design the erroneous fields rendering.
The editor.Property
objects¶
The instances of the Property
class in nagare.editor.Property
are objects:
- with a validation function, set by calling its
.validate()
method. A validation function, when called with a value, must validate and, eventually, convert it. If the value is valid, the (converted) value is returned. Else theValueError
exception must be raised with an error message. - that can be set with a value, by calling its
.set()
method or by directly be called - that always keeps the value in its
input
attribute - that validates the value, by calling its validating function
- that keeps the last valid value in its
value
attribute - that keeps the error message of the validation in its
error
attribute, orNone
if the value is valid.
>>> from nagare.editor import Property
>>> def is_lesser_than_10(n):
... if n<10:
... return n
... else:
... raise ValueError, 'must be lesser than 10'
>>> number = Property()
>>> number.validate(is_lesser_than_10)
<nagare.editor.Property object at 0x842348c>
>>> # Valid value
>>> number.set(2)
>>> number.input
2
>>> number.value
2
>>> number.error
>>> # Invalid value
>>> number.set(20)
>>> number.input
20
>>> number.value
2
>>> number.error
u'must be lesser than 10'
# Setting a valid value by calling the property
>>> number(2)
2
>>> number.input
2
>>> number() # Calling the property without a value return its ``input`` value
2
>>> number.value
2
>>> number.error
# Setting an invalid value by calling the property
>>> number(20)
20
>>> number.input
20
>>> number() # Calling the property without a value return its ``input`` value
20
>>> number.value
2
>>> number.error
u'must be lesser than 10'
The validator
module¶
The nagare.validator
module defines a IntValidator
and a
StringValidator
objects to validate the numbers and the strings. They are
suitable to be used as a validating function of a editor.Property
object:
>>> from nagare.validator import IntValidator
>>> from nagare.editor import Property
>>> number = Property()
# The validating function, first creates a ``IntValidator()``, then checks
# the value is lesser than 10 and positive, and, finally, convert it to an
# integer before to return it
>>> number.validate(lambda v: IntValidator(v).lesser_than(10).greater_than(0))
>>> number(2)
2
>>> number.input
2
>>> number.value
2
>>> number.error
>>> number(20)
20
>>> number.input
20
>>> number.value
2
>>> number.error
u'Must be lesser than 10'
>>> number(-1)
-1
>>> number.input
-1
>>> number.value
2
>>> number.error
u'Must be greater than 0'
The editor.Editor
objects¶
An instances of the Editor
class in nagare.editor.Editor
is an intermediary between the values received from a form an a target object.
An Editor
object automatically creates editor.Property
objects reflecting
the attributes of the target object we want to edit with the form.
An Editor
object will modified the target object attributes only if all the
validating functions of its properties validate the data from the form.
Putting all together¶
In this example, we’ve got pure Python Person
objects with name
and
age
attributes:
from nagare import editor, presentation, component
from nagare.validator import StringValidator, IntValidator
class Person(object):
def __init__(self):
self.name = ''
self.age = 0
We create a dedicated editor for this objects, to be able to edit these attributes with a web form:
class PersonEditor(editor.Editor):
# The ``__init__`` method receives the target ``Person`` object
def __init__(self, person):
# We create the ``name`` and ``age`` ``Properties``, initialized
# with the values from the target object
super(PersonEditor, self).__init__(person, ('name', 'age'))
# We use a string validator to check that the name is not empty i.e
# the name is required
self.name.validate(lambda name: StringValidator(name).not_empty())
# We use an integer validator to check the age is positive
self.age.validate(lambda age: IntValidator(age).greater_than(0))
# This ``commit`` method will write back the values of the ``name`` and
# ``age`` attributes to the target object is they are valid
def commit(self):
super(PersonEditor, self).commit(('name', 'age'))
Then we can create a view on the PersonEditor
objects to render the web
form:
@presentation.render_for(PersonEditor)
def render(self, h, *args):
with h.form:
# The ``action()`` callback calls the ``name`` property of the editor
# with the value from the form. The validating function of the
# property will check the value is valid and, if not, will set
# the ``self.name.error`` value
#
# The ``error()`` method will display the error message if it's
# not ``None``
h << 'Name: ' << h.input(value=self.name).action(self.name).error(self.name.error) << h.br
h << 'Age: ' << h.input(value=self.age).action(self.age).error(self.age.error) << h.br
h << h.input(type='submit', value='Send').action(self.commit)
return h.root
Finally, an application creates a Person
object and wraps it into a
PersonEditor
component to edit these properties:
class App(object):
def __init__(self):
self.person = Person()
# Create a ``PersonEditor`` object that wrap the ``Person`` object to edit
editor = PersonEditor(self.person)
# Transform the editor object into a component
self.editor_component = component.Component(editor)
@presentation.render_for(App)
def render(self, h, *args):
# Display the current values
h << h.p('Name: ', self.person.name)
h << h.p('Age: ', self.person.age)
h << h.hr
# Render the editor
h << self.editor_component
return h.root
Now launch this example with:
<NAGARE_HOME>/bin/nagare-admin serve-module <path/to/the/example/file.py>:App editor
and check that the validation error messages are displayed and that the
Person
target object is only modified is all the values are valid.