Merge pull request #3 from SmileyJames/master

Increased Field and Validator support.
This commit is contained in:
Stephen J. Fuhry
2015-03-05 08:33:29 -05:00
12 changed files with 2537 additions and 110 deletions
+5 -24
View File
@@ -1,4 +1,4 @@
# WTForms-ParsleyJS
# WTForms-ParsleyJS 2.0
## What is this?
@@ -16,32 +16,13 @@ This library will generate the necesssary HTML attributes from your WTForms vali
## Installation
You can install from [pypi](https://pypi.python.org/pypi/WTForms-ParsleyJS) using
You can install from [pypi](https://pypi.python.org/pypi/WTForms-ParsleyJS) using
`pip install wtforms-parsleyjs`
## What is supported?
The following WTForms validators are supported:
* E-Mail Address
* Matching values
* IP4 Address
* Length of string
* Required field
* Regexp (see limitations)
* URL
* `AnyOf`
The `NoneOf` validator is not supported because this functionality is not supported by ParsleyJS.
The following WTForms widgets are supported:
* TextInput
* Select
* CheckboxInput
Radio Buttons are not supported.
All validators documented by WTForms are supported, all documented fields are also supported, with the exception of SubmitField. The reason SubmitField is not included is because I see no use case to validate Submit client side.
## How to use it?
@@ -67,9 +48,9 @@ WTForms-ParsleyJS has been developed and run solely on Python 2.7 - but it may w
## Dependencies
Of course ParsleyJS and WTForms are required. ParsleyJS in turn requires jQuery.
Of course ParsleyJS and WTForms are required. ParsleyJS in turn requires jQuery.
The `AnyOf` validator requires parsleys extra validators which are distributed in a seperate file.
If you wish to use AnyOf validator, NoneOf validator, DateField or DateTimeField then you need to include their matching ParsleyJS plugins, found in the scripts folder.
The sample uses the [Flask web framework](http://flask.pocoo.org/docs/) and [Twitter Bootstrap](http://twitter.github.io/bootstrap/). Because the sample should run out of the box on Heroku, ParsleyJS is included as a git submodule.
+5
View File
@@ -0,0 +1,5 @@
$(document).ready(function() {
window.ParsleyValidator.addValidator('anyof', function (value, array) {
return array.indexOf(value) >= 0;
}, 32).addMessage('en', 'anyof', 'The value you have given is not a listed option.')
});
+32
View File
@@ -0,0 +1,32 @@
$(document).ready(function() {
window.ParsleyValidator.addValidator('datefield', function (str, format) {
/**
* Modified version of micro-strptime.js.
* https://github.com/cho45/micro-strptime.js
*/
if (!format) throw Error("Missing format");
fds = {
'%': '%',
'A': '[a-z]+',
'B': '[a-z]+',
'Y': '[0-9]{4}',
'm': '[0-9]{1,2}',
'd': '[0-9]{1,2}',
'H': '[0-9]{1,2}',
'M': '[0-9]{1,2}',
'S': '[0-9]{1,2}',
's': '[0-9]+',
'Z': 'UTC|Z|[+-][0-9][0-9]:?[0-9][0-9]',
'I': '[0-9]{1,2}',
'p': 'AM|PM'
};
// Create a regular expression from the format string, that matches a string of that format.
var re = new RegExp(format.replace(/%(?:([a-zA-Z%])|('[^']+')|("[^"]+"))/g, function (_, a, b, c) {
var fd = a || b || c;
var d = fds[fd];
if (!d) throw Error("Unknown format descripter: " + fd);
return '(' + d + ')';
}), 'i');
return re.test(str);
}, 32).addMessage('en', 'datefield', 'The input needs to be in the correct date format.')
});
+5
View File
@@ -0,0 +1,5 @@
$(document).ready(function() {
window.ParsleyValidator.addValidator('noneof', function (value, array) {
return array.indexOf(value) === -1;
}, 32).addMessage('en', 'noneof', 'You have entered a value which is not allowed.')
});
+76 -28
View File
@@ -2,25 +2,28 @@ __author__ = 'Johannes Gehrs (jgehrs@gmail.com)'
import re
import copy
import json
from wtforms.validators import Length, NumberRange, Email, EqualTo, IPAddress, \
Regexp, URL, AnyOf, Optional, InputRequired
Regexp, URL, AnyOf, Optional, InputRequired, MacAddress, UUID, NoneOf
try:
from wtforms.validators import DataRequired
except ImportError:
# wtforms < 2.x
from wtforms.validators import Required as DataRequired
from wtforms import StringField
from wtforms.widgets import TextInput as _TextInput, PasswordInput as _PasswordInput, \
CheckboxInput as _CheckboxInput, Select as _Select, TextArea as _TextArea, \
ListWidget as _ListWidget, HiddenInput as _HiddenInput, RadioInput as _RadioInput, \
Input
FileInput as _FileInput, Input
from wtforms.fields import StringField as _StringField, BooleanField as _BooleanField, \
DecimalField as _DecimalField, IntegerField as _IntegerField, \
FloatField as _FloatField, PasswordField as _PasswordField, \
SelectField as _SelectField, TextAreaField as _TextAreaField, \
RadioField as _RadioField
RadioField as _RadioField, DateField as _DateField, \
DateTimeField as _DateTimeField, FileField as _FileField, \
SelectMultipleField as _SelectMultipleField
def parsley_kwargs(field, kwargs):
@@ -39,6 +42,16 @@ def parsley_kwargs(field, kwargs):
one. Do check if the behaviour suits your needs.
"""
new_kwargs = copy.deepcopy(kwargs)
if isinstance(field, DateField) or isinstance(field, DateTimeField):
_date_kwargs(new_kwargs, field)
if isinstance(field, IntegerField):
_integer_kwargs(new_kwargs)
if isinstance(field, DecimalField) or isinstance(field, FloatField):
_number_kwargs(new_kwargs)
if not 'data_trigger' in new_kwargs:
_trigger_kwargs(new_kwargs)
for vali in field.validators:
if isinstance(vali, Email):
_email_kwargs(new_kwargs)
@@ -59,11 +72,15 @@ def parsley_kwargs(field, kwargs):
_url_kwargs(new_kwargs)
if isinstance(vali, AnyOf):
_anyof_kwargs(new_kwargs, vali)
if isinstance(vali, MacAddress):
_mac_address_kwargs(new_kwargs)
if isinstance(vali, UUID):
_uuid_kwargs(new_kwargs)
if isinstance(vali, NoneOf):
_none_of_kwargs(new_kwargs, vali)
if isinstance(vali, Optional):
pass
if not 'data_trigger' in new_kwargs:
_trigger_kwargs(new_kwargs)
if not 'parsley-error-message' in new_kwargs \
and not isinstance(vali, Optional) \
and vali.message is not None:
@@ -81,7 +98,7 @@ def _equal_to_kwargs(kwargs, vali):
def _ip_address_kwargs(kwargs):
# Regexp from http://stackoverflow.com/a/4460645
kwargs[u'data-parsley-regexp'] =\
kwargs[u'data-parsley-pattern'] =\
r'^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \
r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' \
@@ -115,38 +132,40 @@ def _regexp_kwargs(kwargs, vali):
regex_string = vali.regex.pattern
else:
regex_string = vali.regex
kwargs[u'data-parsley-regexp'] = regex_string
kwargs[u'data-parsley-pattern'] = regex_string
def _url_kwargs(kwargs):
kwargs[u'data-parsley-type'] = u'url'
def _string_seq_delimiter(vali, kwargs):
# We normally use a comma as the delimiter - looks clean and it's parsley's default.
# If the strings for which we check contain a comma, we cannot use it as a delimiter.
default_delimiter = u','
fallback_delimiter = u';;;'
delimiter = default_delimiter
for value in vali.values:
if value.find(',') != -1:
delimiter = fallback_delimiter
break
if delimiter != default_delimiter:
kwargs[u'data-parsley-inlist-delimiter'] = delimiter
return delimiter
def _anyof_kwargs(kwargs, vali):
delimiter = _string_seq_delimiter(vali, kwargs)
kwargs[u'data-parsley-inlist'] = delimiter.join(vali.values)
# The inlist validator is no longer available in Parsley 2.x, so a custom anyof validator is used.
kwargs[u'data-parsley-anyof'] = json.dumps(vali.values)
def _mac_address_kwargs(kwargs):
kwargs[u'data-parsley-pattern'] = '^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$'
def _uuid_kwargs(kwargs):
kwargs[u'data-parsley-pattern'] = '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$'
def _none_of_kwargs(kwargs, vali):
#data-parsley-noneof is a custom validator, it can be found in scripts/parsley-noneof.js
kwargs[u'data-parsley-noneof'] = json.dumps(vali.values)
def _trigger_kwargs(kwargs, trigger=u'change'):
kwargs[u'data-parsley-trigger'] = trigger
def _message_kwargs(kwargs, message):
kwargs[u'data-parsley-error-message'] = message
def _date_kwargs(kwargs, field):
kwargs[u'data-parsley-datefield'] = field.format
def _integer_kwargs(kwargs):
kwargs[u'data-parsley-type'] = "integer"
def _number_kwargs(kwargs):
kwargs[u'data-parsley-type'] = "number"
class ParsleyInputMixin(Input):
def __call__(self, field, **kwargs):
@@ -166,8 +185,10 @@ class HiddenInput(_HiddenInput, ParsleyInputMixin):
pass
class TextArea(_TextArea, ParsleyInputMixin):
pass
class TextArea(_TextArea):
def __call__(self, field, **kwargs):
kwargs = parsley_kwargs(field, kwargs)
return super(TextArea, self).__call__(field, **kwargs)
class CheckboxInput(_CheckboxInput, ParsleyInputMixin):
@@ -193,6 +214,12 @@ class ListWidget(_ListWidget):
return super(ListWidget, self).__call__(field, **kwargs)
class FileInput(_FileInput):
def __call__(self, field, **kwargs):
kwargs = parsley_kwargs(field, kwargs)
return super(FileInput, self).__call__(field, **kwargs)
class StringField(_StringField):
def __init__(self, *args, **kwargs):
super(StringField, self).__init__(widget=TextInput(), *args, **kwargs)
@@ -243,3 +270,24 @@ class TextAreaField(_TextAreaField):
class SelectField(_SelectField):
def __init__(self, *args, **kwargs):
super(SelectField, self).__init__(widget=Select(), *args, **kwargs)
class DateTimeField(_DateTimeField):
def __init__(self, *args, **kwargs):
super(DateTimeField, self).__init__(widget=TextInput(), *args, **kwargs)
class DateField(_DateField):
def __init__(self, *args, **kwargs):
super(DateField, self).__init__(widget=TextInput(), *args, **kwargs)
class FileField(_FileField):
def __init__(self, *args, **kwargs):
super(FileField, self).__init__(widget=FileInput(), *args, **kwargs)
class SelectMultipleField(_SelectMultipleField):
def __init__(self, *args, **kwargs):
super(SelectMultipleField, self).__init__(widget=Select(multiple=True), *args, **kwargs)
+281 -43
View File
@@ -2,7 +2,8 @@ __author__ = 'Johannes Gehrs (jgehrs@gmail.com)'
from flask import Flask, render_template, request
from wtforms import Form, validators
from wtformsparsleyjs import IntegerField, BooleanField, SelectField, StringField
import wtformsparsleyjs
import datetime
app = Flask(__name__)
@@ -15,46 +16,283 @@ def parsley_testform():
class ParsleyTestForm(Form):
email = StringField('E-Mail Address', [
validators.Email('Sorrry, not a valid email address.')
], default='test@example.com')
first_value = StringField('Some Value', default='Some value')
second_value = StringField('Should be identical', [
validators.EqualTo(message='Sorry, values do not match.',
fieldname='first_value')
], default='Some value')
ip_address = StringField('IP4 Address', [
validators.IPAddress(message='Sorry, not a valid IP4 Address.')
], default='127.0.0.1')
string_length = StringField('Length of String (5 to 10)', [
validators.Length(message='Length should be between 5 and 10 characters.',
min=5, max=10)
], default='Hello!')
number_range = IntegerField('Number Range (5 to 10)', [
validators.NumberRange(message='Range should be between 5 and 10.',
min=5, max=10)
], default=7)
email = wtformsparsleyjs.StringField(
label = 'E-Mail Address',
validators = [
validators.Email(
message = 'Sorrry, not a valid email address.'
)
],
default='test@example.com'
)
required_text = StringField('Required Field', [
validators.DataRequired(message='Sorry, this is a required field.')
], default='Mandatory text')
required_select = SelectField('Required Select', [
validators.DataRequired(
message='Sorry, you have to make a choice.')
], choices=[('', 'Please select an option'), ('cpp', 'C++'), ('py', 'Python'),
('text', 'Plain Text')
], default='py')
required_checkbox = BooleanField('Required Checkbox', [
validators.DataRequired(message='Sorry, you need to accept this.')
], default=True)
regexp = StringField('Regex-Matched Hex Color-Code', [
validators.Regexp(message='Not a proper color code, sorry.',
regex=r'^#[A-Fa-f0-9]{6}$')
], default='#7D384F')
url = StringField('URL Field', [
validators.URL(message='Sorry, this is not a valid URL,')
], default='http://example.com/parsley')
anyof = StringField('Car, Bike or Plane?', [
validators.AnyOf(message='Sorry, you can only choose from car, bike and plane',
values=['car', 'bike', 'plane'])
], default='car')
ip_address = wtformsparsleyjs.StringField(
label = 'IP4 Address',
validators = [
validators.IPAddress(
message = 'Sorry, not a valid IP4 Address.'
)
],
default='127.0.0.1'
)
uuid = wtformsparsleyjs.StringField(
label = 'UUID',
validators = [
validators.UUID(
message = 'Sorry, not a valid UUID.'
)
],
default='863b5570-ee85-4099-ba1d-33018282cd00'
)
mac_address = wtformsparsleyjs.StringField(
label = 'Mac Address',
validators = [
validators.MacAddress(
message = 'Sorry, not a valid mac address.'
)
],
default = '10:B0:46:8C:80:48'
)
string_length = wtformsparsleyjs.StringField(
label = 'Length of String (5 to 10)',
validators = [
validators.Length(
message = 'Length should be between 5 and 10 characters.',
min = 5,
max = 10
)
],
default = 'Hello!'
)
number_range = wtformsparsleyjs.IntegerField(
label = 'Number Range (5 to 10)',
validators = [
validators.NumberRange(
message = 'Range should be between 5 and 10.',
min = 5,
max = 10
)
],
default = 7
)
required_text = wtformsparsleyjs.StringField(
label = 'Required Field',
validators = [
validators.DataRequired(
message = 'Sorry, this is a required field.'
)
],
default = 'Mandatory text'
)
required_select = wtformsparsleyjs.SelectField(
label = 'Required Select',
validators = [
validators.DataRequired(
message = 'Sorry, you have to make a choice.'
)
],
choices=[
('', 'Please select an option'),
('cpp', 'C++'),
('py', 'Python'),
('text', 'Plain Text')
],
default = 'py'
)
required_checkbox = wtformsparsleyjs.BooleanField(
label = 'Required Checkbox',
validators = [
validators.DataRequired(
message = 'Sorry, you need to accept this.'
)
],
default = True
)
regexp = wtformsparsleyjs.StringField(
label = 'Regex-Matched Hex Color-Code',
validators = [
validators.Regexp(
message = 'Not a proper color code, sorry.',
regex = r'^#[A-Fa-f0-9]{6}$'
)
],
default = '#7D384F'
)
url = wtformsparsleyjs.StringField(
label = 'URL Field',
validators = [
validators.URL(
message = 'Sorry, this is not a valid URL,'
)
],
default = 'http://example.com/parsley'
)
anyof = wtformsparsleyjs.StringField(
'Car, Bike or Plane?',
validators = [
validators.AnyOf(
message = 'Sorry, you can only choose from car, bike and plane',
values = ['car', 'bike', 'plane']
)
],
default = 'car'
)
date_of_birth = wtformsparsleyjs.DateField(
label = "Date of birth in the format DD-MM-YYYY",
format = "%d-%m-%Y",
validators = [
validators.InputRequired(
message = "Sorry this input is required."
)
],
default = datetime.datetime.today().date()
)
date_time = wtformsparsleyjs.DateTimeField(
label = "Date and time in the format DD/MM/YYYY HH:MM",
format = "%d/%m/%Y %H:%M",
validators = [
validators.InputRequired(
message = "Sorry this input is required."
)
],
default = datetime.datetime.today()
)
length = wtformsparsleyjs.DecimalField(
label = "Exact length, as a decimal",
validators = [
validators.InputRequired(
message = "Sorry this input is required."
)
],
default = 4.20
)
txt_file = wtformsparsleyjs.FileField(
label = "Optional file field, of .txt format",
validators = [
validators.Regexp(
r"^.+\.txt$",
message="Must be a *.txt file"
),
validators.Optional()
]
)
float_field = wtformsparsleyjs.FloatField(
label = "A float value",
validators = [
validators.InputRequired(
message = "Sorry this input is required."
)
],
default = 4.20
)
best_thing_ever = wtformsparsleyjs.RadioField(
label = "Is this the best thing ever?",
choices = [
("y", "Yes"),
("n", "No")
],
validators = [
validators.InputRequired(
message = "We need and answer please."
)
],
default = "y"
)
colour = wtformsparsleyjs.SelectField(
label = "Select your favourite colour.",
choices = [
("red", "Red"),
("blue", "Blue"),
("green", "Green")
],
validators = [
validators.InputRequired(
message = "Sorry this input is required."
)
]
)
hobbies = wtformsparsleyjs.SelectMultipleField(
label = "Select your hobbies: ",
choices=[
("cooking", "Cooking"),
("coding", "Coding"),
("reading", "Reading"),
("fishing", "Fishing"),
("sewing", "Sewing")
],
validators = [
validators.Optional()
]
)
name = wtformsparsleyjs.StringField(
label = "Whom would you have me welease?",
validators = [
validators.NoneOf(
["Roger", "Roderick", "Woger", "Woderick"],
message = "Ah. We have no Woger and no Woderick"
)
],
default = "Brian"
)
hidden = wtformsparsleyjs.HiddenField(
label = "Hidden value",
validators = [
validators.NumberRange(
min = 5,
message = "Must be greater than 5"
)
],
default = 6
)
# No default values for passwords, becuase the aren't rendered as a safety
# feature.
secret = wtformsparsleyjs.PasswordField(
label = "Enter your secret:",
validators = [
validators.InputRequired(
message = "Sorry this input is required."
)
],
)
same_secret = wtformsparsleyjs.PasswordField(
label = "Enter your secret again: ",
validators = [
validators.EqualTo(
fieldname = "secret",
message = "Secrets do not match."
)
],
)
life_story = wtformsparsleyjs.TextAreaField(
label = "Tell us your life story...",
validators = [
validators.Length(
min = 50,
message = "C'mon that's not long enough to be a life story"
)
],
default = "This is my life story, it has to be at least 50 characters."
)
@@ -0,0 +1,5 @@
$(document).ready(function() {
window.ParsleyValidator.addValidator('anyof', function (value, array) {
return array.indexOf(value) >= 0;
}, 32).addMessage('en', 'anyof', 'The value you have given is not a listed option.')
});
@@ -0,0 +1,32 @@
$(document).ready(function() {
window.ParsleyValidator.addValidator('datefield', function (str, format) {
/**
* Modified version of micro-strptime.js.
* https://github.com/cho45/micro-strptime.js
*/
if (!format) throw Error("Missing format");
fds = {
'%': '%',
'A': '[a-z]+',
'B': '[a-z]+',
'Y': '[0-9]{4}',
'm': '[0-9]{1,2}',
'd': '[0-9]{1,2}',
'H': '[0-9]{1,2}',
'M': '[0-9]{1,2}',
'S': '[0-9]{1,2}',
's': '[0-9]+',
'Z': 'UTC|Z|[+-][0-9][0-9]:?[0-9][0-9]',
'I': '[0-9]{1,2}',
'p': 'AM|PM'
};
// Create a regular expression from the format string, that matches a string of that format.
var re = new RegExp(format.replace(/%(?:([a-zA-Z%])|('[^']+')|("[^"]+"))/g, function (_, a, b, c) {
var fd = a || b || c;
var d = fds[fd];
if (!d) throw Error("Unknown format descripter: " + fd);
return '(' + d + ')';
}), 'i');
return re.test(str);
}, 32).addMessage('en', 'datefield', 'The input needs to be in the correct date format.')
});
@@ -0,0 +1,5 @@
$(document).ready(function() {
window.ParsleyValidator.addValidator('noneof', function (value, array) {
return array.indexOf(value) === -1;
}, 32).addMessage('en', 'noneof', 'You have entered a value which is not allowed.')
});
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -45,8 +45,10 @@
{% block scripts %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename='scripts/parsleyjs/parsley.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/parsleyjs/parsley.extend.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/parsley.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/parsley-noneof.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/parsley-anyof.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/parsley-datefield.js') }}"></script>
<script>$('form').parsley({ successClass: 'success', errorClass: 'error',
errors: {
classHandler: function (element) {
@@ -5,19 +5,10 @@
{% from "_formhelpers.html" import render_field, render_checkbox %}
<form method=post action="{{ url_for('parsley_testform') }}">
<fieldset>
{{ render_field(form.email) }}
{{ render_field(form.first_value) }}
{{ render_field(form.second_value) }}
{{ render_field(form.ip_address) }}
{{ render_field(form.string_length) }}
{{ render_field(form.number_range) }}
{{ render_field(form.required_text) }}
{{ render_field(form.required_select) }}
{{ render_field(form.required_checkbox) }}
{{ render_field(form.regexp) }}
{{ render_field(form.url) }}
{{ render_field(form.anyof) }}
{% for field in form %}
{{ render_field(field) }}
{% endfor %}
<p><input type=submit value=Submit>
</fieldset>
</form>
{% endblock content %}
{% endblock content %}