Home >Backend Development >Python Tutorial >Using WTForms form framework in Python's Flask
Download and Install
The easiest way to install WTForms is to use easy_install and pip:
easy_install WTForms # or pip install WTForms
You can manually download WTForms from PyPI and run python setup.py install .
If you are the kind of person who likes to take all the risks, just run the latest version from Git and you will be able to get the latest changeset Packaged version, or go to the project homepage to clone the code repository.
Main concepts
The Forms class is the core container of WTForms. Forms (Forms) represent a collection of fields (Fields), and fields can Accessed through the form's dictionary form or attribute form.
Fields (fields) do the most heavy lifting. Each field (field) represents a data type, and field operations force the form input to that data type. For example, InputRequired and StringField Represents two different data types. In addition to the data it contains, the field also contains a large number of useful attributes, such as labels, descriptions, and lists of validation errors.
Each field (field) has a Widget (part ) instance. Widget's job is to render the HTML representation of a field. Each field can specify a Widget instance, but each field has a reasonable widget by default. Some fields are simple and convenient, such as TextAreaField which is just a default widget ( widget) is the TextArea's
StringField.
In order to specify the validation rules, the field contains a list of validators.
Start
Let's get straight to the point and define our first A form::
from wtforms import Form, BooleanField, StringField, validators class RegistrationForm(Form): username = StringField('Username', [validators.Length(min=4, max=25)]) email = StringField('Email Address', [validators.Length(min=6, max=35)]) accept_rules = BooleanField('I accept the site rules', [validators.InputRequired()])
When you create a form, the way you define fields is similar to how many ORMs define their columns ( columns): By defining class variables, i.e. instances of fields.
Because forms are regular Python classes, you can easily extend them to be what you expect::
class ProfileForm(Form): birthday = DateTimeField('Your Birthday', format='%m/%d/%y') signature = TextAreaField('Forum Signature') class AdminProfileForm(ProfileForm): username = StringField('Username', [validators.Length(max=40)]) level = IntegerField('User Level', [validators.NumberRange(min=0, max=10)])
By subclassing, the AdminProfileForm class gets all the fields of an already defined ProfileForm class. This allows you to easily share a common subset of fields between different forms, as in the example above, we Add an admin-only field to the ProfileForm.
Using the form
Using a form is as easy as instantiating it. Consider the following Django-style view function, which uses the previously defined RegistrationForm class::
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 and provide it with some data available in request.POST. Then we check that the request is Instead of using the POST method, if it is, we validate the form and check that the user adheres to the rules. If successful, we create a new User model and assign the data from the validated form to it, and finally save it.
Editing existing objects
Our previous registration example showed how to receive input and validate for new entries, but what if we want to edit an existing object? Very simple::
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 Any data that does not appear in the submitted data will be obtained from the user object.
We also use the populate_obj method of the form to repopulate the user object with the contents of the validated form. This method provides convenience for use when When the field name matches the name of the object you are providing data to. Normally, you will want to assign the value manually, but for this simple example, it is the best. It can also be used with CURD and admin Forms.
Exploring in the Console
WTForms Forms are very simple container objects. Perhaps the easiest way to find out what works for you in a form is to control Playing with forms in Taichung:
>>> from wtforms import Form, StringField, validators >>> class UsernameForm(Form): ... username = StringField('Username', [validators.Length(min=5)], default=u'test') ... >>> form = UsernameForm() >>> form['username'] <wtforms.fields.StringField 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 see is that when you instantiate a form, the form contains instances of all fields, and the fields can be accessed through a dictionary. Or attribute form. These fields have their own attributes, just like the enclosed form.
When we validate the form, it returns logical false, meaning at least one validation rule is not satisfied. form.errors will give you A summary of all the errors.
>>> form2 = UsernameForm(username=u'Robert') >>> form2.data {'username': u'Robert'} >>> form2.validate() True
This time, we pass a new value for username when instantiating the UserForm, which is enough to validate the form.
How the form gets data
In addition to using the first two parameters (formdata and obj) to provide data, you can pass keyword parameters to fill the form. Please note that some parameter names are reserved : formdata, obj, prefix.
formdata has a higher priority than obj, and obj has a higher priority than keyword parameters. 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)
Although you almost never use all 3 ways together in practice, here's an example of how WTForms looks up the username field:
If the form is submitted (request.POST is not empty), process the form input. In practice, Even if there is no form input for this field, if there is any kind of form input, then we will process the form input.
If there is no form input, then the following order is tried:
Check if user has an attribute named username.
Check if a keyword parameter named username is provided.
Finally, if All fail, using the domain's default value, if available.
Validator
WTForms中的验证器(Validators)为域(field)提供一套验证器, 当包含域的表单进行验证时运行. 你提供验证器可通过域构造函数的第二个参数validators:
class ChangeEmailForm(Form): email = StringField('Email', [validators.Length(min=6, max=120), validators.Email()])
你可以为一个域提供任意数量的验证器. 通常, 你会想要提供一个定制的错误消息:
class ChangeEmailForm(Form): email = StringField('Email', [ validators.Length(min=6, message=_(u'Little short for an email address?')), validators.Email(message=_(u'That\'s not a valid email address.')) ])
这通常更好地提供你自己的消息, 作为必要的默认消息是通用的. 这也是提供本地化错误消息的方法.
对于内置的验证器的列表, 查阅 Validators.
渲染域
渲染域和强制它为字符串一样简单:
>>> from wtforms import Form, StringField >>> class SimpleForm(Form): ... content = StringField('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" />'
然而, 渲染域的真正力量来自于它的 __call__() 方法. 调用(calling)域, 你可以提供关键词参数, 它们会在输出中作为HTML属性注入.
>>> form.content(style="width: 200px;", class_="bar") u'<input class="bar" id="content" name="content" style="width: 200px;" type="text" value="foobar" />'
现在, 让我们应用这个力量在 Jinja 模板中渲染表单. 首先, 我们的表单:
class LoginForm(Form): username = StringField('Username') password = PasswordField('Password') form = LoginForm()
然后是模板文件:
<form method="POST" action="/login"> <p>{{ form.username.label }}: {{ form.username(class="css_class") }}</p> <p>{{ form.password.label }}: {{ form.password() }}</p> </form>
相同的, 如果你使用 Django 模板, 当你想要传送关键词参数时, 你可以使用我们在Django扩展中提供的模板标签form_field:
{% load wtforms %} <form method="POST" action="/login"> <p> {{ form.username.label }}: {% form_field form.username class="css_class" %} </p> <p> {{ form.password.label }}: {{ form.password }} </p> </form>
这两个将会输出:
<form method="POST" action="/login"> <p> <label for="username">Username</label>: <input class="css_class" id="username" name="username" type="text" value="" /> </p> <p> <label for="password">Password</label>: <input id="password" name="password" type="password" value="" /> </p> </form>
WTForms是模板引擎不可知的, 同时会和任何允许属性存取、字符串强制(string coercion)、函数调用的引擎共事. 在 Django 模板中, 当你不能传送参数时, 模板标签 form_field 提供便利.
显示错误消息
现在我们的表单拥有一个模板, 让我们增加错误消息::
<form method="POST" action="/login"> <p>{{ form.username.label }}: {{ form.username(class="css_class") }}</p> {% if form.username.errors %} <ul class="errors">{% for error in form.username.errors %}<li>{{ error }}</li>{% endfor %}</ul> {% endif %} <p>{{ form.password.label }}: {{ form.password() }}</p> {% if form.password.errors %} <ul class="errors">{% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}</ul> {% endif %} </form>
如果你喜欢在顶部显示大串的错误消息, 也很简单:
{% if form.errors %} <ul class="errors"> {% for field_name, field_errors in form.errors|dictsort if field_errors %} {% for error in field_errors %} <li>{{ form[field_name].label }}: {{ error }}</li> {% endfor %} {% endfor %} </ul> {% endif %}
由于错误处理会变成相当冗长的事情, 在你的模板中使用 Jinja 宏(macros, 或者相同意义的) 来减少引用是更好的. (例子)
定制验证器
这有两种方式定制的验证器. 通过定义一个定制的验证器并在域中使用它:
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])
或者通过提供一个在表单内的特定域(in-form field-specific)的验证器:
class FourtyTwoForm(Form): num = IntegerField('Number') def validate_num(form, field): if field.data != 42: raise ValidationError(u'Must be 42')
编写WTForm扩展示例
class TagListField(Field): widget = TextInput() def _value(self): if self.data: return u', '.join(self.data) else: return u'' def process_formdata(self, valuelist): if valuelist: self.data = [x.strip() for x in valuelist[0].split(',')] else: self.data = []
根据上面的代码,将TagListField中的字符串转为models.py中定义的Tag对象即可:
class TagListField(Field): widget = TextInput() def __init__(self, label=None, validators=None, **kwargs): super(TagListField, self).__init__(label, validators, **kwargs) def _value(self): if self.data: r = u'' for obj in self.data: r += self.obj_to_str(obj) return u'' else: return u'' def process_formdata(self, valuelist): print 'process_formdata..' print valuelist if valuelist: tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(',')]) self.data = [self.str_to_obj(tag) for tag in tags] else: self.data = None def pre_validate(self, form): pass @classmethod def _remove_duplicates(cls, seq): """去重""" d = {} for item in seq: if item.lower() not in d: d[item.lower()] = True yield item @classmethod def str_to_obj(cls, tag): """将字符串转换位obj对象""" tag_obj = Tag.query.filter_by(name=tag).first() if tag_obj is None: tag_obj = Tag(name=tag) return tag_obj @classmethod def obj_to_str(cls, obj): """将对象转换为字符串""" if obj: return obj.name else: return u'' class TagListField(Field): widget = TextInput() def __init__(self, label=None, validators=None, **kwargs): super(TagListField, self).__init__(label, validators, **kwargs) def _value(self): if self.data: r = u'' for obj in self.data: r += self.obj_to_str(obj) return u'' else: return u'' def process_formdata(self, valuelist): print 'process_formdata..' print valuelist if valuelist: tags = self._remove_duplicates([x.strip() for x in valuelist[0].split(',')]) self.data = [self.str_to_obj(tag) for tag in tags] else: self.data = None def pre_validate(self, form): pass @classmethod def _remove_duplicates(cls, seq): """去重""" d = {} for item in seq: if item.lower() not in d: d[item.lower()] = True yield item @classmethod def str_to_obj(cls, tag): """将字符串转换位obj对象""" tag_obj = Tag.query.filter_by(name=tag).first() if tag_obj is None: tag_obj = Tag(name=tag) return tag_obj @classmethod def obj_to_str(cls, obj): """将对象转换为字符串""" if obj: return obj.name else: return u''
主要就是在process_formdata这一步处理表单的数据,将字符串转换为需要的数据。最终就可以在forms.py中这样定义表单了:
... class ArticleForm(Form): """编辑文章表单""" title = StringField(u'标题', validators=[Required()]) category = QuerySelectField(u'分类', query_factory=get_category_factory(['id', 'name']), get_label='name') tags = TagListField(u'标签', validators=[Required()]) content = PageDownField(u'正文', validators=[Required()]) submit = SubmitField(u'发布') ... ... class ArticleForm(Form): """编辑文章表单""" title = StringField(u'标题', validators=[Required()]) category = QuerySelectField(u'分类', query_factory=get_category_factory(['id', 'name']), get_label='name') tags = TagListField(u'标签', validators=[Required()]) content = PageDownField(u'正文', validators=[Required()]) submit = SubmitField(u'发布') ... 在views.py中处理表单就很方便了: def edit_article(): """编辑文章""" form = ArticleForm() if form.validate_on_submit(): article = Article(title=form.title.data, content=form.content.data) article.tags = form.tags.data article.category = form.category.data try: db.session.add(article) db.session.commit() except: db.session.rollback() return render_template('dashboard/edit.html', form=form) def edit_article(): """编辑文章""" form = ArticleForm() if form.validate_on_submit(): article = Article(title=form.title.data, content=form.content.data) article.tags = form.tags.data article.category = form.category.data try: db.session.add(article) db.session.commit() except: db.session.rollback() return render_template('dashboard/edit.html', form=form)
代码是不是很简洁了?^_^。。。
当然了写一个完整的WTForms扩展还是很麻烦的。这里只是刚刚入门。可以看官方扩展QuerySelectField的源码。。。
效果:
更多Using WTForms form framework in Pythons Flask相关文章请关注PHP中文网!