|
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 |