Commits

Marat Khabibullin committed bcaeb41 Merge

merge

Comments (0)

Files changed (39)

 syntax: regexp
 \.py[co]$
+\.py,cover$
 \..*\.swp$
 \.eprj$
+\.coverage$
+\.DS_Store$
 ^build/
 ^docs/_build
 ^docs/html
 e8936b958da03582dce83ad5574512909ee2446e 0.4
 97d88af6bca0409b9cfde5b206f22209aaf9ec8c 0.5
 9c6469d4b06bf2fac94ca687523fae389378a446 0.6
+eab645ef8ca1dee6fd39ea28f93a1a37a4cb2347 0.6.1
+8e5a93665d108cc8977936e6ab54706ebc05587a 0.6.2
+6a6954927a13911fae6941fd0aac13b8d6372a42 0.6.3
 
 - Ali Aafshar
 - Adam Lowry
+- Christopher Grebs
+- Eduardo Schettino
 - Emil Vladev
 - Rodrigo Moraes
 - Sebastian Wiesner
 WTForms Changelog
 =================
 
+Version 0.6.3
+-------------
+Released April 24, 2011
+
+- Documentation: Substantial documentation improvements, including adding
+  Crash Course as a sphinx document.
+
+- ext.django: QuerySetSelectField (and ModelSelectField) now accept get_label
+  similar to sqlalchemy equivalents.
+
+- ext.appengine:
+ * model_form fixes: FloatField(#50), TimeField, DateTimeField(#55)
+ * ReferencePropertyField: now properly stores model object, not key. (#48)
+
+
+Version 0.6.2
+-------------
+Released January 22, 2011
+
+- Bug Fixes:
+ * ext.appengine: various field fixes (#34, #48), model_form changes (#41)
+ * Fix issue in Optional with non-string input.
+ * Make numeric fields more consistent.
+
+- Tests: Improve test coverage substantially.
+
+Version 0.6.1
+-------------
+Released September 17th, 2010
+
+- Bug Fixes:
+  * ext.appengine ReferencePropertyField (#36, #37)
+  * dateutil fields: render issue (r419), and consistency issue (#35)
+  * Optional validator failed when raw_data was absent (r418)
+
+- Documentation: docs now mention HTML escaping functionality (#38)
+
+- Add preliminary support for providing a translations object that can
+  translate built-in validation and coercion errors (#32)
+
+
 Version 0.6
 -----------
 Released April 25th, 2010.
 # other places throughout the built documents.
 #
 # The short X.Y version.
-version = '0.6.1'
+version = '0.6.4'
 # The full version, including alpha/beta/rc tags.
-release = '0.6.1dev'
+release = '0.6.4dev'
 
 
 # There are two options for replacing |today|: either, you set today to some

docs/crash_course.rst

+Crash Course
+============
+
+So you’ve cracked your knuckles and started working on that awesome python
+webapp you want to write. You get through writing a few pages and finally you
+need to tackle that loathsome task: form input handling and validation. Enter
+WTForms.
+
+But why do I need *yet another* framework? Well, some webapp frameworks take
+the approach of associating database models with form handling. While this can
+be handy for very basic create/update views, chances are not every form you
+need can map directly to a database model. Or maybe you already use a generic
+form handling framework but you want to customize the HTML generation of those
+form fields, and define your own validation.
+
+With WTForms, your form field HTML can be generated for you, but we let you
+customize it in your templates. This allows you to maintain separation of code
+and presentation, and keep those messy parameters out of your python code.
+Because we strive for loose coupling, you should be able to do that in any
+templating engine you like, as well.
+
+
+.. _download-installation:
+
+Download / Installation
+-----------------------
+
+The easiest way to install WTForms is by using easy_install or pip:
+
+.. code-block:: ruby
+
+    easy_install WTForms
+    pip install WTForms
+
+You can also `download`_ WTForms manually
+from PyPI and then run ``python setup.py install``.
+
+If you're the sort that likes to risk it all and run the latest version from
+Mercurial, you can grab a `packaged up version`_
+of the tip, or head over to `Bitbucket`_ 
+and clone the repository.
+
+.. _download: http://pypi.python.org/pypi/WTForms 
+.. _packaged up version: http://bitbucket.org/simplecodes/wtforms/get/tip.zip
+.. _Bitbucket: http://bitbucket.org/simplecodes/wtforms
+
+
+Key Concepts
+------------
+
+ - **Forms** are the core container of WTForms. Forms represent a collection of
+   fields, which can be accessed on the form dictionary-style or atrribute
+   style.
+ - **Fields** do most of the heavy lifting. Each field represents a *data type*
+   and the field handles coercing form input to that datatype. For example,
+   `IntegerField` and `TextField` represent two different data types. Fields
+   contain a number of useful properties, such as a label, description, and a
+   list of validation errors, in addition to the data the field contains.
+ - Every field has a **Widget** instance. The widget's job is rendering an HTML
+   representation of that field. Widget instances can be specified for each
+   field but every field has one by default which makes sense. Some fields are
+   simply conveniences, for example `TextAreaField` is simply a `TextField`
+   with the default widget being a `TextArea`.
+ - In order to specify validation rules, fields contain a list of **Validators**.
+
+Getting Started
+---------------
+
+Let's get right down to business and define our first form::
+
+    from wtforms import Form, BooleanField, TextField, validators
+
+    class RegistrationForm(Form):
+        username     = TextField('Username', [validators.Length(min=4, max=25)])
+        email        = TextField('Email Address', [validators.Length(min=6, max=35)])
+        accept_rules = BooleanField('I accept the site rules', [validators.Required()])
+
+When you create a form, you define the fields in a way that is similar to the
+way many ORM’s have you define their columns; By defining class variables which
+are instantiations of the fields.
+
+Because forms are regular Python classes, you can easily extend them as you
+would expect::
+
+    class ProfileForm(Form):
+        birthday  = DateTimeField('Your Birthday', format='%m/%d/%y')
+        signature = TextAreaField('Forum Signature')
+
+    class AdminProfileForm(ProfileForm):
+        username = TextField('Username', [validators.Length(max=40)])
+        level    = IntegerField('User Level', [validators.NumberRange(min=0, max=10)])
+
+Via subclassing, `AdminProfileForm`, gains all the fields already defined in
+`ProfileForm`. This allows you to easily share common subsets of fields between
+forms, such as the example above, where we are adding admin-only fields to
+`ProfileForm`.
+
+
+Using Forms
+~~~~~~~~~~~
+
+Using a form is as simple as instantiating it. Consider the following
+django-like view, using the `RegistrationForm` we defined earlier::
+
+    def register(request):
+        form = RegistrationForm(request.POST)
+        if request.method == 'POST' and form.validate():
+            user = User()
+            user.username = form.username.data
+            user.email = form.email.data
+            user.save()
+            redirect('register')
+        return render_response('register.html', form=form)
+
+First, we instantiate the form, providing it with any data available in
+``request.POST``. We then check if the request is made using POST, and if it is, 
+we validate the form, and check that the user accepted the rules. If successful, 
+we create a new User and assign the data from the validated form to it, and save
+it.
+
+
+Editing existing objects
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Our earlier registration example showed how to accept input and validate it for
+new entries, but what if we want to edit an existing object? Easy::
+
+    def edit_profile(request):
+        user = request.current_user
+        form = ProfileForm(request.POST, user)
+        if request.method == 'POST' and form.validate():
+            form.populate_obj(user)
+            user.save()
+            redirect('edit_profile')
+        return render_response('edit_profile.html', form=form)
+
+Here, we instantiate the form by providing both request.POST and the user object
+to the form. By doing this, the form will get any data that isn't present in the 
+post data from the `user` object.
+
+We're also using the form's `populate_obj` method to re-populate the user
+object with the contents of the validated form. This method is provided for
+convenience, for use when the field names match the names on the object you're
+providing with data. Typically, you will want to assign the values manually, but
+for this simple case it's perfect. It can also be useful for CRUD and admin
+forms.
+
+
+Exploring in the console
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+WTForms forms are very simple container objects, and perhaps the easiest way to
+find out what's available to you in a form is to play around with a form in the
+console::
+
+    >>> from wtforms import Form, TextField, validators
+    >>> class UsernameForm(Form):
+    ...     username = TextField('Username', [validators.Length(min=5)], default=u'test')
+    ...
+    >>> form = UsernameForm()
+    >>> form['username']
+    <wtforms.fields.TextField object at 0x827eccc>
+    >>> form.username.data
+    u'test'
+    >>> form.validate()
+    False
+    >>> form.errors
+    {'username': [u'Field must be at least 5 characters long.']}
+
+What we've found here is that when you instantiate a form, it contains
+instances of all the fields, which can be accessed via either dictionary-style
+or attribute-style. These fields have their own properties, as does the enclosing form.
+
+When we validate the form, it returns False, meaning at least one validator was
+not satisfied. form.errors will give you a summary of all the errors.
+
+.. code-block:: python
+
+    >>> form2 = UsernameForm(username=u'Robert')
+    >>> form2.data
+    {'username': u'Robert'}
+    >>> form2.validate()
+    True
+
+This time, we passed a new value for username when instantiating UserForm, and
+it was sufficient to validate the form.
+
+
+How Forms get data
+~~~~~~~~~~~~~~~~~~
+
+In addition to providing data using the first two arguments (`formdata` and
+`obj`), you can pass keyword arguments to populate the form. Note though that a
+few names are reserved: `formdata`, `obj`, and `prefix`.
+
+`formdata` takes precendence over `obj`, which itself takes precedence over
+keyword arguments. For example::
+
+    def change_username(request):
+        user = request.current_user
+        form = ChangeUsernameForm(request.POST, user, username='silly')
+        if request.method == 'POST' and form.validate():
+            user.username = form.username.data
+            user.save()
+            return redirect('change_username')
+        return render_response('change_username.html', form=form)
+
+While you almost never use all three methods together in practice, it
+illustrates how WTForms looks up the `username` field:
+
+1. Check if `request.POST` has a `username` key.
+2. Check if `user` has an attribute named `username`.
+3. Check if a keyword argument named `username` was provided.
+4. Finally, if everything else fails, use the default value provided by the
+   field, if any.
+
+
+Validators
+~~~~~~~~~~
+
+Validation in WTForms is done by providing a field with a set of validators to
+run when the containing form is validated. You provide these via the field
+constructor's second argument, `validators`::
+
+    class ChangeEmailForm(Form):
+        email = TextField('Email', [validators.Length(min=6, max=120), validators.Email()])
+
+You can provide any number of validators to a field. Typically, you will want to
+provide a custom error message::
+
+    class ChangeEmailForm(Form):
+        email = TextField('Email', [
+            validators.Length(min=6, message=_(u'Little short for an email address?')),
+            validators.Email(message=_(u'That\'s not a valid email address.'))
+        ])
+
+It is generally preferable to provide your own messages, as the default messages
+by necessity are generic. This is also the way to provide localised error
+messages.
+
+For a list of all the built-in validators, check the :mod:`Validators Reference <wtforms.validators>`
+
+
+Rendering Fields
+~~~~~~~~~~~~~~~~
+
+Rendering a field is as simple as coercing it to a string::
+
+    >>> from wtforms import Form, TextField
+    >>> class SimpleForm(Form):
+    ...   content = TextField('content')
+    ...
+    >>> form = SimpleForm(content='foobar')
+    >>> str(form.content)
+    '<input id="content" name="content" type="text" value="foobar" />'
+    >>> unicode(form.content)
+    u'<input id="content" name="content" type="text" value="foobar" />'
+
+However, the real power comes from rendering the field with its :meth:`~wtforms.fields.Field.__call__`
+method. By calling the field, you can provide keyword arguments, which will be
+injected as html parameters in the output::
+
+    >>> form.content(style="width: 200px;", class_="bar")
+    u'<input class="bar" id="content" name="content" style="width: 200px;" type="text" value="foobar" />'
+
+Now let's apply this power to rendering a form in a `Jinja <http://jinja.pocoo.org/>`_
+template. First, our form::
+
+    class LoginForm(Form):
+        username = TextField('Username')
+        password = PasswordField('Password')
+
+    form = LoginForm()
+
+And the template:
+
+.. code-block:: html+jinja
+
+    <form method="POST" action="/login">
+        <div>{{ form.username.label }}: {{ form.username(class="css_class") }}</div>
+        <div>{{ form.password.label }}: {{ form.password() }}</div>
+    </form>
+
+Alternately, if you're using Django templates, you can use the `form_field`
+templatetag we provide in our Django extension, when you want to pass keyword
+arguments:
+
+.. code-block:: html+django
+
+    {% load wtforms %}
+    <form method="POST" action="/login">
+        <div>
+            {{ form.username.label }}:
+            {% form_field form.username class="css_class" %}
+        </div>
+        <div>
+            {{ form.password.label }}:
+            {{ form.password }}
+        </div>
+    </form>
+
+Both of these will output:
+
+.. code-block:: html
+
+    <form method="POST" action="/login">
+        <div>
+            <label for="username">Username</label>:
+            <input class="css_class" id="username" name="username" type="text" value="" />
+        </div>
+        <div>
+            <label for="password">Password</label>:
+            <input id="password" name="password" type="password" value="" />
+        </div>
+    </form>
+
+WTForms is template engine agnostic, and will work with anything that allows
+attribute access, string coercion, and/or function calls. The `form_field`
+templatetag is provided as a convenience as you can't pass arguments in Django
+templates.
+
+
+Displaying Errors
+~~~~~~~~~~~~~~~~~
+
+Now that we have a template for our form, let's add error messages:
+
+.. code-block:: html+jinja
+
+    <form method="POST" action="/login">
+        <div>{{ form.username.label }}: {{ form.username(class="css_class") }}</div>
+        {% if form.username.errors %}
+            <ul class="errors">{% for error in form.username.errors %}<li>{{ error }}</li>{% endfor %}</ul>
+        {% endif %}
+
+        <div>{{ form.password.label }}: {{ form.password() }}</div>
+        {% if form.password.errors %}
+            <ul class="errors">{% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}</ul>
+        {% endif %}
+    </form>
+
+If you prefer one big list of errors at the top, this is also easy:
+
+.. code-block:: html+jinja
+
+    {% if form.errors %}
+        <ul class="errors">
+            {% for field_name, field_errors in form.errors if field_errors %}
+                {% for error in field_errors %}
+                    <li>{{ form[field_name].label }}: {{ error }}</li>
+                {% endfor %}
+            {% endfor %}
+        </ul>
+    {% endif %}
+
+As error handling can become a rather verbose affair, it is preferable to use
+Jinja macros (or equivalent) to reduce boilerplate in your templates.
+(:ref:`example <jinja-macros-example>`)
+
+Custom Validators
+~~~~~~~~~~~~~~~~~
+
+There are two ways to provide custom validators. By defining a custom validator
+and using it on a field::
+
+    from wtforms.validators import ValidationError
+
+    def is_42(form, field):
+        if field.data != 42:
+            raise ValidationError('Must be 42')
+
+    class FourtyTwoForm(Form):
+        num = IntegerField('Number', [is_42])
+
+Or by providing an in-form field-specific validator::
+
+    class FourtyTwoForm(Form):
+        num = IntegerField('Number')
+
+        def validate_num(form, field):
+            if field.data != 42:
+                raise ValidationError(u'Must be 42')
+
+For more complex validators that take parameters, check the :ref:`custom-validators` section. 
+
 
 
 While linking data to most fields is fairly easy, making drop-down select lists
-using django ORM data can be quite repetetive. To this end, we have added some
-helpful tools to use the django ORM along with wtforms
+using django ORM data can be quite repetitive. To this end, we have added some
+helpful tools to use the django ORM along with wtforms.
 
 
-.. autoclass:: QuerySetSelectField(default field args, queryset=None, label_attr='', allow_blank=False, blank_text=u'')
+.. autoclass:: QuerySetSelectField(default field args, queryset=None, get_label=None, allow_blank=False, blank_text=u'')
 
     .. code-block:: python
 
         class ArticleEdit(Form):
             title    = TextField()
-            column   = QuerySetSelectField(label_attr='title', allow_blank=True)
+            column   = QuerySetSelectField(get_label='title', allow_blank=True)
             category = QuerySetSelectField(queryset=Category.objects.all())
 
         def edit_article(request, id):
     view if needed instead of at form construction time, allowing the select
     field to consist of choices only relevant to the user.
 
-.. autoclass:: ModelSelectField(default field args, model=None, label_attr='', allow_blank=False, blank_text=u'')
+.. autoclass:: ModelSelectField(default field args, model=None, get_label='', allow_blank=False, blank_text=u'')
 
 
 SQLAlchemy
 .. autoclass:: QuerySelectField(default field args, query_factory=None, get_pk=None, get_label=None, allow_blank=False, blank_text=u'')
 
 .. autoclass:: QuerySelectMultipleField(default field args, query_factory=None, get_pk=None, get_label=None, allow_blank=False, blank_text=u'')
-
-.. autoclass:: ModelSelectField(default field args, model=None, get_pk=None, get_label=None, allow_blank=False, blank_text=u'')
 
   * Django
   * Webob (Includes Pylons, Google App Engine, Turbogears)
-  * Werkzeug
+  * Werkzeug (Includes Flask, Tipfy)
   * any other cgi.FieldStorage-type multidict
 
 * **Templating Engines**
         Note: Simply coercing the field to a string or unicode will render it as
         if it was called with no arguments.
 
+    .. automethod:: __html__
+
+        Many template engines use the __html__ method when it exists on a
+        printed object to get an 'html-safe' string that will not be
+        auto-escaped. To allow for printing a bare field without calling it,
+        all WTForms fields implement this method as well.
+
     **Properties**
 
     .. attribute:: name
 formdata wrapper, because the behavior of this is highly variant on the
 wrapper: some return the first item, others return the last, and some may
 return a list.
+
+
+Additional Helper Classes
+-------------------------
+
+.. autoclass:: Flags
+
+    Usage::
+
+        >>> flags = Flags()
+        >>> flags.required = True
+        >>> 'required' in flags
+        True
+        >>> 'nonexistent' in flags
+        False
+        >>> flags.fake
+        False
+
+
+.. class:: Label
+
+    On all fields, the `label` property is an instance of this class.
+    Labels can be printed to yield a
+    ``<label for="field_id">Label Text</label>``
+    HTML tag enclosure. Similar to fields, you can also call the label with
+    additional html params.
+
+    .. attribute:: field_id
+
+        The ID of the field which this label will reference.
+
+    .. attribute:: text
+
+        The original label text passed to the field's constructor.

docs/home_page.rst

+
+WTForms is a forms validation and rendering library for python development. It is available under the BSD license.
+
+The latest version is |version|, released April 24, 2011 (:ref:`download <download-installation>`)
+
+.. include:: crash_course.rst
+
+Next Steps
+----------
+
+The crash course has just skimmed the surface on how you can begin using
+WTForms to handle form input and validation in your application. For more
+information, you'll want to check the following:
+
+ - The :ref:`WTForms documentation <doc-index>` has API documentation for the entire library.
+ - :ref:`specific_problems` can help you tackle specific
+   integration issues with WTForms and other frameworks.
+ - The `mailing list`_ is where you can get help, discuss bugs in WTForms, and
+   propose new features.
+
+.. _mailing list: http://groups.google.com/group/wtforms/
+.. _doc-index:
+
 WTForms Documentation
 =====================
 
 
    faq
    specific_problems
+   crash_course
 
 **Indices and tables:**
 

docs/specific_problems.rst

 .. _this mailing list post: http://groups.google.com/group/wtforms/browse_thread/thread/7099776aacd989e0/772807dfb4b9635b?#772807dfb4b9635b
 
 
+.. _jinja-macros-example:
+
 Rendering Errors
 ----------------
 

docs/validators.rst

 
 .. autoclass:: wtforms.validators.NoneOf
 
+
+.. _custom-validators:
+
 Custom validators
 -----------------
 
 that widgets can easily be created to customize the rendering of existing
 fields.
 
+**Note** All built-in widgets will return upon rendering a "HTML-safe" unicode
+string subclass that many templating frameworks (Jinja2, Mako, Genshi) will
+recognize as not needing to be auto-escaped.
+
 Built-in widgets
 ----------------
 
             options = dict(kwargs, name=field.name, value=value, id=choice_id)
             if checked:
                 options['checked'] = 'checked'
-            html.append(u'<li><input %s /> ' % html_options(**options))
-            html.append(u'<label %s>%s</label></li>')
+            html.append(u'<li><input %s /> ' % html_params(**options))
+            html.append(u'<label for="%s">%s</label></li>' % (field_id, label))
         html.append(u'</ul>')
         return u''.join(html)
-
     ],
     packages=[
         'wtforms',
+        'wtforms.fields',
+        'wtforms.widgets',
         'wtforms.ext',
         'wtforms.ext.appengine',
         'wtforms.ext.dateutil',

tests/ext_appengine/tests.py

 To run the tests, use NoseGAE:
 
 easy_install nose
-easy_install nose-gae
+easy_install nosegae
 
 nosetests --with-gae --without-sandbox
 """
 
 from wtforms import Form, fields as f, validators
 from wtforms.ext.appengine.db import model_form
+from wtforms.ext.appengine.fields import GeoPtPropertyField
+
+
+class DummyPostData(dict):
+    def getlist(self, key):
+        v = self[key]
+        if not isinstance(v, (list, tuple)):
+            v = [v]
+        return v
 
 
 class Author(db.Model):
         i = 0
         for key, name, value in form.author.iter_choices():
             self.assertEqual(key, keys[i])
-            i += 1
+            i += 1
+
+
+class TestFields(TestCase):
+    class GeoTestForm(Form):
+        geo = GeoPtPropertyField()
+
+    def test_geopt_property(self):
+        form = self.GeoTestForm(DummyPostData(geo='5.0, -7.0'))
+        self.assert_(form.validate())
+        self.assertEquals(form.geo.data, u'5.0,-7.0')
+        form = self.GeoTestForm(DummyPostData(geo='5.0,-f'))
+        self.assert_(not form.validate())

tests/ext_dateutil.py

         f = self.F(DummyPostData(a='Grok Grarg Rawr'))
         self.assert_(not f.validate())
 
+    def test_blank_input(self):
+        f = self.F(DummyPostData(a='', b=''))
+        self.assertEqual(f.a.data, None)
+        self.assertEqual(f.b.data, None)
+        self.assert_(not f.validate())
+
     def test_defaults_display(self):
         f = self.F(a=datetime(2001, 11, 15))
         self.assertEqual(f.a.data, datetime(2001, 11, 15))
         self.assertEqual(f.c.data, None)
         self.assert_(f.validate())
 
+    def test_render(self):
+        f = self.F()
+        self.assertEqual(f.b(), ur'<input id="b" name="b" type="text" value="2004-09-12">')
+
 
 if __name__ == '__main__':
     from unittest import main

tests/ext_django/tests.py

         return t.render(Context({'form': self.F(), 'a': self.F().a,  'someclass': "CLASSVAL>!"}))
 
     def test_simple_print(self):
-        self.assertEqual(self._render(u'{% autoescape off %}{{ form.a }}{% endautoescape %}'), u'<input id="a" name="a" type="text" value="" />')
-        self.assertEqual(self._render(u'{% autoescape off %}{{ form.a.label }}{% endautoescape %}'), u'<label for="a">I r label</label>')
+        self.assertEqual(self._render(u'{% autoescape off %}{{ form.a }}{% endautoescape %}'), u'<input id="a" name="a" type="text" value="">')
+        #self.assertEqual(self._render(u'{% autoescape off %}{{ form.a.label }}{% endautoescape %}'), u'<label for="a">I r label</label>')
 
     def test_form_field(self):
-        self.assertEqual(self._render(u'{% form_field form.a %}'), u'<input id="a" name="a" type="text" value="" />')
+        self.assertEqual(self._render(u'{% form_field form.a %}'), u'<input id="a" name="a" type="text" value="">')
         self.assertEqual(self._render(u'{% form_field a class=someclass onclick="alert()" %}'), 
-                         u'<input class="CLASSVAL&gt;!" id="a" name="a" onclick="alert()" type="text" value="" />')
+                         u'<input class="CLASSVAL&gt;!" id="a" name="a" onclick="alert()" type="text" value="">')
 
 class ModelFormTest(TestCase):
     F = model_form(test_models.User, exclude=['id'], field_args = {
         from django.core.management import call_command
         self.queryset = test_models.Group.objects.all()
         class F(Form):
-            a = QuerySetSelectField(allow_blank=True, label_attr='name', widget=lazy_select)
+            a = QuerySetSelectField(allow_blank=True, get_label='name', widget=lazy_select)
             b = QuerySetSelectField(queryset=self.queryset, widget=lazy_select)
 
         self.F = F

tests/ext_sqlalchemy.py

 
 from unittest import TestCase
 
-from wtforms.ext.sqlalchemy.fields import ModelSelectField, QuerySelectField, QuerySelectMultipleField
+from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
 from wtforms.form import Form
 
 
         self.assertEqual(form.a(), [(u'1', 'apple', False), (u'2', 'banana', True)])
         self.assert_(form.validate())
 
-class ModelSelectFieldTest(TestBase):
-    def setUp(self):
-        from sqlalchemy.orm import mapper as sqla_mapper
-        engine = create_engine('sqlite:///:memory:', echo=False)
-        self.Session = session = scoped_session(sessionmaker(autoflush=False, autocommit=False, bind=engine))
-
-        def mapper(cls, *arg, **kw):
-            cls.query = session.query_property()
-            return sqla_mapper(cls, *arg, **kw)
-        self._do_tables(mapper, engine)
-
-    def test(self):
-        sess = self.Session
-        self._fill(sess)
-        class F(Form):
-            a = ModelSelectField(get_label='name', model=self.Test, widget=LazySelect())
-
-        form = F()
-        self.assertEqual(form.a(), [(u'1', 'apple', False), (u'2', 'banana', False)])
-
 
 if __name__ == '__main__':
     from unittest import main
 
 from wtforms import validators, widgets
 from wtforms.fields import *
-from wtforms.fields import Label
+from wtforms.fields import Label, Field
 from wtforms.form import Form
 
 
         self.assertEqual(label().__html__(), expected)
         self.assertEqual(label('hello'), u"""<label for="test">hello</label>""")
         self.assertEqual(TextField(u'hi').bind(Form(), 'a').label.text, u'hi')
+        self.assertEqual(repr(label), "Label('test', u'Caption')") 
+
+    def test_auto_label(self):
+        t1 = TextField().bind(Form(), 'foo_bar')
+        self.assertEqual(t1.label.text, 'Foo Bar')
+
+        t2 = TextField('').bind(Form(), 'foo_bar')
+        self.assertEqual(t2.label.text, '')
 
 
 class FlagsTest(TestCase):
         self.assertEqual(self.flags.required, False)
         self.assert_('required' not in self.flags)
 
+    def test_repr(self):
+        self.assertEqual(repr(self.flags), '<wtforms.fields.Flags: {required}>')
+
 
 class FiltersTest(TestCase):
     class F(Form):
         self.assertEqual(self.F(DummyPostData(a=['  foo bar  '])).a.data, 'foo bar')
 
 
+class FieldTest(TestCase):
+    class F(Form):
+        a = TextField(default='hello')
+
+    def setUp(self):
+        self.field = self.F().a 
+
+    def test_htmlstring(self):
+        self.assert_(isinstance(self.field.__html__(), widgets.HTMLString))
+
+    def test_str_coerce(self):
+        self.assert_(isinstance(str(self.field), str))
+        self.assertEqual(str(self.field), str(self.field()))
+
+    def test_unicode_coerce(self):
+        self.assertEqual(unicode(self.field), self.field()) 
+
+    def test_process_formdata(self):
+        Field.process_formdata(self.field, [42])
+        self.assertEqual(self.field.data, 42)
+
+
+class PrePostTestField(TextField):
+    def pre_validate(self, form):
+        if self.data == "stoponly":
+            raise validators.StopValidation()
+        elif self.data.startswith("stop"):
+            raise validators.StopValidation("stop with message")
+
+    def post_validate(self, form, stopped):
+        if self.data == "p":
+            raise ValueError("Post")
+        elif stopped and self.data == "stop-post":
+            raise ValueError("Post-stopped")
+
+
+class PrePostValidationTest(TestCase):
+    class F(Form):
+        a = PrePostTestField(validators=[validators.Length(max=1, message="too long")])
+
+    def _init_field(self, value):
+        form = self.F(a=value)
+        form.validate()
+        return form.a
+
+    def test_pre_stop(self):
+        a = self._init_field("long")
+        self.assertEqual(a.errors, ["too long"])
+
+        stoponly = self._init_field("stoponly")
+        self.assertEqual(stoponly.errors, [])
+
+        stopmessage = self._init_field("stopmessage")
+        self.assertEqual(stopmessage.errors, ["stop with message"]) 
+
+    def test_post(self):
+        a = self._init_field("p")
+        self.assertEqual(a.errors, ["Post"])
+        stopped = self._init_field("stop-post")
+        self.assertEqual(stopped.errors, ["stop with message", "Post-stopped"])
+
+
 class SelectFieldTest(TestCase):
     class F(Form):
         a = SelectField(choices=[('a', 'hello'), ('btest','bye')], default='a')
-        b = SelectField(choices=[(1, 'Item 1'), (2, 'Item 2')], coerce=int)
+        b = SelectField(choices=[(1, 'Item 1'), (2, 'Item 2')], coerce=int, option_widget=widgets.TextInput())
 
     def test_defaults(self):
         form = self.F()
         self.assertEqual(form.a.data, u'a')
         self.assertEqual(form.b.data, None)
         self.assertEqual(form.validate(), False)
-        self.assertEqual(form.a(), u"""<select id="a" name="a"><option selected="selected" value="a">hello</option><option value="btest">bye</option></select>""")
+        self.assertEqual(form.a(), u"""<select id="a" name="a"><option selected value="a">hello</option><option value="btest">bye</option></select>""")
         self.assertEqual(form.b(), u"""<select id="b" name="b"><option value="1">Item 1</option><option value="2">Item 2</option></select>""")
 
     def test_with_data(self):
         form = self.F(DummyPostData(a=[u'btest']))
         self.assertEqual(form.a.data, u'btest')
-        self.assertEqual(form.a(), u"""<select id="a" name="a"><option value="a">hello</option><option selected="selected" value="btest">bye</option></select>""")
+        self.assertEqual(form.a(), u"""<select id="a" name="a"><option value="a">hello</option><option selected value="btest">bye</option></select>""")
 
     def test_value_coercion(self):
         form = self.F(DummyPostData(b=[u'2']))
     def test_iterable_options(self):
         form = self.F()
         self.assert_(isinstance(list(form.a)[0], form.a._Option))
-        self.assertEqual(list(unicode(x) for x in form.a), [u'<option selected="selected" value="a">hello</option>', '<option value="btest">bye</option>'])
+        self.assertEqual(list(unicode(x) for x in form.a), [u'<option selected value="a">hello</option>', '<option value="btest">bye</option>'])
+        self.assert_(isinstance(list(form.a)[0].widget, widgets.Option))
+        self.assert_(isinstance(list(form.b)[0].widget, widgets.TextInput))
 
 
 class SelectMultipleFieldTest(TestCase):
         self.assertEqual(form.a.data, u'a')
         self.assertEqual(form.b.data, None)
         self.assertEqual(form.validate(), False)
-        self.assertEqual(form.a(), u"""<ul id="a"><li><input checked="checked" id="a-0" name="a" type="radio" value="a" /> <label for="a-0">hello</label></li><li><input id="a-1" name="a" type="radio" value="b" /> <label for="a-1">bye</label></li></ul>""")
-        self.assertEqual(form.b(), u"""<ul id="b"><li><input id="b-0" name="b" type="radio" value="1" /> <label for="b-0">Item 1</label></li><li><input id="b-1" name="b" type="radio" value="2" /> <label for="b-1">Item 2</label></li></ul>""")
-        self.assertEqual([unicode(x) for x in form.a], [u'<input checked="checked" id="a-0" name="a" type="radio" value="a" />', u'<input id="a-1" name="a" type="radio" value="b" />'])
+        self.assertEqual(form.a(), u"""<ul id="a"><li><input checked id="a-0" name="a" type="radio" value="a"> <label for="a-0">hello</label></li><li><input id="a-1" name="a" type="radio" value="b"> <label for="a-1">bye</label></li></ul>""")
+        self.assertEqual(form.b(), u"""<ul id="b"><li><input id="b-0" name="b" type="radio" value="1"> <label for="b-0">Item 1</label></li><li><input id="b-1" name="b" type="radio" value="2"> <label for="b-1">Item 2</label></li></ul>""")
+        self.assertEqual([unicode(x) for x in form.a], [u'<input checked id="a-0" name="a" type="radio" value="a">', u'<input id="a-1" name="a" type="radio" value="b">'])
 
 
 class TextFieldTest(TestCase):
     def test(self):
         form = self.F()
         self.assertEqual(form.a.data, None)
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="">""")
         form = self.F(DummyPostData(a=['hello']))
         self.assertEqual(form.a.data, u'hello')
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="hello" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="hello">""")
         form = self.F(DummyPostData(b=['hello']))
         self.assertEqual(form.a.data, u'')
 
 
     def test(self):
         form = self.F()
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="hidden" value="LE DEFAULT" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="hidden" value="LE DEFAULT">""")
 
 
 class TextAreaFieldTest(TestCase):
 
     def test(self):
         form = self.F()
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="password" value="LE DEFAULT" />""")
-        self.assertEqual(form.b(), u"""<input id="b" name="b" type="password" value="" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="password" value="LE DEFAULT">""")
+        self.assertEqual(form.b(), u"""<input id="b" name="b" type="password" value="">""")
 
 
 class FileFieldTest(TestCase):
 
     def test(self):
         form = self.F()
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="file" value="LE DEFAULT" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="file" value="LE DEFAULT">""")
 
 
 class IntegerFieldTest(TestCase):
         form = self.F(DummyPostData(a=['v'], b=['-15']))
         self.assertEqual(form.a.data, None)
         self.assertEqual(form.a.raw_data, [u'v'])
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="v" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="v">""")
         self.assertEqual(form.b.data, -15)
-        self.assertEqual(form.b(), u"""<input id="b" name="b" type="text" value="-15" />""")
+        self.assertEqual(form.b(), u"""<input id="b" name="b" type="text" value="-15">""")
         self.assert_(not form.a.validate(form))
         self.assert_(form.b.validate(form))
         form = self.F(DummyPostData(a=[], b=['']))
         self.assertEqual(len(form.b.errors), 1)
         form = self.F(b=9)
         self.assertEqual(form.b.data, 9)
+        self.assertEqual(form.a._value(), u'')
+        self.assertEqual(form.b._value(), u'9')
 
 
 class DecimalFieldTest(TestCase):
 
 
     def test_quantize(self):
-        F = make_form(a=DecimalField(places=3, rounding=ROUND_UP))
+        F = make_form(a=DecimalField(places=3, rounding=ROUND_UP), b=DecimalField(places=None))
         form = F(a=Decimal('3.1415926535'))
         self.assertEqual(form.a._value(), u'3.142')
         form.a.rounding = ROUND_DOWN
         self.assertEqual(form.a._value(), u'3.141')
-        form = F(a=3.14159265)
+        self.assertEqual(form.b._value(), u'')
+        form = F(a=3.14159265, b=72)
         self.assertEqual(form.a._value(), u'3.142')
         self.assert_(isinstance(form.a.data, float))
+        self.assertEqual(form.b._value(), u'72')
 
 
 class FloatFieldTest(TestCase):
         form = self.F(DummyPostData(a=['v'], b=['-15.0']))
         self.assertEqual(form.a.data, None)
         self.assertEqual(form.a.raw_data, [u'v'])
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="v" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="v">""")
         self.assertEqual(form.b.data, -15.0)
-        self.assertEqual(form.b(), u"""<input id="b" name="b" type="text" value="-15.0" />""")
+        self.assertEqual(form.b(), u"""<input id="b" name="b" type="text" value="-15.0">""")
         self.assert_(not form.a.validate(form))
         self.assert_(form.b.validate(form))
         form = self.F(DummyPostData(a=[], b=['']))
         self.assertEqual(len(form.b.errors), 1)
         form = self.F(b=9.0)
         self.assertEqual(form.b.data, 9.0)
+        self.assertEqual(form.b._value(), u"9.0")
 
 
 class BooleanFieldTest(TestCase):
         self.assertEqual(form.bool2.data, True)
 
     def test_rendering(self):
-        form = self.BoringForm()
-        self.assertEqual(form.bool1(), u'<input id="bool1" name="bool1" type="checkbox" value="y" />')
-        self.assertEqual(form.bool2(), u'<input checked="checked" id="bool2" name="bool2" type="checkbox" value="y" />')
+        form = self.BoringForm(DummyPostData(bool2=u"x"))
+        self.assertEqual(form.bool1(), u'<input id="bool1" name="bool1" type="checkbox" value="y">')
+        self.assertEqual(form.bool2(), u'<input checked id="bool2" name="bool2" type="checkbox" value="x">')
+        self.assertEqual(form.bool2.raw_data, [u'x'])
 
     def test_with_postdata(self):
         form = self.BoringForm(DummyPostData(bool1=[u'a']))
         d = datetime(2008, 5, 5, 4, 30, 0, 0)
         form = self.F(DummyPostData(a=['2008-05-05', '04:30:00'], b=['2008-05-05 04:30']))
         self.assertEqual(form.a.data, d)
-        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="2008-05-05 04:30:00" />""")
+        self.assertEqual(form.a(), u"""<input id="a" name="a" type="text" value="2008-05-05 04:30:00">""")
         self.assertEqual(form.b.data, d)
-        self.assertEqual(form.b(), u"""<input id="b" name="b" type="text" value="2008-05-05 04:30" />""")
+        self.assertEqual(form.b(), u"""<input id="b" name="b" type="text" value="2008-05-05 04:30">""")
+        self.assert_(form.validate())
+        form = self.F(DummyPostData(a=['2008-05-05']))
+        self.assert_(not form.validate())
+        self.assert_("not match format" in form.a.errors[0])
 
 
 class SubmitFieldTest(TestCase):
         a = SubmitField(u'Label')
 
     def test(self):
-        self.assertEqual(self.F().a(), """<input id="a" name="a" type="submit" value="Label" />""")
+        self.assertEqual(self.F().a(), """<input id="a" name="a" type="submit" value="Label">""")
 
 
 class FormFieldTest(TestCase):
         self.assertEqual(obj_inner.b, None)
 
     def test_widget(self):
-        self.assertEqual(self.F1().a(), u'''<table id="a"><tr><th><label for="a-a">A</label></th><td><input id="a-a" name="a-a" type="text" value="" /></td></tr><tr><th><label for="a-b">B</label></th><td><input id="a-b" name="a-b" type="text" value="" /></td></tr></table>''')
+        self.assertEqual(self.F1().a(), u'''<table id="a"><tr><th><label for="a-a">A</label></th><td><input id="a-a" name="a-a" type="text" value=""></td></tr><tr><th><label for="a-b">B</label></th><td><input id="a-b" name="a-b" type="text" value=""></td></tr></table>''')
 
     def test_separator(self):
         form = self.F2(DummyPostData({'a-a': 'fake', 'a::a': 'real'}))
         self.assertEqual(form['test'].data, u'hello')
         self.assertEqual(self.get_form(prefix='foo[')['test'].name, 'foo[-test')
 
+    def test_formdata_wrapper_error(self):
+        form = self.get_form()
+        self.assertRaises(TypeError, form.process, [])
+
 
 class FormMetaTest(TestCase):
     def test_monkeypatch(self):
         form = self.F()
         self.assertEqual(form.validate(), False)
 
+    def test_field_adding_disabled(self):
+        form = self.F()
+        self.assertRaises(TypeError, form.__setitem__, 'foo', TextField())
+
     def test_field_removal(self):
         form = self.F()
         del form.test
         self.assert_('test' not in form)
         self.assertEqual(form.test, None)
         self.assertEqual(len(list(form)), 0)
+        # Try deleting a nonexistent field
+        self.assertRaises(AttributeError, form.__delattr__, 'fake')
 
     def test_ordered_fields(self):
         class MyForm(Form):

tests/runtests.py

 import sys
 from unittest import defaultTestLoader, TextTestRunner, TestSuite
 
-TESTS = ['form', 'fields', 'validators', 'widgets', 'webob_wrapper']
+TESTS = ['form', 'fields', 'validators', 'widgets', 'webob_wrapper', 'translations']
 TESTS.extend([x for x in sys.argv[1:] if '-' not in x])
 
 sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

tests/translations.py

+from unittest import TestCase
+
+from wtforms import Form, TextField
+from wtforms import validators as v
+
+
+class Lower_Translator(object):
+    """A fake translator that just converts everything to lowercase."""
+
+    def gettext(self, s):
+        return s.lower()
+
+    def ngettext(self, singular, plural, n):
+        if n == 1:
+            return singular.lower()
+        else:
+            return plural.lower()
+
+
+class MyFormBase(Form):
+    def _get_translations(self):
+        return Lower_Translator()
+
+
+class DummyTranslationsTest(TestCase):
+    class F(Form):
+        a = TextField(validators=[v.Length(max=5)])
+
+    def setUp(self):
+        self.a = self.F().a
+
+    def test_gettext(self):
+        x = u"foo"
+        self.assert_(self.a.gettext(x) is x)
+
+    def test_ngettext(self):
+        getit = lambda n: self.a.ngettext(u"antelope", u"antelopes", n)
+        self.assertEqual(getit(0), u"antelopes")
+        self.assertEqual(getit(1), u"antelope")
+        self.assertEqual(getit(2), u"antelopes")
+
+
+
+class TranslationsTest(TestCase):
+    class F(MyFormBase):
+        a = TextField('', [v.Length(max=5)])
+
+    def test_validator_translation(self):
+        form = self.F(a='hellobye')
+        self.assert_(not form.validate())
+        self.assertEquals(form.a.errors[0], u'field cannot be longer than 5 characters.')

tests/validators.py

 from unittest import TestCase
 from wtforms.validators import StopValidation, ValidationError, email, equal_to, ip_address, length, required, optional, regexp, url, NumberRange, AnyOf, NoneOf
 
+class DummyTranslations(object):
+    def gettext(self, string):
+        return string
+
+    def ngettext(self, singular, plural, n):
+        if n == 1:
+            return singular
+
+        return plural
 
 class DummyForm(dict):
     pass
 
 class DummyField(object):
+    _translations = DummyTranslations()
     def __init__(self, data, errors=(), raw_data=None):
         self.data = data
         self.errors = list(errors)
         self.raw_data = raw_data
 
+    def gettext(self, string):
+        return self._translations.gettext(string)
+
+    def ngettext(self, singular, plural, n):
+        return self._translations.ngettext(singular, plural, n)
+
 def grab_error_message(callable, form, field):
     try:
         callable(form, field)
         self.assertRaises(ValidationError, v, self.form, DummyField(0))
         self.assertRaises(ValidationError, v, self.form, DummyField(12))
 
+        onlymin = NumberRange(min=5)
+        self.assertEqual(onlymin(self.form, DummyField(500)), None)
+        self.assertRaises(ValidationError, onlymin, self.form, DummyField(4))
+
+        onlymax = NumberRange(max=50)
+        self.assertEqual(onlymax(self.form, DummyField(30)), None)
+        self.assertRaises(ValidationError, onlymax, self.form, DummyField(75))
+
     def test_lazy_proxy(self):
         """Tests that the validators support lazy translation strings for messages."""
 
         self.assert_(ip_address(message=message))
         self.assert_(url(message=message))
 
-    def test_any_of(self):    
+    def test_any_of(self):
         self.assertEqual(AnyOf(['a', 'b', 'c'])(self.form, DummyField('b')), None)
         self.assertRaises(ValueError, AnyOf(['a', 'b', 'c']), self.form, DummyField(None))
-        
+
     def test_none_of(self):
         self.assertEqual(NoneOf(['a', 'b', 'c'])(self.form, DummyField('d')), None)
         self.assertRaises(ValueError, NoneOf(['a', 'b', 'c']), self.form, DummyField('a'))
 #!/usr/bin/env python
 from unittest import TestCase
-from wtforms.widgets import html_params
+from wtforms.widgets import html_params, Input
 from wtforms.widgets import *
 
 
 
 class TableWidgetTest(TestCase):
     def test(self):
-        field = DummyField([DummyField(x, label='l' + x) for x in ['foo', 'bar']], id='hai')
-        self.assertEqual(TableWidget()(field), u'<table id="hai"><tr><th>lfoo</th><td>foo</td></tr><tr><th>lbar</th><td>bar</td></tr></table>')
+        inner_fields = [
+            DummyField('hidden1', type='HiddenField'),
+            DummyField('foo', label='lfoo'),
+            DummyField('bar', label='lbar'),
+            DummyField('hidden2', type='HiddenField'),
+        ]
+        field = DummyField(inner_fields, id='hai')
+        self.assertEqual(TableWidget()(field), u'<table id="hai"><tr><th>lfoo</th><td>hidden1foo</td></tr><tr><th>lbar</th><td>bar</td></tr></table>hidden2')
 
 
 class BasicWidgetsTest(TestCase):
 
     field = DummyField('foo', name='bar', label='label', id='id') 
 
+    def test_input_type(self):
+        a = Input()
+        self.assertRaises(AttributeError, getattr, a, 'input_type')
+        b = Input(input_type='test')
+        self.assertEqual(b.input_type, 'test')
+
     def test_html_marking(self):
         html = TextInput()(self.field)
         self.assert_(hasattr(html, '__html__'))
         self.assert_(html.__html__() is html)
 
     def test_text_input(self):
-        self.assertEqual(TextInput()(self.field), u'<input id="id" name="bar" type="text" value="foo" />')
+        self.assertEqual(TextInput()(self.field), u'<input id="id" name="bar" type="text" value="foo">')
 
     def test_password_input(self):
         self.assert_(u'type="password"' in PasswordInput()(self.field))
         self.assert_(u'type="hidden"' in HiddenInput()(self.field))
 
     def test_checkbox_input(self):
-        self.assertEqual(CheckboxInput()(self.field, value='v'), '<input checked="checked" id="id" name="bar" type="checkbox" value="v" />')
+        self.assertEqual(CheckboxInput()(self.field, value='v'), '<input checked id="id" name="bar" type="checkbox" value="v">')
         field2 = DummyField(False)
         self.assert_(u'checked' not in CheckboxInput()(field2))
 
 
     def test(self):
         self.assertEqual(Select()(self.field), 
-            u'<select id="" name="f"><option selected="selected" value="foo">lfoo</option><option value="bar">lbar</option></select>')
+            u'<select id="" name="f"><option selected value="foo">lfoo</option><option value="bar">lbar</option></select>')
         self.assertEqual(Select(multiple=True)(self.field), 
-            '<select id="" multiple="multiple" name="f"><option selected="selected" value="foo">lfoo</option><option value="bar">lbar</option></select>')
+            '<select id="" multiple name="f"><option selected value="foo">lfoo</option><option value="bar">lbar</option></select>')
 
 if __name__ == '__main__':
     from unittest import main

wtforms/__init__.py

 from wtforms.form import Form
 from wtforms.validators import ValidationError
 
-__version__ = '0.6.1dev'
+__version__ = '0.6.4dev'

wtforms/ext/appengine/db.py

 
 def convert_FloatProperty(model, prop, kwargs):
     """Returns a form field for a ``db.FloatProperty``."""
-    return f.FloatField(kwargs)
+    return f.FloatField(**kwargs)
 
 
 def convert_DateTimeProperty(model, prop, kwargs):
     if prop.auto_now or prop.auto_now_add:
         return None
 
-    return f.DateTimeField(format='%Y-%m-%d %H-%M-%S', **kwargs)
+    return f.DateTimeField(format='%Y-%m-%d %H:%M:%S', **kwargs)
 
 
 def convert_DateProperty(model, prop, kwargs):
     if prop.auto_now or prop.auto_now_add:
         return None
 
-    return f.DateTimeField(format='%Y-%m-%d', **kwargs)
+    return f.DateField(format='%Y-%m-%d', **kwargs)
 
 
 def convert_TimeProperty(model, prop, kwargs):
     if prop.auto_now or prop.auto_now_add:
         return None
 
-    return f.DateTimeField(format='%H-%M-%S', **kwargs)
+    return f.DateTimeField(format='%H:%M:%S', **kwargs)
 
 
 def convert_ListProperty(model, prop, kwargs):
     | DateTimeProperty   | DateTimeField     | datetime     | skipped if       |
     |                    |                   |              | auto_now[_add]   |
     +--------------------+-------------------+--------------+------------------+
-    | DateProperty       | DateTimeField     | date         | skipped if       |
+    | DateProperty       | DateField         | date         | skipped if       |
     |                    |                   |              | auto_now[_add]   |
     +--------------------+-------------------+--------------+------------------+
     | TimeProperty       | DateTimeField     | time         | skipped if       |

wtforms/ext/appengine/fields.py

+import decimal
+
 from wtforms import fields, widgets
 
 class ReferencePropertyField(fields.SelectFieldBase):
     """
     A field for ``db.ReferenceProperty``. The list items are rendered in a
     select.
+
+    :param reference_class:
+        A db.Model class which will be used to generate the default query
+        to make the list of items. If this is not specified, The `query`
+        property must be overridden before validation.
+    :param label_attr:
+        If specified, use this attribute on the model class as the label
+        associated with each option. Otherwise, the model object's
+        `__str__` or `__unicode__` will be used.
+    :param allow_blank:
+        If set to true, a blank choice will be added to the top of the list
+        to allow `None` to be chosen.
+    :param blank_text:
+        Use this to override the default blank option's label.
     """
     widget = widgets.Select()
 
-    def __init__(self, label=u'', validators=None, reference_class=None,
+    def __init__(self, label=None, validators=None, reference_class=None,
                  label_attr=None, allow_blank=False, blank_text=u'', **kwargs):
         super(ReferencePropertyField, self).__init__(label, validators,
                                                      **kwargs)
         self.allow_blank = allow_blank
         self.blank_text = blank_text
         self._set_data(None)
-        if reference_class is None:
-            raise ValueError('Missing reference_class attribute in '
-                             'ReferencePropertyField')
-
-        self.query = reference_class.all()
+        if reference_class is not None:
+            self.query = reference_class.all()
 
     def _get_data(self):
         if self._formdata is not None:
             for obj in self.query:
-                key = str(obj.key())
-                if key == self._formdata:
-                    self._set_data(key)
+                if str(obj.key()) == self._formdata:
+                    self._set_data(obj)
                     break
         return self._data
 
 
         for obj in self.query:
             key = str(obj.key())
-            label = self.label_attr and getattr(obj, self.label_attr) or key
-            yield (key, label, key == self.data)
+            label = self.label_attr and getattr(obj, self.label_attr) or obj
+            yield (key, label, self.data and ( self.data.key( ) == obj.key() ) )
 
     def process_formdata(self, valuelist):
         if valuelist:
 
     def pre_validate(self, form):
         if not self.allow_blank or self.data is not None:
-            for obj in self.queryset:
-                if self.data == str(obj.key()):
+            for obj in self.query:
+                if str(self.data.key()) == str(obj.key()):
                     break
             else:
-                raise ValidationError('Not a valid choice')
+                raise ValueError(self.gettext(u'Not a valid choice'))
 
 
 class StringListPropertyField(fields.TextAreaField):
     A field for ``db.StringListProperty``. The list items are rendered in a
     textarea.
     """
-    def process_data(self, value):
-        if isinstance(value, list):
-            value = '\n'.join(value)
+    def _value(self):
+        if self.raw_data:
+            return self.raw_data[0]
+        else:
+            return self.data and unicode("\n".join(self.data)) or u''
 
-        self.data = value
-
-    def populate_obj(self, obj, name):
-        if isinstance(self.data, basestring):
-            value = self.data.splitlines()
-        else:
-            value = []
-
-        setattr(obj, name, value)
+    def process_formdata(self, valuelist):
+        if valuelist:
+            try:
+                self.data = valuelist[0].splitlines()
+            except ValueError:
+                raise ValueError(self.gettext(u'Not a valid list'))
 
 
 class GeoPtPropertyField(fields.TextField):
-    """For now, no processing or prevalidation is done."""
+
+    def process_formdata(self, valuelist):
+        if valuelist:
+            try:
+                lat, lon = valuelist[0].split(',')
+                self.data = u'%s,%s' % (decimal.Decimal(lat.strip()), decimal.Decimal(lon.strip()),)
+            except (decimal.InvalidOperation, ValueError):
+                raise ValueError(u'Not a valid coordinate location')

wtforms/ext/dateutil/fields.py

     :param display_format:
         A format string to pass to strftime() to format dates for display.
     """
-    widget = TextInput
+    widget = TextInput()
 
-    def __init__(self, label=u'', validators=None, parse_kwargs=None,
+    def __init__(self, label=None, validators=None, parse_kwargs=None,
                  display_format='%Y-%m-%d %H:%M', **kwargs):
         super(DateTimeField, self).__init__(label, validators, **kwargs)
         if parse_kwargs is None:
     def process_formdata(self, valuelist):
         if valuelist:
             date_str = u' '.join(valuelist)
+            if not date_str:
+                self.data = None
+                raise ValidationError(self.gettext(u'Please input a date/time value'))
+
             parse_kwargs = self.parse_kwargs.copy()
             if 'default' not in parse_kwargs:
                 try:
                 self.data = parser.parse(date_str, **parse_kwargs)
             except ValueError:
                 self.data = None
-                raise ValidationError(u'Invalid date/time input')
+                raise ValidationError(self.gettext(u'Invalid date/time input'))
 
 
 class DateField(DateTimeField):
     """
     Same as the DateTimeField, but stores only the date portion.
     """
-    def __init__(self, label=u'', validators=None, parse_kwargs=None,
+    def __init__(self, label=None, validators=None, parse_kwargs=None,
                  display_format='%Y-%m-%d', **kwargs):
         super(DateField, self).__init__(label, validators, parse_kwargs=parse_kwargs, display_format=display_format, **kwargs)
 

wtforms/ext/django/fields.py

 """
 Useful form fields for use with the Django ORM.
 """
+import operator
+import warnings
+
 from wtforms import widgets
 from wtforms.fields import SelectFieldBase
 from wtforms.validators import ValidationError
     Given a QuerySet either at initialization or inside a view, will display a
     select drop-down field of choices. The `data` property actually will
     store/keep an ORM model instance, not the ID. Submitting a choice which is
-    not in the queryset will result in a validation error. 
+    not in the queryset will result in a validation error.
 
-    Specifying `label_attr` in the constructor will use that property of the
-    model instance for display in the list, else the model object's `__str__`
-    or `__unicode__` will be used.
+    Specify `get_label` to customize the label associated with each option. If
+    a string, this is the name of an attribute on the model object to use as
+    the label text. If a one-argument callable, this callable will be passed
+    model instance and expected to return the label text. Otherwise, the model
+    object's `__str__` or `__unicode__` will be used.
 
     If `allow_blank` is set to `True`, then a blank choice will be added to the
     top of the list. Selecting this choice will result in the `data` property
     """
     widget = widgets.Select()
 
-    def __init__(self, label=u'', validators=None, queryset=None, label_attr='', allow_blank=False, blank_text=u'', **kwargs):
+    def __init__(self, label=None, validators=None, queryset=None, get_label=None, label_attr=None, allow_blank=False, blank_text=u'', **kwargs):
         super(QuerySetSelectField, self).__init__(label, validators, **kwargs)
-        self.label_attr = label_attr
         self.allow_blank = allow_blank
         self.blank_text = blank_text
         self._set_data(None)
         if queryset is not None:
             self.queryset = queryset.all() # Make sure the queryset is fresh
 
+        if label_attr is not None:
+            warnings.warn('label_attr= will be removed in WTForms 0.7, use get_label= instead.', DeprecationWarning)
+            self.get_label = operator.attrgetter(label_attr)
+        elif get_label is None:
+            self.get_label = lambda x: x
+        elif isinstance(get_label, basestring):
+            self.get_label = operator.attrgetter(get_label)
+        else:
+            self.get_label = get_label
+
     def _get_data(self):
         if self._formdata is not None:
             for obj in self.queryset:
             yield (u'__None', self.blank_text, self.data is None)
 
         for obj in self.queryset:
-            label = self.label_attr and getattr(obj, self.label_attr) or obj
-            yield (obj.pk, label, obj == self.data)
+            yield (obj.pk, self.get_label(obj), obj == self.data)
 
     def process_formdata(self, valuelist):
         if valuelist:
                 if self.data == obj:
                     break
             else:
-                raise ValidationError('Not a valid choice')
+                raise ValidationError(self.gettext('Not a valid choice'))
 
 
 class ModelSelectField(QuerySetSelectField):
     Like a QuerySetSelectField, except takes a model class instead of a
     queryset and lists everything in it.
     """
-    def __init__(self, label=u'', validators=None, model=None, **kwargs):
+    def __init__(self, label=None, validators=None, model=None, **kwargs):
         super(ModelSelectField, self).__init__(label, validators, queryset=model._default_manager.all(), **kwargs)

wtforms/ext/sqlalchemy/fields.py

 Useful form fields for use with SQLAlchemy ORM.
 """
 import operator
-import warnings
 
 from wtforms import widgets
 from wtforms.fields import SelectFieldBase
 
 
 __all__ = (
-    'QuerySelectField', 'QuerySelectMultipleField', 'ModelSelectField',
+    'QuerySelectField', 'QuerySelectMultipleField',
 )
 
 
     top of the list. Selecting this choice will result in the `data` property
     being `None`. The label for this blank choice can be set by specifying the
     `blank_text` parameter.
-
-    The `pk_attr` and `label_attr` parameters are deprecated and will be
-    removed in a future release.
     """
     widget = widgets.Select()
 
-    def __init__(self, label=u'', validators=None, query_factory=None,
-                 get_pk=None, get_label=None, allow_blank=False, blank_text=u'',
-                 pk_attr=None, label_attr=None, **kwargs):
+    def __init__(self, label=None, validators=None, query_factory=None,
+                 get_pk=None, get_label=None, allow_blank=False,
+                 blank_text=u'', **kwargs):
         super(QuerySelectField, self).__init__(label, validators, **kwargs)
         self.query_factory = query_factory
-        if pk_attr is not None:
-            warnings.warn('pk_attr= will be removed in WTForms 0.7, use get_pk= instead.', DeprecationWarning)
-            self.get_pk = operator.attrgetter(pk_attr)
-        elif get_pk is None:
+
+        if get_pk is None:
             if not has_identity_key:
                 raise Exception('The sqlalchemy identity_key function could not be imported.')
             self.get_pk = get_pk_from_identity
         else:
             self.get_pk = get_pk
 
-        if label_attr is not None:
-            warnings.warn('label_attr= will be removed in WTForms 0.7, use get_label= instead.', DeprecationWarning)
-            self.get_label = operator.attrgetter(label_attr)
-        elif get_label is None:
+        if get_label is None:
             self.get_label = lambda x: x
         elif isinstance(get_label, basestring):
             self.get_label = operator.attrgetter(get_label)
                 if self.data == obj:
                     break
             else:
-                raise ValidationError('Not a valid choice')
+                raise ValidationError(self.gettext(u'Not a valid choice'))
 
 
 class QuerySelectMultipleField(QuerySelectField):
     """
     widget = widgets.Select(multiple=True)
 
-    def __init__(self, label=u'', validators=None, default=None, **kwargs):
+    def __init__(self, label=None, validators=None, default=None, **kwargs):
         if default is None:
             default = []
         super(QuerySelectMultipleField, self).__init__(label, validators, default=default, **kwargs)
 
     def pre_validate(self, form):
         if self._invalid_formdata:
-            raise ValidationError('Not a valid choice')
+            raise ValidationError(self.gettext(u'Not a valid choice'))
         elif self.data:
             obj_list = list(x[1] for x in self._get_object_list())
             for v in self.data:
                 if v not in obj_list:
-                    raise ValidationError('Not a valid choice')
-
-
-class ModelSelectField(QuerySelectField):
-    """
-    Similar to QuerySelectField, only for model classes.
-
-    Using this field is only meaningful when using scoped sessions in
-    SQLAlchemy, because otherwise model instances do not know how to make
-    queries of themselves. This field is simply a convenience for using
-    `Model.query` as the factory for QuerySelectField.
-
-    **Note**: This field is deprecated and will be removed in a future release
-    of WTForms.
-    """
-    def __init__(self, label=u'', validators=None, model=None, **kwargs):
-        warnings.warn(
-            'Session-aware mappers are deprecated as of SQLAlchemy 0.5.5; '
-            'this field will be removed by WTForms 0.7',
-            DeprecationWarning
-        )
-        assert model is not None, "Must specify a model."
-        query_factory = lambda: model.query
-        super(ModelSelectField, self).__init__(label, validators, query_factory=query_factory, **kwargs)
+                    raise ValidationError(self.gettext('Not a valid choice'))
 
 
 def get_pk_from_identity(obj):

wtforms/fields.py

-import datetime
-import decimal
-import itertools
-import time
-from gettext import gettext as _
-
-from wtforms import widgets
-from wtforms.validators import StopValidation
-
-
-__all__ = (
-    'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
-    'FileField', 'FloatField', 'FormField', 'HiddenField', 'IntegerField',
-    'PasswordField', 'RadioField', 'SelectField', 'SelectMultipleField',
-    'SubmitField', 'TextField', 'TextAreaField',
-)
-
-
-_unset_value = object()
-
-
-class Field(object):
-    """
-    Field base class
-    """
-    widget = None
-    errors = tuple()
-    process_errors = tuple()
-    _formfield = True
-
-    def __new__(cls, *args, **kwargs):
-        if '_form' in kwargs and '_name' in kwargs:
-            return super(Field, cls).__new__(cls)
-        else:
-            return UnboundField(cls, *args, **kwargs)
-
-    def __init__(self, label=u'', validators=None, filters=tuple(),
-                 description=u'', id=None, default=None, widget=None,
-                 _form=None, _name=None, _prefix=''):
-        """
-        Construct a new field.
-
-        :param label:
-            The label of the field. Available after construction through the
-            `label` property.
-        :param validators:
-            A sequence of validators to call when `validate` is called.
-        :param filters:
-            A sequence of filters which are run on input data by `process`.
-        :param description:
-            A description for the field, typically used for help text.
-        :param id:
-            An id to use for the field. A reasonable default is set by the form,
-            and you shouldn't need to set this manually.
-        :param default:
-            The default value to assign to the field, if no form or object
-            input is provided. May be a callable.
-        :param widget:
-            If provided, overrides the widget used to render the field.
-        :param _form:
-            The form holding this field. It is passed by the form itself during
-            construction. You should never pass this value yourself.
-        :param _name:
-            The name of this field, passed by the enclosing form during its
-            construction. You should never pass this value yourself.
-        :param _prefix:
-            The prefix to prepend to the form name of this field, passed by
-            the enclosing form during construction.
-
-        If `_form` and `_name` isn't provided, an :class:`UnboundField` will be
-        returned instead. Call its :func:`bind` method with a form instance and
-        a name to construct the field.
-        """
-        self.short_name = _name
-        self.name = _prefix + _name
-        self.id = id or self.name
-        self.label = Label(self.id, label or _name.replace('_', ' ').title())
-        if validators is None:
-            validators = []
-        self.validators = validators
-        self.filters = filters
-        self.description = description
-        self.type = type(self).__name__
-        self.default = default
-        self.raw_data = None
-        if widget:
-            self.widget = widget
-        self.flags = Flags()
-        for v in validators:
-            flags = getattr(v, 'field_flags', ())
-            for f in flags:
-                setattr(self.flags, f, True)
-
-    def __unicode__(self):
-        """
-        Returns a HTML representation of the field. For more powerful rendering,
-        see the `__call__` method.
-        """
-        return self()
-
-    def __str__(self):
-        """
-        Returns a HTML representation of the field. For more powerful rendering,
-        see the `__call__` method.
-        """
-        return self()
-
-    def __html__(self):
-        """
-        Returns a HTML representation of the field. For more powerful rendering,
-        see the `__call__` method.
-        """
-        return self()
-
-    def __call__(self, **kwargs):
-        """
-        Render this field as HTML, using keyword args as additional attributes.
-
-        Any HTML attribute passed to the method will be added to the tag
-        and entity-escaped properly.
-        """
-        return self.widget(self, **kwargs)
-
-    def validate(self, form, extra_validators=tuple()):
-        """
-        Validates the field and returns True or False. `self.errors` will
-        contain any errors raised during validation. This is usually only
-        called by `Form.validate`.
-
-        Subfields shouldn't override this, but rather override either
-        `pre_validate`, `post_validate` or both, depending on needs.
-
-        :param form: The form the field belongs to.
-        :param extra_validators: A list of extra validators to run.
-        """
-        self.errors = list(self.process_errors)
-        stop_validation = False
-
-        # Call pre_validate
-        try:
-            self.pre_validate(form)
-        except StopValidation, e:
-            if e.args and e.args[0]:
-                self.errors.append(e.args[0])
-            stop_validation = True
-        except ValueError, e:
-            self.errors.append(e.args[0])
-
-        # Run validators
-        if not stop_validation:
-            for validator in itertools.chain(self.validators, extra_validators):
-                try:
-                    validator(form, self)
-                except StopValidation, e:
-                    if e.args and e.args[0]:
-                        self.errors.append(e.args[0])
-                    stop_validation = True
-                    break
-                except ValueError, e:
-                    self.errors.append(e.args[0])
-
-        # Call post_validate
-        try:
-            self.post_validate(form, stop_validation)
-        except ValueError, e:
-            self.errors.append(e.args[0])
-
-        return len(self.errors) == 0
-
-    def pre_validate(self, form):
-        """
-        Override if you need field-level validation. Runs before any other
-        validators.
-
-        :param form: The form the field belongs to.
-        """
-        pass
-
-    def post_validate(self, form, validation_stopped):
-        """
-        Override if you need to run any field-level validation tasks after
-        normal validation. This shouldn't be needed in most cases.
-
-        :param form: The form the field belongs to.
-        :param validation_stopped:
-            `True` if any validator raised StopValidation.
-        """
-        pass
-
-    def process(self, formdata, data=_unset_value):
-        """
-        Process incoming data, calling process_data, process_formdata as needed,
-        and run filters.
-
-        If `data` is not provided, process_data will be called on the field's
-        default.
-
-        Field subclasses usually won't override this, instead overriding the
-        process_formdata and process_data methods. Only override this for
-        special advanced processing, such as when a field encapsulates many
-        inputs.
-        """
-        self.process_errors = []
-        if data is _unset_value:
-            try:
-                data = self.default()
-            except TypeError:
-                data = self.default
-        try:
-            self.process_data(data)
-        except ValueError, e:
-            self.process_errors.append(e.args[0])
-
-        if formdata:
-            try:
-                if self.name in formdata:
-                    self.raw_data = formdata.getlist(self.name)
-                else:
-                    self.raw_data = []
-                self.process_formdata(self.raw_data)
-            except ValueError, e:
-                self.process_errors.append(e.args[0])
-
-        for filter in self.filters:
-            try:
-                self.data = filter(self.data)
-            except ValueError, e:
-                self.process_errors.append(e.args[0])
-
-    def process_data(self, value):
-        """
-        Process the Python data applied to this field and store the result.
-
-        This will be called during form construction by the form's `kwargs` or
-        `obj` argument.
-
-        :param value: The python object containing the value to process.
-        """
-        self.data = value
-
-    def process_formdata(self, valuelist):
-        """
-        Process data received over the wire from a form.
-
-        This will be called during form construction with data supplied
-        through the `formdata` argument.
-
-        :param valuelist: A list of strings to process.
-        """
-        if valuelist:
-            self.data = valuelist[0]
-
-    def populate_obj(self, obj, name):
-        """
-        Populates `obj.<name>` with the field's data.
-
-        :note: This is a destructive operation. If `obj.<name>` already exists,
-               it will be overridden. Use with caution.
-        """
-        setattr(obj, name, self.data)
-
-
-class UnboundField(object):
-    _formfield = True
-    creation_counter = 0
-
-    def __init__(self, field_class, *args, **kwargs):
-        UnboundField.creation_counter += 1
-        self.field_class = field_class
-        self.args = args
-        self.kwargs = kwargs
-        self.creation_counter = UnboundField.creation_counter
-
-    def bind(self, form, name, prefix='', **kwargs):
-        return self.field_class(_form=form, _prefix=prefix, _name=name, *self.args, **dict(self.kwargs, **kwargs))
-
-    def __repr__(self):
-        return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)
-
-
-class Flags(object):
-    """
-    Holds a set of boolean flags as attributes.
-
-    Accessing a non-existing attribute returns False for its value.
-    """