|
29
|
1 |
import re |
|
|
2 |
import urlparse |
|
|
3 |
|
|
|
4 |
from django.core.exceptions import ValidationError |
|
|
5 |
from django.utils.translation import ugettext_lazy as _ |
|
|
6 |
from django.utils.encoding import smart_unicode |
|
|
7 |
|
|
|
8 |
# These values, if given to validate(), will trigger the self.required check. |
|
|
9 |
EMPTY_VALUES = (None, '', [], (), {}) |
|
|
10 |
|
|
|
11 |
try: |
|
|
12 |
from django.conf import settings |
|
|
13 |
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT |
|
|
14 |
except ImportError: |
|
|
15 |
# It's OK if Django settings aren't configured. |
|
|
16 |
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' |
|
|
17 |
|
|
|
18 |
class RegexValidator(object): |
|
|
19 |
regex = '' |
|
|
20 |
message = _(u'Enter a valid value.') |
|
|
21 |
code = 'invalid' |
|
|
22 |
|
|
|
23 |
def __init__(self, regex=None, message=None, code=None): |
|
|
24 |
if regex is not None: |
|
|
25 |
self.regex = regex |
|
|
26 |
if message is not None: |
|
|
27 |
self.message = message |
|
|
28 |
if code is not None: |
|
|
29 |
self.code = code |
|
|
30 |
|
|
|
31 |
if isinstance(self.regex, basestring): |
|
|
32 |
self.regex = re.compile(regex) |
|
|
33 |
|
|
|
34 |
def __call__(self, value): |
|
|
35 |
""" |
|
|
36 |
Validates that the input matches the regular expression. |
|
|
37 |
""" |
|
|
38 |
if not self.regex.search(smart_unicode(value)): |
|
|
39 |
raise ValidationError(self.message, code=self.code) |
|
|
40 |
|
|
|
41 |
class URLValidator(RegexValidator): |
|
|
42 |
regex = re.compile( |
|
|
43 |
r'^https?://' # http:// or https:// |
|
|
44 |
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... |
|
|
45 |
r'localhost|' #localhost... |
|
|
46 |
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip |
|
|
47 |
r'(?::\d+)?' # optional port |
|
|
48 |
r'(?:/?|[/?]\S+)$', re.IGNORECASE) |
|
|
49 |
|
|
|
50 |
def __init__(self, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT): |
|
|
51 |
super(URLValidator, self).__init__() |
|
|
52 |
self.verify_exists = verify_exists |
|
|
53 |
self.user_agent = validator_user_agent |
|
|
54 |
|
|
|
55 |
def __call__(self, value): |
|
|
56 |
try: |
|
|
57 |
super(URLValidator, self).__call__(value) |
|
|
58 |
except ValidationError, e: |
|
|
59 |
# Trivial case failed. Try for possible IDN domain |
|
|
60 |
if value: |
|
|
61 |
value = smart_unicode(value) |
|
|
62 |
scheme, netloc, path, query, fragment = urlparse.urlsplit(value) |
|
|
63 |
try: |
|
|
64 |
netloc = netloc.encode('idna') # IDN -> ACE |
|
|
65 |
except UnicodeError: # invalid domain part |
|
|
66 |
raise e |
|
|
67 |
url = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) |
|
|
68 |
super(URLValidator, self).__call__(url) |
|
|
69 |
else: |
|
|
70 |
raise |
|
|
71 |
else: |
|
|
72 |
url = value |
|
|
73 |
|
|
|
74 |
if self.verify_exists: |
|
|
75 |
import urllib2 |
|
|
76 |
headers = { |
|
|
77 |
"Accept": "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", |
|
|
78 |
"Accept-Language": "en-us,en;q=0.5", |
|
|
79 |
"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", |
|
|
80 |
"Connection": "close", |
|
|
81 |
"User-Agent": self.user_agent, |
|
|
82 |
} |
|
|
83 |
try: |
|
|
84 |
req = urllib2.Request(url, None, headers) |
|
|
85 |
u = urllib2.urlopen(req) |
|
|
86 |
except ValueError: |
|
|
87 |
raise ValidationError(_(u'Enter a valid URL.'), code='invalid') |
|
|
88 |
except: # urllib2.URLError, httplib.InvalidURL, etc. |
|
|
89 |
raise ValidationError(_(u'This URL appears to be a broken link.'), code='invalid_link') |
|
|
90 |
|
|
|
91 |
|
|
|
92 |
def validate_integer(value): |
|
|
93 |
try: |
|
|
94 |
int(value) |
|
|
95 |
except (ValueError, TypeError), e: |
|
|
96 |
raise ValidationError('') |
|
|
97 |
|
|
|
98 |
class EmailValidator(RegexValidator): |
|
|
99 |
|
|
|
100 |
def __call__(self, value): |
|
|
101 |
try: |
|
|
102 |
super(EmailValidator, self).__call__(value) |
|
|
103 |
except ValidationError, e: |
|
|
104 |
# Trivial case failed. Try for possible IDN domain-part |
|
|
105 |
if value and u'@' in value: |
|
|
106 |
parts = value.split(u'@') |
|
|
107 |
domain_part = parts[-1] |
|
|
108 |
try: |
|
|
109 |
parts[-1] = parts[-1].encode('idna') |
|
|
110 |
except UnicodeError: |
|
|
111 |
raise e |
|
|
112 |
super(EmailValidator, self).__call__(u'@'.join(parts)) |
|
|
113 |
else: |
|
|
114 |
raise |
|
|
115 |
|
|
|
116 |
email_re = re.compile( |
|
|
117 |
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom |
|
|
118 |
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string |
|
|
119 |
r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain |
|
|
120 |
validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid') |
|
|
121 |
|
|
|
122 |
slug_re = re.compile(r'^[-\w]+$') |
|
|
123 |
validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid') |
|
|
124 |
|
|
|
125 |
ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') |
|
|
126 |
validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid') |
|
|
127 |
|
|
|
128 |
comma_separated_int_list_re = re.compile('^[\d,]+$') |
|
|
129 |
validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid') |
|
|
130 |
|
|
|
131 |
|
|
|
132 |
class BaseValidator(object): |
|
|
133 |
compare = lambda self, a, b: a is not b |
|
|
134 |
clean = lambda self, x: x |
|
|
135 |
message = _(u'Ensure this value is %(limit_value)s (it is %(show_value)s).') |
|
|
136 |
code = 'limit_value' |
|
|
137 |
|
|
|
138 |
def __init__(self, limit_value): |
|
|
139 |
self.limit_value = limit_value |
|
|
140 |
|
|
|
141 |
def __call__(self, value): |
|
|
142 |
cleaned = self.clean(value) |
|
|
143 |
params = {'limit_value': self.limit_value, 'show_value': cleaned} |
|
|
144 |
if self.compare(cleaned, self.limit_value): |
|
|
145 |
raise ValidationError( |
|
|
146 |
self.message % params, |
|
|
147 |
code=self.code, |
|
|
148 |
params=params, |
|
|
149 |
) |
|
|
150 |
|
|
|
151 |
class MaxValueValidator(BaseValidator): |
|
|
152 |
compare = lambda self, a, b: a > b |
|
|
153 |
message = _(u'Ensure this value is less than or equal to %(limit_value)s.') |
|
|
154 |
code = 'max_value' |
|
|
155 |
|
|
|
156 |
class MinValueValidator(BaseValidator): |
|
|
157 |
compare = lambda self, a, b: a < b |
|
|
158 |
message = _(u'Ensure this value is greater than or equal to %(limit_value)s.') |
|
|
159 |
code = 'min_value' |
|
|
160 |
|
|
|
161 |
class MinLengthValidator(BaseValidator): |
|
|
162 |
compare = lambda self, a, b: a < b |
|
|
163 |
clean = lambda self, x: len(x) |
|
|
164 |
message = _(u'Ensure this value has at least %(limit_value)d characters (it has %(show_value)d).') |
|
|
165 |
code = 'min_length' |
|
|
166 |
|
|
|
167 |
class MaxLengthValidator(BaseValidator): |
|
|
168 |
compare = lambda self, a, b: a > b |
|
|
169 |
clean = lambda self, x: len(x) |
|
|
170 |
message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).') |
|
|
171 |
code = 'max_length' |
|
|
172 |
|