Skip to content

Instantly share code, notes, and snippets.

@wichert
Created November 30, 2012 09:55
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save wichert/4174867 to your computer and use it in GitHub Desktop.
Save wichert/4174867 to your computer and use it in GitHub Desktop.
WTForms and pyramid integration
<form id="loginForm" method="post" action="${request.route_url('login')}">
<input type="hidden" name="csrf_token" value="${request.session.get_csrf_token()}"/>
<fieldset class="concise">
<metal:field tal:define="name 'came_from'" use-macro="snippets['hidden']"/>
<metal:field tal:define="name 'login'" use-macro="snippets['text']"/>
<metal:field tal:define="name 'password'" use-macro="snippets['password']"/>
</fieldset>
<div class="buttonBar">
<button type="submit" class="default" i18n:translate="">Login</button>
</div>
</form>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:tal="http://xml.zope.org/namespaces/tal"
tal:condition="False">
<metal:macro define-macro="hidden">
<input tal:define="field form[name]" type="hidden" name="${field.name}" value="${field._value()}"/>
</metal:macro>
<metal:macro define-macro="text">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="text" required="${'required' if field.flags.required else None}" name="${field.name}" value="${field._value()}"/>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="date">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="date" min="2000-01-01" required="${'required' if field.flags.required else None}" name="${field.name}" value="${field._value()}"/>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="email">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="email" required="${'required' if field.flags.required else None}" name="${field.name}" value="${field._value()}"/>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="url">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="url" required="${'required' if field.flags.required else None}" name="${field.name}" value="${field._value()}" class="span-9"/>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="password">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="password" required="${'required' if field.flags.required else None}" name="${field.name}"/>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="file">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="file" required="${'required' if field.flags.required else None}" name="${field.name}" />
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="number">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<input type="number" required="${'required' if field.flags.required else None}" name="${field.name}" value="${field._value()}" min="0" />
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
<metal:macro define-macro="radioList">
<fieldset class="comprehensive radioList" tal:define="field form[name]">
<legend>${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup></legend>
<label tal:repeat="option field.iter_choices()">
<input type="radio" name="${field.name}" value="${option[0]}"
required="${'required' if not field.flags.required else None}" checked="${'checked' if option[2] else None}"/> ${option[1]}</label>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</fieldset>
</metal:macro>
<metal:macro define-macro="checkList">
<fieldset class="comprehensive checkList" tal:define="field form[name]" >
<legend>${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup></legend>
<label tal:repeat="option field.iter_choices()">
<input type="checkbox" name="${field.name}" value="${option[0]}"
checked="${'checked' if option[2] else None}"/> ${option[1]}</label>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</fieldset>
</metal:macro>
<metal:macro define-macro="select">
<label tal:define="field form[name]">${field.label.text} <sup tal:condition="field.flags.required" class="required">*</sup>
<select name="${field.name}" required="${'request' if not field.flags.required else None}">
<option tal:repeat="option field.iter_choices()" value="${option[0]}"
selected="${'selected' if option[2] else None}">${option[1]}</option>
</select>
<em tal:repeat="msg field.errors" class="message error">${msg}</em>
</label>
</metal:macro>
</html>
import wtforms
import unittest
import pyramid.testing
from s4u.bfgtools.wtform import BasicFormView
class BooleanSelectFieldTests(unittest.TestCase):
def BooleanSelectField(self, *a, **kw):
from s4u.bfgtools.wtform import BooleanSelectField
return BooleanSelectField(*a, **kw)
def test_init_default_choices(self):
from s4u.bfgtools.wtform import BooleanSelectField
field = BooleanSelectField(_form=None, _name='field')
self.assertTrue(field.choices is BooleanSelectField.choices)
def test_init_custom_choices(self):
from s4u.bfgtools.wtform import BooleanSelectField
field = BooleanSelectField(_form=None, _name='field', choices='choices')
self.assertTrue(field.choices is not BooleanSelectField.choices)
self.assertEqual(field.choices, 'choices')
def test_iter_choices(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.data = True
self.assertEqual(field.iter_choices(),
[('false', 'No', False), ('true', 'Yes', True)])
def test_process_data_None(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.process_data(None)
self.assertEqual(field.data, None)
def test_process_data_coerce_to_boolean(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.process_data([])
self.assertEqual(field.data, False)
field.process_data('value')
self.assertEqual(field.data, True)
def test_to_python(self):
field = self.BooleanSelectField(_form=None, _name='field')
self.assertEqual(field._to_python('TrUe'), True)
self.assertEqual(field._to_python('yes'), True)
self.assertEqual(field._to_python('false'), False)
self.assertEqual(field._to_python('NO'), False)
def test_process_formdata_no_value(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.data = 'dummy'
field.process_formdata([])
self.assertEqual(field.data, None)
def test_process_formdata_proper_data(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.data = 'dummy'
field.process_formdata(['false'])
self.assertEqual(field.data, False)
def test_value_no_value(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.raw_data = None
self.assertEqual(field._value(), None)
def test_value_boolean_data(self):
field = self.BooleanSelectField(_form=None, _name='field')
field.raw_data = ['yes']
self.assertEqual(field._value(), 'true')
class DatePartFieldTests(unittest.TestCase):
def DatePartField(self, *a, **kw):
from s4u.bfgtools.wtform import DatePartField
return DatePartField(*a, **kw)
def test_process_data_no_date(self):
field = self.DatePartField(_form=None, _name='date')
field.process_data('dummy')
self.assertEqual(field.year, None)
self.assertEqual(field.month, None)
self.assertEqual(field.day, None)
def test_process_data_date_value(self):
import datetime
field = self.DatePartField(_form=None, _name='date')
field.process_data(datetime.date(2012, 2, 3))
self.assertEqual(field.year, 2012)
self.assertEqual(field.month, 2)
self.assertEqual(field.day, 3)
def test_process_default_value(self):
import datetime
field = self.DatePartField(_form=None, _name='date',
default=datetime.date(2012, 2, 3))
field.process(None)
self.assertEqual(field.year, 2012)
def test_process_default_function(self):
import datetime
field = self.DatePartField(_form=None, _name='date',
default=lambda: datetime.date(2012, 2, 3))
field.process(None)
self.assertEqual(field.year, 2012)
def test_process_custom_data(self):
import datetime
field = self.DatePartField(_form=None, _name='date')
field.process(None, datetime.date(2012, 2, 3))
self.assertEqual(field.data, datetime.date(2012, 2, 3))
self.assertEqual(field.year, 2012)
def test_process_valid_date(self):
import datetime
import mock
formdata = mock.Mock()
formdata.getlist.return_value = ['10']
field = self.DatePartField(_form=None, _name='date')
field.process(formdata)
self.assertEqual(field.raw_data['year'], '10')
self.assertEqual(field.raw_data['month'], '10')
self.assertEqual(field.raw_data['day'], '10')
self.assertEqual(field.data, datetime.date(10, 10, 10))
def test_process_no_form_data(self):
field = self.DatePartField(_form=None, _name='date')
field.process({})
self.assertEqual(field.raw_data, {})
self.assertEqual(field.data, None)
self.assertEqual(field.process_errors, [])
def test_process_missing_day(self):
import mock
field = self.DatePartField(_form=None, _name='date')
formdata = mock.Mock()
def getlist(key):
return ['1975'] if key.endswith('year') else []
formdata.getlist = getlist
field.process(formdata)
self.assertEqual(field.data, None)
self.assertEqual(len(field.process_errors), 1)
def test_process_invalid_data(self):
import mock
formdata = mock.Mock()
formdata.getlist.return_value = ['X']
field = self.DatePartField(_form=None, _name='date')
field.process(formdata)
self.assertTrue(field.process_errors)
self.assertEqual(field.data, None)
self.assertEqual(field.raw_data['year'], 'X')
self.assertEqual(field.raw_data['month'], 'X')
self.assertEqual(field.raw_data['day'], 'X')
def test_process_bad_date(self):
import mock
formdata = mock.Mock()
formdata.getlist.return_value = ['13']
field = self.DatePartField(_form=None, _name='date')
field.process(formdata)
self.assertTrue(field.process_errors)
self.assertEqual(field.data, None)
self.assertEqual(field.raw_data['year'], '13')
self.assertEqual(field.raw_data['month'], '13')
self.assertEqual(field.raw_data['day'], '13')
def test_process_filters(self):
import datetime
import mock
formdata = mock.Mock()
flt = mock.Mock(return_value='filtered')
formdata.getlist.return_value = ['10']
field = self.DatePartField(_form=None, _name='date', filters=[flt])
field.process(formdata)
flt.assert_called_with(datetime.date(10, 10, 10))
self.assertEqual(field.data, 'filtered')
def test_process_filter_errors(self):
import datetime
import mock
formdata = mock.Mock()
flt1 = mock.Mock(return_value='filtered')
flt2 = mock.Mock(side_effect=ValueError('Ooops'))
formdata.getlist.return_value = ['10']
field = self.DatePartField(_form=None, _name='date', filters=[flt1, flt2])
field.process(formdata)
flt1.assert_called_with(datetime.date(10, 10, 10))
flt2.assert_called_with('filtered')
self.assertEqual(field.data, 'filtered')
self.assertTrue(field.process_errors)
class RequiredTests(unittest.TestCase):
def Required(self, *a, **kw):
from s4u.bfgtools.wtform import Required
return Required(*a, **kw)
def test_default_message(self):
validator = self.Required()
self.assertEqual(validator.message, u'This field is required.')
def test_custom_message(self):
validator = self.Required(u'Other message')
self.assertEqual(validator.message, u'Other message')
def test_missing_value(self):
import mock
from wtforms.validators import StopValidation
field = mock.Mock()
field.errors = []
field.data = None
validator = self.Required()
self.assertRaises(StopValidation,
validator.__call__, None, field)
def test_plain_value(self):
import mock
field = mock.Mock()
field.data = 'value'
validator = self.Required()
validator(None, field)
def test_fieldstorage(self):
import mock
import cgi
field = mock.Mock()
field.data = cgi.FieldStorage()
validator = self.Required()
validator(None, field)
class NumberRangeTests(unittest.TestCase):
def NumberRange(self, *a, **kw):
from s4u.bfgtools.wtform import NumberRange
return NumberRange(*a, **kw)
def test_custom_lt_message(self):
import mock
from wtforms import ValidationError
validator = self.NumberRange(min=10, msg_less_than='My message')
field = mock.Mock()
field.data = 5
self.assertRaises(ValidationError,
validator.__call__, None, field)
field.gettext.assert_called_with('My message')
def test_custom_gt_message(self):
import mock
from wtforms import ValidationError
validator = self.NumberRange(max=10, msg_greater_than='My message')
field = mock.Mock()
field.data = 15
self.assertRaises(ValidationError,
validator.__call__, None, field)
field.gettext.assert_called_with('My message')
def test_custom_between_message(self):
import mock
from wtforms import ValidationError
validator = self.NumberRange(min=5, max=10,
msg_between='My message')
field = mock.Mock()
field.data = 15
self.assertRaises(ValidationError,
validator.__call__, None, field)
field.gettext.assert_called_with('My message')
def test_ok(self):
import mock
validator = self.NumberRange(min=5, max=10,
msg_between='My message')
field = mock.Mock()
field.data = 8
validator(None, field)
class IsImageTests(unittest.TestCase):
def IsImage(self, *a, **kw):
from s4u.bfgtools.wtform import IsImage
return IsImage(*a, **kw)
def test_validate_image_bad_image(self):
from StringIO import StringIO
validator = self.IsImage()
self.assertTrue(not validator.validate_image(StringIO('dummy')))
def test_validate_image_png(self):
from StringIO import StringIO
from s4u.image.testing import PNG
validator = self.IsImage()
self.assertTrue(validator.validate_image(StringIO(PNG)))
def test_invalid_format(self):
from StringIO import StringIO
import mock
validator = self.IsImage()
with mock.patch('PIL.Image.open') as mock_open:
image = mock.Mock()
image.format = 'BMP'
mock_open.return_value = image
self.assertTrue(not validator.validate_image(StringIO('dummy')))
def test_call_no_image_data(self):
import mock
field = mock.Mock()
field.data = None
validator = self.IsImage()
validator(None, field)
def test_call_bad_image(self):
import cgi
import mock
from wtforms import ValidationError
validator = self.IsImage()
validator.validate_image = mock.Mock(return_value=False)
field = mock.Mock()
field.data = cgi.FieldStorage()
self.assertRaises(ValidationError,
validator.__call__, None, field)
def test_call_valid_image(self):
import cgi
import mock
validator = self.IsImage()
validator.validate_image = mock.Mock(return_value=True)
field = mock.Mock()
field.data = cgi.FieldStorage()
validator(None, field)
class URLTests(unittest.TestCase):
def URL(self, *a, **kw):
from s4u.bfgtools.wtform import URL
return URL(*a, **kw)
def test_valid_url(self):
import mock
field = mock.Mock()
field.data = u'http://example.com/'
validator = self.URL()
validator(None, field)
def test_bad_url(self):
import mock
from wtforms import ValidationError
field = mock.Mock()
field.data = u'invalid'
validator = self.URL()
self.assertRaises(ValidationError, validator.__call__, None, field)
def test_custom_error_message(self):
import mock
from wtforms import ValidationError
field = mock.Mock()
field.data = u'invalid'
validator = self.URL(message='other message')
try:
validator(None, field)
except ValidationError as e:
self.assertEqual(e.message, 'other message')
def test_restrict_schema_invalid_schema(self):
import mock
from wtforms import ValidationError
field = mock.Mock()
field.data = u'ftp://example.com'
validator = self.URL(['http'])
self.assertRaises(ValidationError, validator.__call__, None, field)
def test_restrict_schema_allowed_schema(self):
import mock
field = mock.Mock()
field.data = u'http://example.com'
validator = self.URL(['http'])
validator(None, field)
def test_custom_scheme_error_message(self):
import mock
from wtforms import ValidationError
field = mock.Mock()
field.data = u'ftp://example.com'
validator = self.URL(['http'], scheme_message='other message')
try:
validator(None, field)
except ValidationError as e:
self.assertEqual(e.message, 'other message')
class EmailTests(unittest.TestCase):
def Email(self, *a, **kw):
from s4u.bfgtools.wtform import Email
return Email(*a, **kw)
def test_default_message(self):
import mock
from wtforms import ValidationError
field = mock.Mock()
field.data = u'invalid'
validator = self.Email()
try:
validator(None, field)
except ValidationError as e:
self.assertTrue(e.message is validator._message)
def test_custom_message(self):
import mock
from wtforms import ValidationError
field = mock.Mock()
field.data = u'invalid'
validator = self.Email(message='other message')
try:
validator(None, field)
except ValidationError as e:
self.assertEqual(e.message, 'other message')
class PyramidTranslationsTests(unittest.TestCase):
def PyramidTranslations(self, *a, **kw):
from s4u.bfgtools.wtform import PyramidTranslations
return PyramidTranslations(*a, **kw)
def test_interface(self):
translations = self.PyramidTranslations(pyramid.testing.DummyRequest())
self.assertTrue(callable(translations.gettext))
msg = u'foo' # Indirection to fool lingua extraction
self.assertEqual(translations.gettext(msg), u'foo')
self.assertTrue(callable(translations.ngettext))
class FormTests(unittest.TestCase):
def Form(self, *a, **kw):
from s4u.bfgtools.wtform import Form
return Form(*a, **kw)
def test_generate_csrf_token(self):
import mock
with mock.patch('pyramid.testing.DummySession.get_csrf_token') \
as mock_get:
form = self.Form(request=pyramid.testing.DummyRequest())
mock_get.return_value = 'dummy token'
self.assertEqual(
form.generate_csrf_token('context'),
'dummy token')
self.assertTrue(mock_get.called)
def test_validate_csrf_token_valid_token(self):
import mock
field = mock.Mock()
field.data = 'one'
field.current_token = 'one'
form = self.Form(request=pyramid.testing.DummyRequest())
form.validate_csrf_token(field)
def test_validate_csrf_token_invalid_token(self):
import mock
field = mock.Mock()
field.data = 'one'
field.current_token = 'two'
form = self.Form(request=pyramid.testing.DummyRequest())
self.assertRaises(ValueError, form.validate_csrf_token, field)
class BasicFormViewTests(unittest.TestCase):
def test_init_plain_request_uses_default_values(self):
class TestView(MockView):
def default_data(self):
return {'foo': 1}
view = TestView(None, pyramid.testing.DummyRequest())
self.assertEqual(view.form['foo'].data, 1)
def test_init_post_request_uses_request_data(self):
from webob.multidict import MultiDict
request = pyramid.testing.DummyRequest(post=MultiDict({'foo': '2'}))
view = MockView(None, request)
self.assertEqual(view.form['foo'].data, 2)
def test_default_data_copy_from_context(self):
context = pyramid.testing.DummyModel(foo=3)
view = MockView(context, pyramid.testing.DummyRequest())
self.assertEqual(view.form['foo'].data, 3)
def test_default_data_not_copied_from_context_if_requested(self):
context = pyramid.testing.DummyModel(foo=3)
class MyView(MockView):
defaults_from_context = False
view = MyView(context, pyramid.testing.DummyRequest())
self.assertEqual(view.form['foo'].data, None)
def test_do_post_not_implemented(self):
self.assertRaises(NotImplementedError,
MockView(None, pyramid.testing.DummyRequest()).do_post)
def test_call_failed_post(self):
import mock
from webob.multidict import MultiDict
class TestView(MockView):
def do_post(self):
return None
view = TestView(None, pyramid.testing.DummyRequest(
post=MultiDict()))
view.form = mock.Mock()
view.form.validate.return_value = True
response = view()
self.assertTrue(isinstance(response, dict))
self.assertEqual(response.keys(), ['form'])
def test_call_return_do_post_response_if_not_None(self):
from webob.multidict import MultiDict
marker = []
class TestView(MockView):
def do_post(self):
return marker
request = pyramid.testing.DummyRequest(post=MultiDict({'foo': '2'}))
view = TestView(None, request)
self.assertTrue(view() is marker)
def test_call_get_request(self):
view = MockView(None, pyramid.testing.DummyRequest())
response = view()
self.assertTrue(isinstance(response, dict))
self.assertEqual(response.keys(), ['form'])
def test_call_include_template_data(self):
view = MockView(None, pyramid.testing.DummyRequest())
view.template_data = lambda: {'foo': 'bar'}
response = view()
self.assertTrue(isinstance(response, dict))
self.assertEqual(set(response.keys()), set(['form', 'foo']))
self.assertEqual(response['foo'], 'bar')
class TestForm(wtforms.Form):
foo = wtforms.IntegerField()
class MockView(BasicFormView):
form_class = TestForm
import cgi
import datetime
import urlparse
try:
from wtforms.ext.csrf import SecureForm
except ImportError: # pragma: no cover
# BBB for WTForms <0.6.4 which does not support CSRF
from wtforms import Form as SecureForm
from wtforms.fields import _unset_value
from wtforms.fields import Field
import wtforms.validators
from wtforms import ValidationError
import PIL.Image
from pyramid.i18n import get_localizer
from s4u.bfgtools import _
class BooleanSelectField(Field):
choices = [(False, _(u'No')), (True, _(u'Yes'))]
def __init__(self, label=None, validators=None, choices=None, **kwargs):
super(BooleanSelectField, self).__init__(label, validators, **kwargs)
if choices:
self.choices = choices
def iter_choices(self):
return [('true' if choice[0] else 'false',
choice[1],
self.data == choice[0])
for choice in self.choices]
def process_data(self, value):
self.data = bool(value) if value is not None else None
def _to_python(self, value):
return value.lower() in ['yes', 'true']
def process_formdata(self, valuelist):
if not valuelist:
self.data = None
else:
self.data = self._to_python(valuelist[0])
def _value(self):
if not self.raw_data:
return None
else:
return 'true' if self._to_python(self.raw_data[0]) else 'false'
class DatePartField(wtforms.DateField):
"""Custom date field which uses separate fields (with identical name)
for day, month and year.
"""
day = None
month = None
year = None
def process_data(self, value):
if not isinstance(value, datetime.date):
return
self.data = value
self.day = value.day
self.month = value.month
self.year = value.year
def process(self, formdata, data=_unset_value):
self.data = None
self.process_errors = []
if data is _unset_value:
try:
data = self.default()
except TypeError:
data = self.default
self.process_data(data)
self.raw_data = {}
if formdata:
for part in ['day', 'month', 'year']:
name = '%s.%s' % (self.name, part)
raw_value = formdata.getlist(name)
if not (raw_value and raw_value[0]):
continue
try:
self.raw_data[part] = raw_value[0]
setattr(self, part, int(raw_value[0]))
except ValueError:
self.process_errors.append(_(u'Invalid date.'))
if self.raw_data: # Trick the Optional validator
self.raw_data[0] = 'dummy'
if not self.process_errors:
if bool(self.day) ^ bool(self.year):
self.process_errors.append(_(u'Please enter both day and year.'))
elif self.year and self.month and self.day:
try:
self.data = datetime.date(self.year, self.month, self.day)
except (TypeError, ValueError) as e:
self.process_errors.append(_(u'Invalid date.'))
return
for filter in self.filters:
try:
self.data = filter(self.data)
except ValueError, e:
self.process_errors.append(e.args[0])
class Required(wtforms.validators.Required):
"""Enhanced version of Required which knows how to deal with file fields.
"""
message = _(u'This field is required.')
def __init__(self, message=None):
if message is not None:
self.message = message
def __call__(self, form, field):
if isinstance(field.data, cgi.FieldStorage):
return
elif isinstance(field.data, bool):
return
super(Required, self).__call__(form, field)
class NumberRange(object):
"""
Validates that a number is of a minimum and/or maximum value, inclusive.
This will work with any comparable number type, such as floats and
decimals, not just integers.
:param min:
The minimum required value of the number. If not provided, minimum
value will not be checked.
:param max:
The maximum value of the number. If not provided, maximum value
will not be checked.
:param message:
Error message to raise in case of a validation error. Can be
interpolated using `%(min)s` and `%(max)s` if desired. Useful defaults
are provided depending on the existence of min and max.
"""
msg_between = _(u'Please enter a number between ${min} and ${max}.')
msg_less_than = _(u'Please enter a number below or equal to ${min}.')
msg_greater_than = _(u'Please enter a number above or equal to ${min}.')
def __init__(self, min=None, max=None,
msg_between=None, msg_less_than=None, msg_greater_than=None):
self.min = min
self.max = max
if msg_between:
self.msg_between = msg_between
if msg_less_than:
self.msg_less_than = msg_less_than
if msg_greater_than:
self.msg_greater_than = msg_greater_than
def __call__(self, form, field):
data = field.data
if data is None or (self.min is not None and data < self.min) or \
(self.max is not None and data > self.max):
if self.max is None:
message = self.msg_less_than % {'min': self.min}
elif self.min is None:
message = self.msg_greater_than % {'max': self.max}
else:
message = self.msg_between % \
{'min': self.min, 'max': self.max}
message = field.gettext(message)
raise ValidationError(message)
class BaseValidator(object):
"""Base class for custom WTForms validators.
"""
def __init__(self, message=None):
if message is not None:
self.message = message
class IsImage(BaseValidator):
"""Check if uploaded file is valid image.
"""
message = _(u'Please upload a valid image. Supported image formats '
u'are jpeg, png and gif.')
def validate_image(self, data):
try:
image = PIL.Image.open(data)
image.load() # broken JPEG
data.seek(0)
image = PIL.Image.open(data) # verify needs fresh image
image.verify() # broken PNG
if image.format not in ['GIF', 'PNG', 'JPEG']:
return False
except (IOError, OverflowError):
return False
finally:
if hasattr(data, 'seek'):
data.seek(0)
return True
def __call__(self, form, field):
if not isinstance(field.data, cgi.FieldStorage):
return
if not self.validate_image(field.data.file):
raise wtforms.ValidationError(self.message)
class URL(BaseValidator):
message = _(u'Please enter a valid URL.')
scheme_message = _(u'Please enter a valid URL.')
def __init__(self, allowed_schemes=None,
message=None, scheme_message=None):
super(URL, self).__init__(message)
self.allowed_schemes = allowed_schemes
if scheme_message is not None:
self.scheme_message = scheme_message
def __call__(self, form, field):
url = urlparse.urlparse(field.data)
if not url.netloc:
raise ValidationError(self.message)
if self.allowed_schemes and url.scheme not in self.allowed_schemes:
raise ValidationError(self.scheme_message)
class Email(wtforms.validators.Email):
# Customised validator which has a different default message.
_message = _(u'Please enter a valid email address.')
def __init__(self, *a, **kw):
super(Email, self).__init__(*a, **kw)
if self.message is None:
self.message = self._message
class PyramidTranslations(object):
"""An WTForms translations handler which uses the Pyramid
:py:class:`Localizer <pyramid.i18n.Localizer>`.
"""
def __init__(self, request):
self.localizer = get_localizer(request)
self.gettext = self.localizer.translate
self.ngettext = self.localizer.pluralize
class Form(SecureForm):
"""Base form class supporting CSRF and translations.
"""
def __init__(self, *a, **kw):
self.request = kw.pop('request')
self._translations = PyramidTranslations(self.request)
SecureForm.__init__(self, *a, **kw)
def _get_translations(self):
return self._translations
def generate_csrf_token(self, csrf_context):
return self.request.session.get_csrf_token()
def validate_csrf_token(self, field):
if field.data != field.current_token:
raise ValueError('Invalid CSRF')
class BasicFormView(object):
"""Abstract base class for forms using a `WTForms
<http://wtforms.simplecodes.com>`_ form.
Derived classes must replace the :py:attr:`form_class` class
variable and implement their own :py:meth:`do_post` method.
"""
#: form class to use.
form_class = None
#: Load form defaults from the current context
defaults_from_context = True
def __init__(self, context, request):
self.context = context
self.request = request
if not self.defaults_from_context:
context = None
if request.method == 'POST':
self.form = self.form_class(request.POST, context,
request=request, **self.default_data())
else:
self.form = self.form_class(None, context,
request=request,
**self.default_data())
def default_data(self):
"""Utility method to return default values for the form.
This is only needed if the default values can not be retrieved
as attributes on the current context.
"""
return {}
def do_post(self):
"""Perform all POST processing.
Implementations of this method must return None of processing failed
for some reason and the form must be shown again. If processing
succeeded a response object must be returned (commonly a
:py:class:`HTTPFound <pyramid.httpexceptions.HTTPFound>` instance).
"""
raise NotImplementedError()
def template_data(self):
"""Return extra data that should be exposed to templates.
"""
return {}
def __call__(self):
if self.request.method == 'POST' and self.form.validate():
response = self.do_post()
if response is not None:
return response
result = {'form': self.form}
result.update(self.template_data())
return result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment