/app/api/packages/flaskext/wtf/__init__.py
Python | 226 lines | 121 code | 50 blank | 55 comment | 14 complexity | 4b622cc509b0247b81942bcb38b2cb51 MD5 | raw file
1# -*- coding: utf-8 -*-
2"""
3 flaskext.wtf
4 ~~~~~~~~~~~~
5
6 Flask-WTF extension
7
8 :copyright: (c) 2010 by Dan Jacob.
9 :license: BSD, see LICENSE for more details.
10"""
11
12import warnings
13import uuid
14
15from wtforms.fields import BooleanField, DecimalField, DateField, \
16 DateTimeField, FieldList, FloatField, FormField, \
17 HiddenField, IntegerField, PasswordField, RadioField, SelectField, \
18 SelectMultipleField, SubmitField, TextField, TextAreaField
19
20from wtforms.validators import Email, email, EqualTo, equal_to, \
21 IPAddress, ip_address, Length, length, NumberRange, number_range, \
22 Optional, optional, Required, required, Regexp, regexp, \
23 URL, url, AnyOf, any_of, NoneOf, none_of
24
25from wtforms.widgets import CheckboxInput, FileInput, HiddenInput, \
26 ListWidget, PasswordInput, RadioInput, Select, SubmitInput, \
27 TableWidget, TextArea, TextInput
28
29from wtforms.fields import FileField as _FileField
30
31try:
32 import sqlalchemy
33 _is_sqlalchemy = True
34except ImportError:
35 _is_sqlalchemy = False
36
37
38from wtforms import Form as BaseForm
39from wtforms import fields, widgets, validators, ValidationError
40
41from flask import request, session, current_app
42
43from jinja2 import Markup
44
45from flaskext.wtf import html5
46
47from flaskext.wtf import recaptcha
48
49from flaskext.wtf.recaptcha.fields import RecaptchaField
50from flaskext.wtf.recaptcha.widgets import RecaptchaWidget
51from flaskext.wtf.recaptcha.validators import Recaptcha
52
53fields.RecaptchaField = RecaptchaField
54widgets.RecaptchaWidget = RecaptchaWidget
55validators.Recaptcha = Recaptcha
56
57from flaskext.wtf.file import FileField
58from flaskext.wtf.file import FileAllowed, FileRequired, file_allowed, \
59 file_required
60
61fields.FileField = FileField
62
63validators.file_allowed = file_allowed
64validators.file_required = file_required
65validators.FileAllowed = FileAllowed
66validators.FileRequired = FileRequired
67
68
69__all__ = ['Form', 'ValidationError',
70 'fields', 'validators', 'widgets', 'html5']
71
72__all__ += fields.__all__
73__all__ += validators.__all__
74__all__ += widgets.__all__
75__all__ += recaptcha.__all__
76
77if _is_sqlalchemy:
78 from wtforms.ext.sqlalchemy.fields import QuerySelectField, \
79 QuerySelectMultipleField
80
81 __all__ += ['QuerySelectField',
82 'QuerySelectMultipleField']
83
84 for field in (QuerySelectField,
85 QuerySelectMultipleField):
86
87 setattr(fields, field.__name__, field)
88
89
90def _generate_csrf_token():
91 return str(uuid.uuid4())
92
93
94class Form(BaseForm):
95
96 """
97 Subclass of WTForms **Form** class. The main difference is that
98 **request.form** is passed as `formdata` argument to constructor
99 so can handle request data implicitly.
100
101 In addition this **Form** implementation has automatic CSRF handling.
102 """
103
104 csrf = fields.HiddenField()
105
106 def __init__(self, formdata=None, *args, **kwargs):
107
108 csrf_enabled = kwargs.pop('csrf_enabled', None)
109
110 if csrf_enabled is None:
111 csrf_enabled = current_app.config.get('CSRF_ENABLED', True)
112
113 self.csrf_enabled = csrf_enabled
114
115 self.csrf_session_key = kwargs.pop('csrf_session_key', None)
116
117 if self.csrf_session_key is None:
118 self.csrf_session_key = \
119 current_app.config.get('CSRF_SESSION_KEY', '_csrf_token')
120
121 csrf_token = session.get(self.csrf_session_key, None)
122
123 if csrf_token is None:
124 csrf_token = self.reset_csrf()
125
126 super(Form, self).__init__(formdata, csrf=csrf_token, *args, **kwargs)
127
128 def is_submitted(self):
129 """
130 Checks if form has been submitted. The default case is if the HTTP
131 method is **PUT** or **POST**.
132 """
133
134 return request and request.method in ("PUT", "POST")
135
136 def process(self, formdata=None, obj=None, **kwargs):
137
138 if self.is_submitted():
139
140 if formdata is None:
141 formdata = request.form
142
143 # ensure csrf validation occurs ONLY when formdata is passed
144 # in case "csrf" is the only field in the form
145
146 if not formdata and not request.files:
147 self.csrf_is_valid = False
148 else:
149 self.csrf_is_valid = None
150
151 super(Form, self).process(formdata, obj, **kwargs)
152
153 @property
154 def csrf_token(self):
155 """
156 Renders CSRF field inside a hidden DIV.
157
158 :deprecated: Use **hidden_tag** instead.
159 """
160 warnings.warn("csrf_token is deprecated. Use hidden_tag instead",
161 DeprecationWarning)
162
163 return self.hidden_tag('csrf')
164
165 def reset_csrf(self):
166 """
167 Resets the CSRF token in the session. If you are reusing the form
168 in the same view (i.e. you are not redirecting somewhere else)
169 it's recommended you call this before rendering the form.
170 """
171
172 csrf_token = _generate_csrf_token()
173 session[self.csrf_session_key] = csrf_token
174 return csrf_token
175
176 def validate_csrf(self, field):
177 if not self.csrf_enabled:
178 return
179
180 csrf_token = session.pop(self.csrf_session_key, None)
181 is_valid = field.data and \
182 field.data == csrf_token and \
183 self.csrf_is_valid is not False
184
185 # reset this field, otherwise stale token is displayed
186 field.data = self.reset_csrf()
187
188 # we set this flag to ensure consistent behaviour when
189 # calling validate() more than once
190
191 self.csrf_is_valid = bool(is_valid)
192
193 if not is_valid:
194 raise ValidationError, "Missing or invalid CSRF token"
195
196 def hidden_tag(self, *fields):
197 """
198 Wraps hidden fields in a hidden DIV tag, in order to keep XHTML
199 compliance.
200
201 .. versionadded:: 0.3
202
203 :param fields: list of hidden field names. If not provided will render
204 all hidden fields, including the CSRF field.
205 """
206
207 if not fields:
208 fields = [f for f in self if isinstance(f, HiddenField)]
209
210 rv = [u'<div style="display:none;">']
211 for field in fields:
212 if isinstance(field, basestring):
213 field = getattr(self, field)
214 rv.append(unicode(field))
215 rv.append(u"</div>")
216
217 return Markup(u"".join(rv))
218
219 def validate_on_submit(self):
220 """
221 Checks if form has been submitted and if so runs validate. This is
222 a shortcut, equivalent to ``form.is_submitted() and form.validate()``
223 """
224 return self.is_submitted() and self.validate()
225
226