Codebase list flask-wtf / b49f1de
Imported Upstream version 0.5.2 SVN-Git Migration 8 years ago
13 changed file(s) with 787 addition(s) and 242 deletion(s). Raw diff Collapse all Expand all
00 Metadata-Version: 1.0
11 Name: Flask-WTF
2 Version: 0.3.3
2 Version: 0.5.2
33 Summary: Simple integration of Flask and WTForms
44 Home-page: http://bitbucket.org/danjac/flask-wtf
55 Author: Dan Jacob
1111 docs/Makefile
1212 docs/conf.py
1313 docs/index.rst
14 docs/index.rst.orig
1415 docs/make.bat
1516 docs/_static/flask-wtf.png
1617 docs/_themes/README
2223 docs/_themes/flask_small/static/flasky.css_t
2324 flaskext/__init__.py
2425 flaskext/wtf/__init__.py
26 flaskext/wtf/file.py
27 flaskext/wtf/html5.py
2528 flaskext/wtf/recaptcha/__init__.py
2629 flaskext/wtf/recaptcha/fields.py
2730 flaskext/wtf/recaptcha/validators.py
2831 flaskext/wtf/recaptcha/widgets.py
2932 tests/__init__.py
30 tests/__init__.py.orig
3133 tests/flask.png
3234 tests/templates/hidden.html
3335 tests/templates/index.html
00 Metadata-Version: 1.0
11 Name: Flask-WTF
2 Version: 0.3.3
2 Version: 0.5.2
33 Summary: Simple integration of Flask and WTForms
44 Home-page: http://bitbucket.org/danjac/flask-wtf
55 Author: Dan Jacob
4444 # built documents.
4545 #
4646 # The short X.Y version.
47 version = '0.3'
47 version = '0.5'
4848 # The full version, including alpha/beta/rc tags.
49 release = '0.3'
49 release = '0.5'
5050
5151 # The language for content autogenerated by Sphinx. Refer to documentation
5252 # for a list of supported languages.
3737
3838 form = MyForm(csrf_enabled=False)
3939
40 Generally speaking it's a good idea to enable CSRF. There are two situations where you might not want to:
41 unit tests and AJAX forms. In the first case, switching ``CSRF_ENABLED`` to ``False`` means that your
42 forms will still work (and the CSRF hidden field will still be printed) but no validation will be done. In the
43 second, CSRF validation is skipped if ``request.is_xhr`` is ``True`` (you can't do cross-domain AJAX anyway,
44 so CSRF validation is redundant).
40 Generally speaking it's a good idea to enable CSRF. If you wish to disable checking in certain circumstances - for
41 example, in unit tests - you can set ``CSRF_ENABLED`` to **False** in your configuration.
42
43 **NOTE:** Previous to version **0.5.2**, **Flask-WTF** automatically skipped CSRF validation in the case of AJAX
44 POST requests, as AJAX toolkits added headers such as ``X-Requested-With`` when using the XMLHttpRequest and browsers
45 enforced a strict same-origin policy.
46
47 However it has since come to light that various browser plugins can circumvent these measures, rendering AJAX requests
48 insecure by allowing forged requests to appear as an AJAX request.
49
50 Therefore CSRF checking will now be applied to all POST requests, unless you disable CSRF at your own risk through the options
51 described above.
52
53 A more complete description of the issue can be found `here <http://www.djangoproject.com/weblog/2011/feb/08/security/>`_.
54
55 One common pattern in wtforms is `enclosed forms <http://wtforms.simplecodes.com/docs/0.6.1/fields.html#field-enclosures>`_. For example::
56
57 class TelephoneForm(Form):
58 country_code = IntegerField('Country Code', [validators.required()])
59 area_code = IntegerField('Area Code/Exchange', [validators.required()])
60 number = TextField('Number')
61
62 class ContactForm(Form):
63 first_name = TextField()
64 last_name = TextField()
65 mobile_phone = FormField(TelephoneForm)
66 office_phone = FormField(TelephoneForm)
67
68 The problem with using the ``Form`` class provided by Flask-WTF is that the class will automatically include the CSRF validation. You don't need this for every single form - just the enclosing "master" form.
69
70 The easiest way to do this is to just override the enclosed form constructor::
71
72 class TelephoneForm(Form):
73 country_code = IntegerField('Country Code', [validators.required()])
74 area_code = IntegerField('Area Code/Exchange', [validators.required()])
75 number = TextField('Number')
76
77 def __init__(self, *args, **kwargs):
78 kwargs['csrf_enabled'] = False
79 super(TelephoneForm, self).__init__(*args, **kwargs)
80
81 This will disable CSRF validation for all ``TelephoneForm`` instances.
4582
4683 The ``CSRF_SESSION_KEY`` sets the key used in the Flask session for storing the generated token string. Usually
4784 the default should suffice, in certain cases you might want a custom key (for example, having several forms in a
115152 form=form,
116153 filename=filename)
117154
155 It's recommended you use **werkzeug.secure_filename** on any uploaded files as shown in the example to prevent malicious attempts to access your filesystem.
156
118157 Remember to set the ``enctype`` of your HTML form to ``multipart/form-data`` to enable file uploads::
119158
120159 <form action="." method="POST" enctype="multipart/form-data">
121160 ....
122161 </form>
162
163 **Note:** as of version **0.4** all **FileField** instances have access to the corresponding **FileStorage** object
164 in **request.files**, including those embedded in **FieldList** instances.
165
166
167 Validating file uploads
168 -----------------------
169
170 **Flask-WTF** supports validation through the `Flask Uploads <http://packages.python.org/Flask-Uploads/>`_ extension. If you use this (highly recommended) extension you can use it to add validation to your file fields. For example::
171
172
173 from flaskext.uploads import UploadSet, IMAGES
174 from flaskext.wtf import Form, FileField, file_allowed, \
175 file_required
176
177 images = UploadSet("images", IMAGES)
178
179 class UploadForm(Form):
180
181 upload = FileField("Upload your image",
182 validators=[file_required(),
183 file_allowed(images, "Images only!")])
184
185 In the above example, only image files (JPEGs, PNGs etc) can be uploaded. The **file_required** validator, which does not require **Flask-Uploads**, will raise a validation error if the field does not contain a **FileStorage** object.
186
187
188 HTML5 widgets
189 -------------
190
191 **Flask-WTF** supports a number of HTML5 widgets. Of course, these widgets must be supported by your target browser(s) in order to be properly used.
192
193 HTML5-specific widgets are available under the **flaskext.wtf.html5** package::
194
195 from flaskext.wtf.html5 import URLField
196
197 class LinkForm():
198
199 url = URLField(validators=[url()])
200
201 See the `API`_ for more details.
123202
124203 Recaptcha
125204 ---------
142221
143222 ``RECAPTCHA_OPTIONS`` is an optional dict of configuration options. The public and private keys are required in
144223 order to authenticate your request with Recaptcha - see `documentation <https://www.google.com/recaptcha/admin/create>`_ for details on how to obtain your keys.
224
225 Under test conditions (i.e. Flask app ``testing`` is ``True``) Recaptcha will always validate - this is because it's hard to know the correct Recaptcha image when running tests. Bear in mind that you need to pass the data to `recaptcha_challenge_field` and `recaptcha_response_field`, not `recaptcha`::
226
227 response = self.client.post("/someurl/", data={
228 'recaptcha_challenge_field' : 'test',
229 'recaptcha_response_field' : 'test'})
145230
146231 If `flaskext-babel <http://packages.python.org/Flask-Babel/>`_ is installed then Recaptcha message strings can be localized.
147232
210295
211296 .. autoclass:: RecaptchaWidget
212297
298 .. module:: flaskext.wtf.file
299
300 .. autoclass:: FileField
301 :members:
302
303 .. autoclass:: FileAllowed
304
305 .. autoclass:: FileRequired
306
307 .. module:: flaskext.wtf.html5
308
309 .. autoclass:: SearchInput
310
311 .. autoclass:: SearchField
312
313 .. autoclass:: URLInput
314
315 .. autoclass:: URLField
316
317 .. autoclass:: EmailInput
318
319 .. autoclass:: EmailField
320
321 .. autoclass:: NumberInput
322
323 .. autoclass:: IntegerField
324
325 .. autoclass:: DecimalField
326
327 .. autoclass:: RangeInput
328
329 .. autoclass:: IntegerRangeField
330
331 .. autoclass:: DecimalRangeField
332
333
334
213335 .. _Flask: http://flask.pocoo.org
214336 .. _Bitbucket: http://bitbucket.org/danjac/flask-wtf
0 Flask-WTF
1 ======================================
2
3 .. module:: Flask-WTF
4
5 **Flask-WTF** offers simple integration with `WTForms <http://wtforms.simplecodes.com/docs/0.6/>`_. This integration
6 includes optional CSRF handling for greater security.
7
8 Source code and issue tracking at `Bitbucket`_.
9
10 Installing Flask-WTF
11 ---------------------
12
13 Install with **pip** and **easy_install**::
14
15 pip install Flask-WTF
16
17 or download the latest version from Bitbucket::
18
19 hg clone http://bitbucket.org/danjac/flask-wtf
20
21 cd flask-wtf
22
23 python setup.py develop
24
25 If you are using **virtualenv**, it is assumed that you are installing Flask-WTF
26 in the same virtualenv as your Flask application(s).
27
28 Configuring Flask-WTF
29 ----------------------
30
31 The following settings are used with **Flask-WTF**:
32
33 * ``CSRF_ENABLED`` default ``True``
34 * ``CSRF_SESSION_KEY`` default ``_csrf_token``
35
36 ``CSRF_ENABLED`` enables CSRF. You can disable by passing in the ``csrf_enabled`` parameter to your form::
37
38 form = MyForm(csrf_enabled=False)
39
40 Generally speaking it's a good idea to enable CSRF. There are two situations where you might not want to:
41 unit tests and AJAX forms. In the first case, switching ``CSRF_ENABLED`` to ``False`` means that your
42 forms will still work (and the CSRF hidden field will still be printed) but no validation will be done. In the
43 second, CSRF validation is skipped if ``request.is_xhr`` is ``True`` (you can't do cross-domain AJAX anyway,
44 so CSRF validation is redundant).
45
46 One common pattern in wtforms is `enclosed forms <http://wtforms.simplecodes.com/docs/0.6.1/fields.html#field-enclosures>`_. For example::
47
48 class TelephoneForm(Form):
49 country_code = IntegerField('Country Code', [validators.required()])
50 area_code = IntegerField('Area Code/Exchange', [validators.required()])
51 number = TextField('Number')
52
53 class ContactForm(Form):
54 first_name = TextField()
55 last_name = TextField()
56 mobile_phone = FormField(TelephoneForm)
57 office_phone = FormField(TelephoneForm)
58
59 The problem with using the ``Form`` class provided by Flask-WTF is that the class will automatically include the CSRF validation. You don't need this for every single form - just the enclosing "master" form.
60
61 The easiest way to do this is to just override the enclosed form constructor::
62
63 class TelephoneForm(Form):
64 country_code = IntegerField('Country Code', [validators.required()])
65 area_code = IntegerField('Area Code/Exchange', [validators.required()])
66 number = TextField('Number')
67
68 def __init__(self, *args, **kwargs):
69 kwargs['csrf_enabled'] = False
70 super(TelephoneForm, self).__init__(*args, **kwargs)
71
72 This will disable CSRF validation for all ``TelephoneForm`` instances.
73
74 The ``CSRF_SESSION_KEY`` sets the key used in the Flask session for storing the generated token string. Usually
75 the default should suffice, in certain cases you might want a custom key (for example, having several forms in a
76 single page).
77
78 Both these settings can be overriden in the ``Form`` constructor by passing in ``csrf_enabled`` and ``csrf_session_key``
79 optional arguments respectively.
80
81 In addition, there are additional configuration settings required for Recaptcha integration : see below.
82
83 Creating forms
84 --------------
85
86 **Flask-WTF** provides you with all the API features of WTForms. For example::
87
88 from flaskext.wtf import Form, TextField, Required
89
90 class MyForm(Form):
91 name = TextField(name, validators=[Required()])
92
93 In addition, a CSRF token hidden field is created. You can print this in your template as any other field::
94
95
96 <form method="POST" action=".">
97 {{ form.csrf }}
98 {{ form.name.label }} {{ form.name(size=20) }}
99 <input type="submit" value="Go">
100 </form>
101
102 However, in order to create valid XHTML/HTML the ``Form`` class has a method ``hidden_tag`` which renders any
103 hidden fields, including the CSRF field, inside a hidden DIV tag::
104
105 <form method="POST" action=".">
106 {{ form.hidden_tag() }}
107
108 Using the 'safe' filter
109 -----------------------
110
111 The **safe** filter used to be required with WTForms in Jinja2 templates, otherwise your markup would be escaped. For example:
112
113 {{ form.name|safe }}
114
115 However widgets in the latest version of WTForms return a `HTML safe string <http://jinja.pocoo.org/2/documentation/api#jinja2.Markup>`_ so you shouldn't need to use **safe**.
116
117 Ensure you are running the latest stable version of WTForms so that you don't need to use this filter everywhere.
118
119 File uploads
120 ------------
121
122 The ``Form`` instance automatically appends a ``file`` attribute to any ``FileField`` field instances if the form is posted.
123
124 This ``file`` attribute is an instance of `Werkzeug FileStorage <http://werkzeug.pocoo.org/documentation/0.5.1/datastructures.html#werkzeug.FileStorage>`_ instance from ``request.files``.
125
126 For example::
127
128 from werkzeug import secure_filename
129
130 class PhotoForm(Form):
131
132 photo = FileField("Your photo")
133
134 @app.route("/upload/", methods=("GET", "POST"))
135 def upload():
136 form = PhotoForm()
137 if form.validate_on_submit():
138 filename = secure_filename(form.photo.file.filename)
139 else:
140 filename = None
141
142 return render_template("upload.html",
143 form=form,
144 filename=filename)
145
146 It's recommended you use **werkzeug.secure_filename** on any uploaded files as shown in the example to prevent malicious attempts to access your filesystem.
147
148 Remember to set the ``enctype`` of your HTML form to ``multipart/form-data`` to enable file uploads::
149
150 <form action="." method="POST" enctype="multipart/form-data">
151 ....
152 </form>
153
154 **Note:** as of version **0.4** all **FileField** instances have access to the corresponding **FileStorage** object
155 in **request.files**, including those embedded in **FieldList** instances.
156
157
158 Validating file uploads
159 -----------------------
160
161 **Flask-WTF** supports validation through the Flask-Uploads extension. If you use this (highly recommended) extension you can use it to add validation to your file fields. For example::
162
163
164 from flaskext.uploads import UploadSet, IMAGES
165 from flaskext.wtf import Form, FileField, file_allowed, \
166 file_required
167
168 images = UploadSet("images", IMAGES)
169
170 class UploadForm(Form):
171
172 upload = FileField("Upload your image",
173 validators=[file_required(),
174 file_allowed(images, "Images only!")])
175
176 In the above example, only image files (JPEGs, PNGs etc) can be uploaded. The ``file_required`` validator, which does not require ``Flask-Uploads``, will raise a validation error if the field does not contain a FileStorage object.
177
178
179 HTML5 widgets
180 -------------
181
182 **Flask-WTF** supports a number of HTML5 widgets. Of course, these widgets must be supported by your target browser(s) in order to be properly used.
183
184 HTML5-specific widgets are available under the **flaskext.wtf.html5** package::
185
186 from flaskext.wtf.html5 import URLField
187
188 class LinkForm():
189
190 url = URLField(validators=[url()])
191
192 See the API for more details.
193
194 Recaptcha
195 ---------
196
197 **Flask-WTF** also provides Recaptcha support through a ``RecaptchaField``::
198
199 from flaskext.wtf import Form, TextField, RecaptchaField
200
201 class SignupForm(Form):
202 username = TextField("Username")
203 recaptcha = RecaptchaField()
204
205 This field handles all the nitty-gritty details of Recaptcha validation and output. The following settings
206 are required in order to use Recaptcha:
207
208 * ``RECAPTCHA_USE_SSL`` : default ``False``
209 * ``RECAPTCHA_PUBLIC_KEY``
210 * ``RECAPTCHA_PRIVATE_KEY``
211 * ``RECAPTCHA_OPTIONS``
212
213 ``RECAPTCHA_OPTIONS`` is an optional dict of configuration options. The public and private keys are required in
214 order to authenticate your request with Recaptcha - see `documentation <https://www.google.com/recaptcha/admin/create>`_ for details on how to obtain your keys.
215
216 Under test conditions (i.e. Flask app ``testing`` is ``True``) Recaptcha will always validate - this is because it's hard to know the correct Recaptcha image when running tests. Bear in mind that you need to pass the data to `recaptcha_challenge_field` and `recaptcha_response_field`, not `recaptcha`::
217
218 response = self.client.post("/someurl/", data={
219 'recaptcha_challenge_field' : 'test',
220 'recaptcha_response_field' : 'test'})
221
222 If `flaskext-babel <http://packages.python.org/Flask-Babel/>`_ is installed then Recaptcha message strings can be localized.
223
224 API changes
225 -----------
226
227 The ``Form`` class provided by **Flask-WTF** is the same as for WTForms, but with a couple of changes. Aside from CSRF
228 validation, a convenience method ``validate_on_submit`` is added::
229
230 from flask import Flask, request, flash, redirect, url_for, \
231 render_template
232
233 from flaskext.wtf import Form, TextField
234
235 app = Flask(__name__)
236
237 class MyForm(Form):
238 name = TextField("Name")
239
240 @app.route("/submit/", methods=("GET", "POST"))
241 def submit():
242
243 form = MyForm()
244 if form.validate_on_submit():
245 flash("Success")
246 return redirect(url_for("index"))
247 return render_template("index.html", form=form)
248
249 Note the difference from a pure WTForms solution::
250
251 from flask import Flask, request, flash, redirect, url_for, \
252 render_template
253
254 from flaskext.wtf import Form, TextField
255
256 app = Flask(__name__)
257
258 class MyForm(Form):
259 name = TextField("Name")
260
261 @app.route("/submit/", methods=("GET", "POST"))
262 def submit():
263
264 form = MyForm(request.form)
265 if request.method == "POST" and form.validate():
266 flash("Success")
267 return redirect(url_for("index"))
268 return render_template("index.html", form=form)
269
270 ``validate_on_submit`` will automatically check if the request method is PUT or POST.
271
272 You don't need to pass ``request.form`` into your form instance, as the ``Form`` automatically populates from ``request.form`` unless
273 specified. Other arguments are as with ``wtforms.Form``.
274
275 API
276 ---
277
278 .. module:: flaskext.wtf
279
280 .. autoclass:: Form
281
282 .. automodule:: flaskext.wtf.file
283
284 .. automodule:: flaskext.wtf.html5
285
286 .. _Flask: http://flask.pocoo.org
287 .. _Bitbucket: http://bitbucket.org/danjac/flask-wtf
1212 import uuid
1313
1414 from wtforms.fields import BooleanField, DecimalField, DateField, \
15 DateTimeField, FieldList, FloatField, FileField, FormField, \
15 DateTimeField, FieldList, FloatField, FormField, \
1616 HiddenField, IntegerField, PasswordField, RadioField, SelectField, \
1717 SelectMultipleField, SubmitField, TextField, TextAreaField
1818
2424 from wtforms.widgets import CheckboxInput, FileInput, HiddenInput, \
2525 ListWidget, PasswordInput, RadioInput, Select, SubmitInput, \
2626 TableWidget, TextArea, TextInput
27
28 from wtforms.fields import FileField as _FileField
2729
2830 try:
2931 import sqlalchemy
3941
4042 from jinja2 import Markup
4143
44 from flaskext.wtf import html5
45
4246 from flaskext.wtf import recaptcha
4347
4448 from flaskext.wtf.recaptcha.fields import RecaptchaField
4953 widgets.RecaptchaWidget = RecaptchaWidget
5054 validators.Recaptcha = Recaptcha
5155
52 __all__ = ['Form', 'ValidationForm',
53 'fields', 'validators', 'widgets']
56 from flaskext.wtf.file import FileField
57 from flaskext.wtf.file import FileAllowed, FileRequired, file_allowed, \
58 file_required
59
60 fields.FileField = FileField
61
62 validators.file_allowed = file_allowed
63 validators.file_required = file_required
64 validators.FileAllowed = FileAllowed
65 validators.FileRequired = FileRequired
66
67
68 __all__ = ['Form', 'ValidationError',
69 'fields', 'validators', 'widgets', 'html5']
5470
5571 __all__ += fields.__all__
5672 __all__ += validators.__all__
5975
6076 if _is_sqlalchemy:
6177 from wtforms.ext.sqlalchemy.fields import QuerySelectField, \
62 QuerySelectMultipleField, ModelSelectField
78 QuerySelectMultipleField
6379
6480 __all__ += ['QuerySelectField',
65 'QuerySelectMultipleField',
66 'ModelSelectField']
81 'QuerySelectMultipleField']
6782
6883 for field in (QuerySelectField,
69 QuerySelectMultipleField,
70 ModelSelectField):
84 QuerySelectMultipleField):
7185
7286 setattr(fields, field.__name__, field)
7387
128142 # ensure csrf validation occurs ONLY when formdata is passed
129143 # in case "csrf" is the only field in the form
130144
131 if not formdata:
145 if not formdata and not request.files:
132146 self.csrf_is_valid = False
133147 else:
134148 self.csrf_is_valid = None
135149
136 if request.files:
137
138 for name, field in self._fields.iteritems():
139 if isinstance(field, FileField) and name in request.files:
140 field.file = request.files[name]
141
142150 super(Form, self).process(formdata, obj, **kwargs)
143151
144152 @property
165173 return csrf_token
166174
167175 def validate_csrf(self, field):
168 if not self.csrf_enabled or request.is_xhr:
176 if not self.csrf_enabled:
169177 return
170178
171179 csrf_token = session.pop(self.csrf_session_key, None)
189197 Wraps hidden fields in a hidden DIV tag, in order to keep XHTML
190198 compliance.
191199
192 :versionadded: 0.3
200 .. versionadded:: 0.3
193201
194202 :param fields: list of hidden field names. If not provided will render
195203 all hidden fields, including the CSRF field.
196204 """
197205
198206 if not fields:
199 fields = [f.name for f in self if isinstance(f, HiddenField)]
207 fields = [f for f in self if isinstance(f, HiddenField)]
200208
201209 rv = [u'<div style="display:none;">']
202 rv += [unicode(getattr(self, field)) for field in fields]
210 for field in fields:
211 if isinstance(field, basestring):
212 field = getattr(self, field)
213 rv.append(unicode(field))
203214 rv.append(u"</div>")
204215
205216 return Markup(u"".join(rv))
0 from flask import request
1
2 from wtforms import FileField as _FileField
3 from wtforms import ValidationError
4
5
6 class FileField(_FileField):
7 """
8 Subclass of **wtforms.FileField** providing a `file` property
9 returning the relevant **FileStorage** instance in **request.files**.
10 """
11 @property
12 def file(self):
13 """
14 Returns FileStorage class if available from request.files
15 or None
16 """
17 return request.files.get(self.name, None)
18
19
20 class FileRequired(object):
21 """
22 Validates that field has a **FileStorage** instance
23 attached.
24
25 `message` : error message
26
27 You can also use the synonym **file_required**.
28 """
29
30 def __init__(self, message=None):
31 self.message=message
32
33 def __call__(self, form, field):
34 file = getattr(field, "file", None)
35
36 if not file:
37 raise ValidationError, self.message
38
39 file_required = FileRequired
40
41
42 class FileAllowed(object):
43 """
44 Validates that the uploaded file is allowed by the given
45 Flask-Uploads UploadSet.
46
47 `upload_set` : instance of **flaskext.uploads.UploadSet**
48
49 `message` : error message
50
51 You can also use the synonym **file_allowed**.
52 """
53
54 def __init__(self, upload_set, message=None):
55 self.upload_set = upload_set
56 self.message = message
57
58 def __call__(self, form, field):
59 file = getattr(field, "file", None)
60
61 if file is not None and \
62 not self.upload_set.file_allowed(file, file.filename):
63 raise ValidationError, self.message
64
65 file_allowed = FileAllowed
66
0 from wtforms import TextField
1 from wtforms import IntegerField as _IntegerField
2 from wtforms import DecimalField as _DecimalField
3 from wtforms import DateField as _DateField
4 from wtforms.widgets import Input
5
6 class DateInput(Input):
7 """
8 Creates `<input type=date>` widget
9 """
10 input_type = "date"
11
12
13 class NumberInput(Input):
14 """
15 Creates `<input type=number>` widget
16 """
17 input_type="number"
18
19
20 class RangeInput(Input):
21 """
22 Creates `<input type=range>` widget
23 """
24 input_type="range"
25
26
27 class URLInput(Input):
28 """
29 Creates `<input type=url>` widget
30 """
31 input_type = "url"
32
33
34 class EmailInput(Input):
35 """
36 Creates `<input type=email>` widget
37 """
38
39 input_type = "email"
40
41
42 class SearchInput(Input):
43 """
44 Creates `<input type=search>` widget
45 """
46
47 input_type = "search"
48
49
50 class SearchField(TextField):
51 """
52 **TextField** using **SearchInput** by default
53 """
54 widget = SearchInput()
55
56
57 class DateField(_DateField):
58 """
59 **DateField** using **DateInput** by default
60 """
61
62 widget = DateInput()
63
64
65 class URLField(TextField):
66 """
67 **TextField** using **URLInput** by default
68 """
69
70 widget = URLInput()
71
72
73 class EmailField(TextField):
74 """
75 **TextField** using **EmailInput** by default
76 """
77
78 widget = EmailInput()
79
80
81 class IntegerField(_IntegerField):
82 """
83 **IntegerField** using **NumberInput** by default
84 """
85
86 widget = NumberInput()
87
88
89 class DecimalField(_DecimalField):
90 """
91 **DecimalField** using **NumberInput** by default
92 """
93
94 widget = NumberInput()
95
96
97 class IntegerRangeField(_IntegerField):
98 """
99 **IntegerField** using **RangeInput** by default
100 """
101
102 widget = RangeInput()
103
104
105 class DecimalRangeField(_DecimalField):
106 """
107 **DecimalField** using **RangeInput** by default
108 """
109
110 widget = RangeInput()
3838 def _validate_recaptcha(self, challenge, response, remote_addr):
3939 """Performs the actual validation."""
4040
41 if current_app.testing:
42 return True
43
4144 try:
4245 private_key = current_app.config['RECAPTCHA_PRIVATE_KEY']
4346 except KeyError:
1818
1919 setup(
2020 name='Flask-WTF',
21 version='0.3.3',
21 version='0.5.2',
2222 url='http://bitbucket.org/danjac/flask-wtf',
2323 license='BSD',
2424 author='Dan Jacob',
3939 tests_require=[
4040 'nose',
4141 'Flask-Testing',
42 'Flask-Uploads',
4243 ],
4344 classifiers=[
4445 'Development Status :: 4 - Beta',
22 import re
33
44 from flask import Flask, Response, render_template, jsonify
5 from flaskext.uploads import UploadSet, IMAGES, TEXT, configure_uploads
56 from flaskext.testing import TestCase as _TestCase
67 from flaskext.wtf import Form, TextField, FileField, HiddenField, \
7 SubmitField, Required
8 SubmitField, Required, FieldList, file_required, file_allowed, html5
9
10 class DummyField(object):
11 def __init__(self, data, name='f', label='', id='', type='TextField'):
12 self.data = data
13 self.name = name
14 self.label = label
15 self.id = id
16 self.type = type
17
18 _value = lambda x: x.data
19 __unicode__ = lambda x: x.data
20 __call__ = lambda x, **k: x.data
21 __iter__ = lambda x: iter(x.data)
22 iter_choices = lambda x: iter(x.data)
23
824
925 class TestCase(_TestCase):
1026
1733 class HiddenFieldsForm(Form):
1834 name = HiddenField()
1935 url = HiddenField()
36 method = HiddenField()
2037 secret = HiddenField()
2138 submit = SubmitField("Submit")
39
40 def __init__(self, *args, **kwargs):
41 super(HiddenFieldsForm, self).__init__(*args, **kwargs)
42 self.method.name = '_method'
2243
2344 class SimpleForm(Form):
2445 pass
4667 assert form.csrf_enabled
4768 assert not form.validate()
4869 assert not form.validate()
49 return Response("OK")
70 return "OK"
5071
5172 @app.route("/hidden/")
5273 def hidden():
6990
7091 return app
7192
93 class HTML5Tests(TestCase):
94
95 field = DummyField("name", id="name", name="name")
96
97 def test_url_input(self):
98
99 assert html5.URLInput()(self.field) == \
100 '<input id="name" name="name" type="url" value="name" />'
101
102 def test_search_input(self):
103
104 assert html5.SearchInput()(self.field) == \
105 '<input id="name" name="name" type="search" value="name" />'
106
107 def test_date_input(self):
108
109 assert html5.DateInput()(self.field) == \
110 '<input id="name" name="name" type="date" value="name" />'
111
112 def test_email_input(self):
113
114 assert html5.EmailInput()(self.field) == \
115 '<input id="name" name="name" type="email" value="name" />'
116
117 def test_number_input(self):
118
119 assert html5.NumberInput()(self.field, min=0, max=10) == \
120 '<input id="name" max="10" min="0" name="name" type="number" value="name" />'
121
122 def test_range_input(self):
123
124 assert html5.RangeInput()(self.field, min=0, max=10) == \
125 '<input id="name" max="10" min="0" name="name" type="range" value="name" />'
126
127 # FILE UPLOAD TESTS #
128
129 images = UploadSet("images", IMAGES)
130 text = UploadSet("text", TEXT)
131
132 class FileUploadForm(Form):
133
134 upload = FileField("Upload file")
135
136 class MultipleFileUploadForm(Form):
137
138 uploads = FieldList(FileField("upload"), min_entries=3)
139
140
141 class ImageUploadForm(Form):
142
143 upload = FileField("Upload file",
144 validators=[file_required(),
145 file_allowed(images)])
146
147 class TextUploadForm(Form):
148
149 upload = FileField("Upload file",
150 validators=[file_required(),
151 file_allowed(text)])
152
153
154
72155 class TestFileUpload(TestCase):
73156
157
74158 def create_app(self):
75
76 class FileUploadForm(Form):
77
78 upload = FileField("Upload file")
79159
80160 app = super(TestFileUpload, self).create_app()
81161 app.config['CSRF_ENABLED'] = False
162 app.config['UPLOADED_FILES_DEST'] = 'uploads'
163 app.config['UPLOADS_DEFAULT_DEST'] = 'uploads'
164 configure_uploads(app, [images, text])
165
166 @app.route("/upload-image/", methods=("POST",))
167 def upload_image():
168 form = ImageUploadForm()
169 if form.validate_on_submit():
170 return "OK"
171 return "invalid"
172
173 @app.route("/upload-text/", methods=("POST",))
174 def upload_text():
175 form = TextUploadForm()
176 if form.validate_on_submit():
177 return "OK"
178 return "invalid"
179
180
181 @app.route("/upload-multiple/", methods=("POST",))
182 def upload_multiple():
183 form = MultipleFileUploadForm()
184 if form.validate_on_submit():
185 assert len(form.uploads.entries) == 3
186 for upload in form.uploads.entries:
187 assert upload.file is not None
188
189 return "OK"
190
191 @app.route("/upload-multiple-field/", methods=("POST",))
192 def upload_multiple_field():
193 form = MultipleFileFieldUploadForm()
194 if form.validate_on_submit():
195 assert len(form.uploads.files) == 3
196 for upload in form.uploads.files:
197 assert "flask.png" in upload.filename
198
199 return "OK"
82200
83201 @app.route("/upload/", methods=("POST",))
84202 def upload():
97215
98216 return app
99217
218 def test_multiple_files(self):
219
220 fps = [self.app.open_resource("flask.png") for i in xrange(3)]
221 data = [("uploads-%d" % i, fp) for i, fp in enumerate(fps)]
222 response = self.client.post("/upload-multiple/", data=dict(data))
223 assert response.status_code == 200
224
100225 def test_valid_file(self):
101226
102227 with self.app.open_resource("flask.png") as fp:
103 response = self.client.post("/upload/",
228 response = self.client.post("/upload-image/",
104229 data={'upload' : fp})
105230
106 assert "flask.png</h3>" in response.data
231 assert "OK" in response.data
232
233 def test_missing_file(self):
234
235 response = self.client.post("/upload-image/",
236 data={'upload' : "test"})
237
238 assert "invalid" in response.data
239
240 def test_invalid_file(self):
241
242 with self.app.open_resource("flask.png") as fp:
243 response = self.client.post("/upload-text/",
244 data={'upload' : fp})
245
246 assert "invalid" in response.data
247
107248
108249 def test_invalid_file(self):
109250
142283 def test_hidden_tag(self):
143284
144285 response = self.client.get("/hidden/")
145 assert response.data.count('type="hidden"') == 4
286 assert response.data.count('type="hidden"') == 5
287 assert 'name="_method"' in response.data
288
146289
147290 class TestCSRF(TestCase):
148291
+0
-203
tests/__init__.py.orig less more
0 from __future__ import with_statement
1
2 import re
3
4 from flask import Flask, render_template, jsonify, request
5 from flaskext.testing import TestCase as _TestCase
6 from flaskext.wtf import Form, TextField, FileField, HiddenField, \
7 SubmitField, Required
8
9 class TestCase(_TestCase):
10
11 def create_app(self):
12
13 class MyForm(Form):
14 name = TextField("Name", validators=[Required()])
15 submit = SubmitField("Submit")
16
17 class HiddenFieldsForm(Form):
18 name = HiddenField()
19 url = HiddenField()
20 secret = HiddenField()
21 submit = SubmitField("Submit")
22
23 app = Flask(__name__)
24 app.secret_key = "secret"
25
26 @app.route("/", methods=("GET", "POST"))
27 def index():
28
29 form = MyForm()
30 if form.validate_on_submit():
31 name = form.name.data.upper()
32 else:
33 name = ''
34
35 return render_template("index.html",
36 form=form,
37 name=name)
38
39
40 @app.route("/validate_twice/", methods=("GET", "POST"))
41 def validate_twice():
42
43 form = MyForm()
44 if request.method == "GET":
45 assert not form.validate()
46 assert not form.validate()
47 else:
48 assert form.validate()
49 assert not form.validate()
50
51 return render_template("index.html",
52 form=form)
53
54 @app.route("/hidden/")
55 def hidden():
56
57 form = HiddenFieldsForm()
58 return render_template("hidden.html", form=form)
59
60 @app.route("/ajax/", methods=("POST",))
61 def ajax_submit():
62 form = MyForm()
63 if form.validate_on_submit():
64 return jsonify(name=form.name.data,
65 success=True,
66 errors=None)
67
68 return jsonify(name=None,
69 errors=form.errors,
70 success=False)
71
72
73 return app
74
75 class TestFileUpload(TestCase):
76
77 def create_app(self):
78
79 class FileUploadForm(Form):
80
81 upload = FileField("Upload file")
82
83 app = super(TestFileUpload, self).create_app()
84 app.config['CSRF_ENABLED'] = False
85
86 @app.route("/upload/", methods=("POST",))
87 def upload():
88 form = FileUploadForm()
89 if form.validate_on_submit():
90
91 filedata = form.upload.file
92
93 else:
94
95 filedata = None
96
97 return render_template("upload.html",
98 filedata=filedata,
99 form=form)
100
101 return app
102
103 def test_valid_file(self):
104
105 with self.app.open_resource("flask.png") as fp:
106 response = self.client.post("/upload/",
107 data={'upload' : fp})
108
109 assert "flask.png</h3>" in response.data
110
111 def test_invalid_file(self):
112
113 response = self.client.post("/upload/",
114 data={'upload' : 'flask.png'})
115
116 assert "flask.png</h3>" not in response.data
117
118
119 class TestValidateOnSubmit(TestCase):
120
121 def test_not_submitted(self):
122
123 response = self.client.get("/")
124 assert 'DANNY' not in response.data
125
126 def test_submitted_not_valid(self):
127
128 self.app.config['CSRF_ENABLED'] = False
129
130 response = self.client.post("/", data={})
131
132 assert 'DANNY' not in response.data
133
134 def test_submitted_and_valid(self):
135
136 self.app.config['CSRF_ENABLED'] = False
137
138 response = self.client.post("/", data={"name" : "danny"})
139 print response.data
140
141 assert 'DANNY' in response.data
142
143
144 class TestHiddenTag(TestCase):
145
146 def test_hidden_tag(self):
147
148 response = self.client.get("/hidden/")
149 assert response.data.count('type="hidden"') == 4
150
151 class TestCSRF(TestCase):
152
153 def test_csrf_token(self):
154
155 response = self.client.get("/")
156 assert '<div style="display:none;"><input id="csrf" name="csrf" type="hidden" value' in response.data
157
158 def test_invalid_csrf(self):
159
160 response = self.client.post("/", data={"name" : "danny"})
161 assert 'DANNY' not in response.data
162 assert "Missing or invalid CSRF token" in response.data
163
164 def test_check_csrf_twice(self):
165
166 self.app.config['CSRF_ENABLED'] = False
167
168 response = self.client.get("/validate_twice/")
169 self.assert_200(response)
170
171 response = self.client.post("/validate_twice/", data={"name":"test"})
172 self.assert_200(response)
173
174 def test_csrf_disabled(self):
175
176 self.app.config['CSRF_ENABLED'] = False
177
178 response = self.client.post("/", data={"name" : "danny"})
179 assert 'DANNY' in response.data
180
181 def test_ajax(self):
182
183 response = self.client.post("/ajax/",
184 data={"name" : "danny"},
185 headers={'X-Requested-With' : 'XMLHttpRequest'})
186
187 assert response.status_code == 200
188
189 def test_valid_csrf(self):
190
191 response = self.client.get("/")
192 pattern = re.compile(r'name="csrf" type="hidden" value="([0-9a-zA-Z-]*)"')
193 match = pattern.search(response.data)
194 assert match
195
196 csrf_token = match.groups()[0]
197
198 response = self.client.post("/", data={"name" : "danny",
199 "csrf" : csrf_token})
200
201 assert "DANNY" in response.data
202