Contains the information necessary to render (and modify the rendering of) a form field
Methods taking an options parameter will accept several ways of specifying those options:
Options can be “chained” indefinitely because each modification returns a new Field instance, so you can write:
>>> from formalchemy.tests import FieldSet, User
>>> fs = FieldSet(User)
>>> fs.append(Field('foo').dropdown(options=[('one', 1), ('two', 2)]).radio())
or:
>>> fs.configure(options=[fs.name.label('Username').readonly()])
Update field attributes in place. Allowed attributes are: validate, renderer, required, readonly, nul_as, label, multiple, options, size, instructions, metadata:
>>> field = Field('myfield')
>>> field.set(label='My field', renderer=SelectFieldRenderer,
... options=[('Value', 1)])
AttributeField(myfield)
>>> field.label_text
'My field'
>>> field.renderer
<SelectFieldRenderer for AttributeField(myfield)>
The value of this Field: use the corresponding value in the bound data, if any; otherwise, use the value in the bound model. For SQLAlchemy models, if there is still no value, use the default defined on the corresponding Column.
For SQLAlchemy collections, a list of the primary key values of the items in the collection is returned.
Invalid form data will cause an error to be raised. Controllers should thus validate first. Renderers should thus never access .value; use .model_value instead.
Give some HTML options to renderer.
Trailing underscore (_) characters will be stripped. For example, you might want to add a class attribute to your checkbox. You would need to specify .options(class_=’someclass’).
For WebHelpers-aware people: those parameters will be passed to the text_area(), password(), text(), etc.. webhelpers.
Attach some metadata attributes to the Field, to be used by conditions in templates.
Example usage:
>>> test = Field('test')
>>> field = test.with_metadata(instructions='use this widget this way')
...
And further in your templates you can verify:
>>> 'instructions' in field.metadata
True
and display the content in a <span> or something.
A manually-added form field
Create a new Field object.
field name
data type, from formalchemy.types (Integer, Float, String, Binary, Boolean, Date, DateTime, Time) or a custom type
default value. If value is a callable, it will be passed the current bound model instance when the value is read. This allows creating a Field whose value depends on the model once, then binding different instances to it later.
Field corresponding to an SQLAlchemy attribute.
>>> from formalchemy.tests import FieldSet, Order
>>> fs = FieldSet(Order)
>>> print fs.user.key
user
>>> print fs.user.name
user_id
It is important to note that althought these objects are called renderers, they are also responsible for deserialization of data received from the web and insertion of those (possibly mangled) values back to the SQLALchemy object, if any.
They also have to take into consideration that the data used when displaying can come either from the self.params (the dict-like object received from the web) or from the model. The latter case happens when first displaying a form, and the former when validation triggered an error, and the form is to be re-displayed (and still contain the values you entered).
This should be the super class of all Renderer classes.
Renderers generate the html corresponding to a single Field, and are also responsible for deserializing form data into Python objects.
Subclasses should override render and deserialize. See their docstrings for details.
Turns the user-submitted data into a Python value.
The raw data received from the web can be accessed via self.params. This dict-like object usually accepts the getone() and getall() method calls.
For SQLAlchemy collections, return a list of primary keys, and !FormAlchemy will take care of turning that into a list of objects. For manually added collections, return a list of values.
You will need to override this in a child Renderer object if you want to mangle the data from your web form, before it reaches your database model. For example, if your render() method displays a select box filled with items you got from a CSV file or another source, you will need to decide what to do with those values when it’s time to save them to the database – or is this field going to determine the hashing algorithm for your password ?.
This function should return the value that is going to be assigned to the model and used in the place of the model value if there was an error with the form.
Note
Note that this function will be called twice, once when the fieldset is .validate()‘d – with it’s value only tested, and a second time when the fieldset is .sync()‘d – and it’s value assigned to the model. Also note that deserialize() can also raise a ValidationError() exception if it finds some errors converting it’s values.
If calling this function twice poses a problem to your logic, for example, if you have heavy database queries, or temporary objects created in this function, consider using the deserialize_once decorator, provided using:
from formalchemy.fields import deserialize_once
@deserialize_once
def deserialize(self):
... my stuff ...
return calculated_only_once
Finally, you should only have to override this if you are using custom (e.g., Composite) types.
Name of rendered input element.
The fieldset_prefix is defined when instantiating the FieldSet object, by passing the prefix= keyword argument.
The ModelName is taken by introspection from the model passed in at that same moment.
The pk is the primary key of the object being edited. If you are creating a new object, then the pk is an empty string.
The fieldname is, well, the field name.
Note
This method as the direct consequence that you can not create two objects of the same class, using the same FieldSet, on the same page. You can however, create more than one object of a certain class, provided that you create multiple FieldSet instances and pass the prefix= keyword argument.
Otherwise, FormAlchemy deals very well with editing multiple existing objects of same/different types on the same page, without any name clash. Just be careful with multiple object creation.
When creating your own Renderer objects, use self.name to get the field’s name HTML attribute, both when rendering and deserializing.
This gives access to the POSTed data, as received from the web user. You should call .getone, or .getall to retrieve a single value or multiple values for a given key.
For example, when coding a renderer, you’d use:
vals = self.params.getall(self.name)
to catch all the values for the renderer’s form entry.
Render the field. Use self.name to get a unique name for the input element and id. self.value may also be useful if you are not rendering multiple input elements.
When rendering, you can verify self.errors to know if you are rendering a new form, or re-displaying a form with errors. Knowing that, you could select the data either from the model, or the web form submission.
Render a string field:
>>> fs = FieldSet(One)
>>> fs.append(Field(name='text', type=types.String, value='a value'))
Edit mode:
>>> print fs.text.render()
<input id="One--text" name="One--text" type="text" value="a value" />
Read only mode:
>>> print fs.text.render_readonly()
a value
Render a string field:
>>> fs = FieldSet(One)
>>> fs.append(Field(name='passwd').with_renderer(PasswordFieldRenderer))
Edit mode:
>>> print fs.passwd.render()
<input id="One--passwd" name="One--passwd" type="password" />
Read only mode:
>>> print fs.passwd.render_readonly()
******
Render a string field:
>>> fs = FieldSet(One)
>>> fs.append(Field(name='text',value='a value').with_renderer(TextAreaFieldRenderer))
Edit mode:
>>> print fs.text.render()
<textarea id="One--text" name="One--text">a value</textarea>
Read only mode:
>>> print fs.text.render_readonly()
a value
Render a date field:
>>> date = datetime(2000, 12, 31, 9, 00)
>>> fs = FieldSet(One)
>>> fs.append(Field(name='date', type=types.Date, value=date))
Edit mode:
>>> print pretty_html(fs.date.render()) #doctest: +ELLIPSIS
<span id="One--date">
<select id="One--date__month" name="One--date__month">
<option value="MM">
Month
</option>
<option value="1">
January
</option>
...
<option selected="selected" value="12">
December
</option>
</select>
<select id="One--date__day" name="One--date__day">
<option value="DD">
Day
</option>
<option value="1">
1
</option>
...
<option selected="selected" value="31">
31
</option>
</select>
<input id="One--date__year" maxlength="4" name="One--date__year" size="4" type="text" value="2000" />
</span>
Read only mode:
>>> print fs.date.render_readonly()
2000-12-31
Render a time field:
>>> time = datetime(2000, 12, 31, 9, 03, 30).time()
>>> fs = FieldSet(One)
>>> fs.append(Field(name='time', type=types.Time, value=time))
Edit mode:
>>> print pretty_html(fs.time.render()) #doctest: +ELLIPSIS
<span id="One--time">
<select id="One--time__hour" name="One--time__hour">
<option value="HH">
HH
</option>
<option value="0">
0
</option>
...
<option selected="selected" value="9">
9
</option>
...
<option value="23">
23
</option>
</select>
:
<select id="One--time__minute" name="One--time__minute">
<option value="MM">
MM
</option>
<option value="0">
0
</option>
...
<option selected="selected" value="3">
3
</option>
...
<option value="59">
59
</option>
</select>
:
<select id="One--time__second" name="One--time__second">
<option value="SS">
SS
</option>
<option value="0">
0
</option>
...
<option selected="selected" value="30">
30
</option>
...
<option value="59">
59
</option>
</select>
</span>
Read only mode:
>>> print fs.time.render_readonly()
09:03:30
Render a datetime field:
>>> datetime = datetime(2000, 12, 31, 9, 03, 30)
>>> fs = FieldSet(One)
>>> fs.append(Field(name='datetime', type=types.DateTime, value=datetime))
Edit mode:
>>> print pretty_html(fs.datetime.render()) #doctest: +ELLIPSIS
<span id="One--datetime">
<select id="One--datetime__month" name="One--datetime__month">
<option value="MM">
Month
</option>
...
<option selected="selected" value="12">
December
</option>
</select>
<select id="One--datetime__day" name="One--datetime__day">
<option value="DD">
Day
</option>
...
<option selected="selected" value="31">
31
</option>
</select>
<input id="One--datetime__year" maxlength="4" name="One--datetime__year" size="4" type="text" value="2000" />
<select id="One--datetime__hour" name="One--datetime__hour">
<option value="HH">
HH
</option>
...
<option selected="selected" value="9">
9
</option>
...
</select>
:
<select id="One--datetime__minute" name="One--datetime__minute">
<option value="MM">
MM
</option>
...
<option selected="selected" value="3">
3
</option>
...
</select>
:
<select id="One--datetime__second" name="One--datetime__second">
<option value="SS">
SS
</option>
...
<option selected="selected" value="30">
30
</option>
...
</select>
</span>
Read only mode:
>>> print fs.datetime.render_readonly()
2000-12-31 09:03:30
You can write your own FieldRenderer s to customize the widget (input element[s]) used to edit different types of fields...
class EditableRenderer(ModelRenderer):
default_renderers = {
fatypes.String: fields.TextFieldRenderer,
fatypes.Unicode: fields.TextFieldRenderer,
fatypes.Text: fields.TextFieldRenderer,
fatypes.Integer: fields.IntegerFieldRenderer,
fatypes.Float: fields.FloatFieldRenderer,
fatypes.Numeric: fields.FloatFieldRenderer,
fatypes.Interval: fields.IntervalFieldRenderer,
fatypes.Boolean: fields.CheckBoxFieldRenderer,
fatypes.DateTime: fields.DateTimeFieldRenderer,
fatypes.Date: fields.DateFieldRenderer,
fatypes.Time: fields.TimeFieldRenderer,
fatypes.Binary: fields.FileFieldRenderer,
fatypes.List: fields.SelectFieldRenderer,
fatypes.Set: fields.SelectFieldRenderer,
'dropdown': fields.SelectFieldRenderer,
'checkbox': fields.CheckBoxSet,
'radio': fields.RadioSet,
'password': fields.PasswordFieldRenderer,
'textarea': fields.TextAreaFieldRenderer,
}
For instance, to make Boolean s render as select fields with Yes/No options by default, you could write:
>>> from formalchemy.fields import SelectFieldRenderer
>>> class BooleanSelectRenderer(SelectFieldRenderer):
... def render(self, **kwargs):
... kwargs['options'] = [('Yes', True), ('No', False)]
... return SelectFieldRenderer.render(self, **kwargs)
>>> FieldSet.default_renderers[types.Boolean] = BooleanSelectRenderer
Of course, you can subclass FieldSet if you don’t want to change the defaults globally.
One more example, this one to use the JQuery UI DatePicker to render Date objects:
>>> from formalchemy.fields import FieldRenderer
>>> class DatePickerFieldRenderer(FieldRenderer):
... def render(self):
... value= self.value and self.value or ''
... vars = dict(name=self.name, value=value)
... return """
... <input id="%(name)s" name="%(name)s"
... type="text" value="%(value)s">
... <script type="text/javascript">
... $('#%(name)s').datepicker({dateFormat: 'yy-mm-dd'})
... </script>
... """ % vars
(Obviously the page template will need to add references to the jquery library and css.)
Another example to render a link field:
>>> class LinkFieldRenderer(FieldRenderer):
... def render(self, **kwargs):
... """render html for edit mode"""
... from formalchemy import helpers as h
... return h.text_field(self.name, value=self._value, **kwargs)
... def render_readonly(self, **kwargs):
... """render html for read only mode"""
... kwargs = {'value':self.field.raw_value}
... return '<a href="%(value)s">%(value)s</a>' % kwargs
Then bind it to a specific field:
>>> from formalchemy.tests import *
>>> fs = FieldSet(One)
>>> fs.append(Field('link', value='http://www.formalchemy.org'))
>>> fs.configure(include=[fs.link.with_renderer(LinkFieldRenderer)])
Here is the result for edit mode:
>>> print fs.render()
<div>
<label class="field_opt" for="One--link">
Link
</label>
<input id="One--link" name="One--link" type="text" value="http://www.formalchemy.org" />
</div>
<script type="text/javascript">
//<![CDATA[
document.getElementById("One--link").focus();
//]]>
</script>
And for read only mode:
>>> fs.readonly = True
>>> print fs.render()
<tbody>
<tr>
<td class="field_readonly">
Link:
</td>
<td>
<a href="http://www.formalchemy.org">
http://www.formalchemy.org
</a>
</td>
</tr>
</tbody>