1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE
10 """
11
12 import os
13 import re
14 import datetime
15 import time
16 import cgi
17 import urllib
18 import struct
19 import decimal
20 import unicodedata
21 from cStringIO import StringIO
22 from utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
23 from dal import FieldVirtual, FieldMethod
24
25 JSONErrors = (NameError, TypeError, ValueError, AttributeError,
26 KeyError)
27 try:
28 import json as simplejson
29 except ImportError:
30 from gluon.contrib import simplejson
31 from gluon.contrib.simplejson.decoder import JSONDecodeError
32 JSONErrors += (JSONDecodeError,)
33
34 __all__ = [
35 'ANY_OF',
36 'CLEANUP',
37 'CRYPT',
38 'IS_ALPHANUMERIC',
39 'IS_DATE_IN_RANGE',
40 'IS_DATE',
41 'IS_DATETIME_IN_RANGE',
42 'IS_DATETIME',
43 'IS_DECIMAL_IN_RANGE',
44 'IS_EMAIL',
45 'IS_EMPTY_OR',
46 'IS_EXPR',
47 'IS_FLOAT_IN_RANGE',
48 'IS_IMAGE',
49 'IS_IN_DB',
50 'IS_IN_SET',
51 'IS_INT_IN_RANGE',
52 'IS_IPV4',
53 'IS_IPV6',
54 'IS_IPADDRESS',
55 'IS_LENGTH',
56 'IS_LIST_OF',
57 'IS_LOWER',
58 'IS_MATCH',
59 'IS_EQUAL_TO',
60 'IS_NOT_EMPTY',
61 'IS_NOT_IN_DB',
62 'IS_NULL_OR',
63 'IS_SLUG',
64 'IS_STRONG',
65 'IS_TIME',
66 'IS_UPLOAD_FILENAME',
67 'IS_UPPER',
68 'IS_URL',
69 'IS_JSON',
70 ]
71
72 try:
73 from globals import current
74 have_current = True
75 except ImportError:
76 have_current = False
80 if text is None:
81 return None
82 elif isinstance(text, (str, unicode)) and have_current:
83 if hasattr(current, 'T'):
84 return str(current.T(text))
85 return str(text)
86
89 return (str(x[1]).upper() > str(y[1]).upper() and 1) or -1
90
93 """
94 Root for all validators, mainly for documentation purposes.
95
96 Validators are classes used to validate input fields (including forms
97 generated from database tables).
98
99 Here is an example of using a validator with a FORM::
100
101 INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))
102
103 Here is an example of how to require a validator for a table field::
104
105 db.define_table('person', SQLField('name'))
106 db.person.name.requires=IS_NOT_EMPTY()
107
108 Validators are always assigned using the requires attribute of a field. A
109 field can have a single validator or multiple validators. Multiple
110 validators are made part of a list::
111
112 db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')]
113
114 Validators are called by the function accepts on a FORM or other HTML
115 helper object that contains a form. They are always called in the order in
116 which they are listed.
117
118 Built-in validators have constructors that take the optional argument error
119 message which allows you to change the default error message.
120 Here is an example of a validator on a database table::
121
122 db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this'))
123
124 where we have used the translation operator T to allow for
125 internationalization.
126
127 Notice that default error messages are not translated.
128 """
129
136
138 raise NotImplementedError
139 return (value, None)
140
143 """
144 example::
145
146 INPUT(_type='text', _name='name', requires=IS_MATCH('.+'))
147
148 the argument of IS_MATCH is a regular expression::
149
150 >>> IS_MATCH('.+')('hello')
151 ('hello', None)
152
153 >>> IS_MATCH('hell')('hello')
154 ('hello', None)
155
156 >>> IS_MATCH('hell.*', strict=False)('hello')
157 ('hello', None)
158
159 >>> IS_MATCH('hello')('shello')
160 ('shello', 'invalid expression')
161
162 >>> IS_MATCH('hello', search=True)('shello')
163 ('shello', None)
164
165 >>> IS_MATCH('hello', search=True, strict=False)('shellox')
166 ('shellox', None)
167
168 >>> IS_MATCH('.*hello.*', search=True, strict=False)('shellox')
169 ('shellox', None)
170
171 >>> IS_MATCH('.+')('')
172 ('', 'invalid expression')
173 """
174
175 - def __init__(self, expression, error_message='invalid expression',
176 strict=False, search=False, extract=False,
177 unicode=False):
178 if strict or not search:
179 if not expression.startswith('^'):
180 expression = '^(%s)' % expression
181 if strict:
182 if not expression.endswith('$'):
183 expression = '(%s)$' % expression
184 if unicode:
185 if not isinstance(expression,unicode):
186 expression = expression.decode('utf8')
187 self.regex = re.compile(expression,re.UNICODE)
188 else:
189 self.regex = re.compile(expression)
190 self.error_message = error_message
191 self.extract = extract
192 self.unicode = unicode
193
195 if self.unicode and not isinstance(value,unicode):
196 match = self.regex.search(str(value).decode('utf8'))
197 else:
198 match = self.regex.search(str(value))
199 if match is not None:
200 return (self.extract and match.group() or value, None)
201 return (value, translate(self.error_message))
202
205 """
206 example::
207
208 INPUT(_type='text', _name='password')
209 INPUT(_type='text', _name='password2',
210 requires=IS_EQUAL_TO(request.vars.password))
211
212 the argument of IS_EQUAL_TO is a string
213
214 >>> IS_EQUAL_TO('aaa')('aaa')
215 ('aaa', None)
216
217 >>> IS_EQUAL_TO('aaa')('aab')
218 ('aab', 'no match')
219 """
220
221 - def __init__(self, expression, error_message='no match'):
222 self.expression = expression
223 self.error_message = error_message
224
226 if value == self.expression:
227 return (value, None)
228 return (value, translate(self.error_message))
229
232 """
233 example::
234
235 INPUT(_type='text', _name='name',
236 requires=IS_EXPR('5 < int(value) < 10'))
237
238 the argument of IS_EXPR must be python condition::
239
240 >>> IS_EXPR('int(value) < 2')('1')
241 ('1', None)
242
243 >>> IS_EXPR('int(value) < 2')('2')
244 ('2', 'invalid expression')
245 """
246
247 - def __init__(self, expression, error_message='invalid expression', environment=None):
248 self.expression = expression
249 self.error_message = error_message
250 self.environment = environment or {}
251
253 if callable(self.expression):
254 return (value, self.expression(value))
255
256 self.environment.update(value=value)
257 exec '__ret__=' + self.expression in self.environment
258 if self.environment['__ret__']:
259 return (value, None)
260 return (value, translate(self.error_message))
261
264 """
265 Checks if length of field's value fits between given boundaries. Works
266 for both text and file inputs.
267
268 Arguments:
269
270 maxsize: maximum allowed length / size
271 minsize: minimum allowed length / size
272
273 Examples::
274
275 #Check if text string is shorter than 33 characters:
276 INPUT(_type='text', _name='name', requires=IS_LENGTH(32))
277
278 #Check if password string is longer than 5 characters:
279 INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
280
281 #Check if uploaded file has size between 1KB and 1MB:
282 INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))
283
284 >>> IS_LENGTH()('')
285 ('', None)
286 >>> IS_LENGTH()('1234567890')
287 ('1234567890', None)
288 >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long
289 ('1234567890', 'enter from 0 to 5 characters')
290 >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short
291 ('1234567890', 'enter from 20 to 50 characters')
292 """
293
294 - def __init__(self, maxsize=255, minsize=0,
295 error_message='enter from %(min)g to %(max)g characters'):
296 self.maxsize = maxsize
297 self.minsize = minsize
298 self.error_message = error_message
299
301 if value is None:
302 length = 0
303 if self.minsize <= length <= self.maxsize:
304 return (value, None)
305 elif isinstance(value, cgi.FieldStorage):
306 if value.file:
307 value.file.seek(0, os.SEEK_END)
308 length = value.file.tell()
309 value.file.seek(0, os.SEEK_SET)
310 elif hasattr(value, 'value'):
311 val = value.value
312 if val:
313 length = len(val)
314 else:
315 length = 0
316 if self.minsize <= length <= self.maxsize:
317 return (value, None)
318 elif isinstance(value, str):
319 try:
320 lvalue = len(value.decode('utf8'))
321 except:
322 lvalue = len(value)
323 if self.minsize <= lvalue <= self.maxsize:
324 return (value, None)
325 elif isinstance(value, unicode):
326 if self.minsize <= len(value) <= self.maxsize:
327 return (value.encode('utf8'), None)
328 elif isinstance(value, (tuple, list)):
329 if self.minsize <= len(value) <= self.maxsize:
330 return (value, None)
331 elif self.minsize <= len(str(value)) <= self.maxsize:
332 return (str(value), None)
333 return (value, translate(self.error_message)
334 % dict(min=self.minsize, max=self.maxsize))
335
337 """
338 example::
339 INPUT(_type='text', _name='name',
340 requires=IS_JSON(error_message="This is not a valid json input")
341
342 >>> IS_JSON()('{"a": 100}')
343 ({u'a': 100}, None)
344
345 >>> IS_JSON()('spam1234')
346 ('spam1234', 'invalid json')
347 """
348
349 - def __init__(self, error_message='invalid json'):
350 self.error_message = error_message
351
353 if value is None:
354 return None
355 try:
356 return (simplejson.loads(value), None)
357 except JSONErrors:
358 return (value, translate(self.error_message))
359
364
367 """
368 example::
369
370 INPUT(_type='text', _name='name',
371 requires=IS_IN_SET(['max', 'john'],zero=''))
372
373 the argument of IS_IN_SET must be a list or set
374
375 >>> IS_IN_SET(['max', 'john'])('max')
376 ('max', None)
377 >>> IS_IN_SET(['max', 'john'])('massimo')
378 ('massimo', 'value not allowed')
379 >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john'))
380 (('max', 'john'), None)
381 >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john'))
382 (('bill', 'john'), 'value not allowed')
383 >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way
384 ('id1', None)
385 >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
386 ('id1', None)
387 >>> import itertools
388 >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
389 ('1', None)
390 >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way
391 ('id1', None)
392 """
393
394 - def __init__(
395 self,
396 theset,
397 labels=None,
398 error_message='value not allowed',
399 multiple=False,
400 zero='',
401 sort=False,
402 ):
403 self.multiple = multiple
404 if isinstance(theset, dict):
405 self.theset = [str(item) for item in theset]
406 self.labels = theset.values()
407 elif theset and isinstance(theset, (tuple, list)) \
408 and isinstance(theset[0], (tuple, list)) and len(theset[0]) == 2:
409 self.theset = [str(item) for item, label in theset]
410 self.labels = [str(label) for item, label in theset]
411 else:
412 self.theset = [str(item) for item in theset]
413 self.labels = labels
414 self.error_message = error_message
415 self.zero = zero
416 self.sort = sort
417
419 if not self.labels:
420 items = [(k, k) for (i, k) in enumerate(self.theset)]
421 else:
422 items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
423 if self.sort:
424 items.sort(options_sorter)
425 if zero and not self.zero is None and not self.multiple:
426 items.insert(0, ('', self.zero))
427 return items
428
430 if self.multiple:
431
432 if not value:
433 values = []
434 elif isinstance(value, (tuple, list)):
435 values = value
436 else:
437 values = [value]
438 else:
439 values = [value]
440 thestrset = [str(x) for x in self.theset]
441 failures = [x for x in values if not str(x) in thestrset]
442 if failures and self.theset:
443 if self.multiple and (value is None or value == ''):
444 return ([], None)
445 return (value, translate(self.error_message))
446 if self.multiple:
447 if isinstance(self.multiple, (tuple, list)) and \
448 not self.multiple[0] <= len(values) < self.multiple[1]:
449 return (values, translate(self.error_message))
450 return (values, None)
451 return (value, None)
452
453
454 regex1 = re.compile('\w+\.\w+')
455 regex2 = re.compile('%\((?P<name>[^\)]+)\)s')
459 """
460 example::
461
462 INPUT(_type='text', _name='name',
463 requires=IS_IN_DB(db, db.mytable.myfield, zero=''))
464
465 used for reference fields, rendered as a dropbox
466 """
467
468 - def __init__(
469 self,
470 dbset,
471 field,
472 label=None,
473 error_message='value not in database',
474 orderby=None,
475 groupby=None,
476 distinct=None,
477 cache=None,
478 multiple=False,
479 zero='',
480 sort=False,
481 _and=None,
482 ):
483 from dal import Table
484 if isinstance(field, Table):
485 field = field._id
486
487 if hasattr(dbset, 'define_table'):
488 self.dbset = dbset()
489 else:
490 self.dbset = dbset
491 (ktable, kfield) = str(field).split('.')
492 if not label:
493 label = '%%(%s)s' % kfield
494 if isinstance(label, str):
495 if regex1.match(str(label)):
496 label = '%%(%s)s' % str(label).split('.')[-1]
497 ks = regex2.findall(label)
498 if not kfield in ks:
499 ks += [kfield]
500 fields = ks
501 else:
502 ks = [kfield]
503 fields = 'all'
504 self.fields = fields
505 self.label = label
506 self.ktable = ktable
507 self.kfield = kfield
508 self.ks = ks
509 self.error_message = error_message
510 self.theset = None
511 self.orderby = orderby
512 self.groupby = groupby
513 self.distinct = distinct
514 self.cache = cache
515 self.multiple = multiple
516 self.zero = zero
517 self.sort = sort
518 self._and = _and
519
521 if self._and:
522 self._and.record_id = id
523
525 table = self.dbset.db[self.ktable]
526 if self.fields == 'all':
527 fields = [f for f in table]
528 else:
529 fields = [table[k] for k in self.fields]
530 ignore = (FieldVirtual,FieldMethod)
531 fields = filter(lambda f:not isinstance(f,ignore), fields)
532 if self.dbset.db._dbname != 'gae':
533 orderby = self.orderby or reduce(lambda a, b: a | b, fields)
534 groupby = self.groupby
535 distinct = self.distinct
536 dd = dict(orderby=orderby, groupby=groupby,
537 distinct=distinct, cache=self.cache,
538 cacheable=True)
539 records = self.dbset(table).select(*fields, **dd)
540 else:
541 orderby = self.orderby or \
542 reduce(lambda a, b: a | b, (
543 f for f in fields if not f.name == 'id'))
544 dd = dict(orderby=orderby, cache=self.cache, cacheable=True)
545 records = self.dbset(table).select(table.ALL, **dd)
546 self.theset = [str(r[self.kfield]) for r in records]
547 if isinstance(self.label, str):
548 self.labels = [self.label % r for r in records]
549 else:
550 self.labels = [self.label(r) for r in records]
551
560
562 table = self.dbset.db[self.ktable]
563 field = table[self.kfield]
564 if self.multiple:
565 if self._and:
566 raise NotImplementedError
567 if isinstance(value, list):
568 values = value
569 elif value:
570 values = [value]
571 else:
572 values = []
573 if isinstance(self.multiple, (tuple, list)) and \
574 not self.multiple[0] <= len(values) < self.multiple[1]:
575 return (values, translate(self.error_message))
576 if self.theset:
577 if not [v for v in values if not v in self.theset]:
578 return (values, None)
579 else:
580 from dal import GoogleDatastoreAdapter
581
582 def count(values, s=self.dbset, f=field):
583 return s(f.belongs(map(int, values))).count()
584 if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter):
585 range_ids = range(0, len(values), 30)
586 total = sum(count(values[i:i + 30]) for i in range_ids)
587 if total == len(values):
588 return (values, None)
589 elif count(values) == len(values):
590 return (values, None)
591 elif self.theset:
592 if str(value) in self.theset:
593 if self._and:
594 return self._and(value)
595 else:
596 return (value, None)
597 else:
598 if self.dbset(field == value).count():
599 if self._and:
600 return self._and(value)
601 else:
602 return (value, None)
603 return (value, translate(self.error_message))
604
607 """
608 example::
609
610 INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table))
611
612 makes the field unique
613 """
614
615 - def __init__(
616 self,
617 dbset,
618 field,
619 error_message='value already in database or empty',
620 allowed_override=[],
621 ignore_common_filters=False,
622 ):
623
624 from dal import Table
625 if isinstance(field, Table):
626 field = field._id
627
628 if hasattr(dbset, 'define_table'):
629 self.dbset = dbset()
630 else:
631 self.dbset = dbset
632 self.field = field
633 self.error_message = error_message
634 self.record_id = 0
635 self.allowed_override = allowed_override
636 self.ignore_common_filters = ignore_common_filters
637
640
642 if isinstance(value,unicode):
643 value = value.encode('utf8')
644 else:
645 value = str(value)
646 if not value.strip():
647 return (value, translate(self.error_message))
648 if value in self.allowed_override:
649 return (value, None)
650 (tablename, fieldname) = str(self.field).split('.')
651 table = self.dbset.db[tablename]
652 field = table[fieldname]
653 subset = self.dbset(field == value,
654 ignore_common_filters=self.ignore_common_filters)
655 id = self.record_id
656 if isinstance(id, dict):
657 fields = [table[f] for f in id]
658 row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).first()
659 if row and any(str(row[f]) != str(id[f]) for f in id):
660 return (value, translate(self.error_message))
661 else:
662 row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
663 if row and str(row.id) != str(id):
664 return (value, translate(self.error_message))
665 return (value, None)
666
669 """
670 Determine that the argument is (or can be represented as) an int,
671 and that it falls within the specified range. The range is interpreted
672 in the Pythonic way, so the test is: min <= value < max.
673
674 The minimum and maximum limits can be None, meaning no lower or upper limit,
675 respectively.
676
677 example::
678
679 INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10))
680
681 >>> IS_INT_IN_RANGE(1,5)('4')
682 (4, None)
683 >>> IS_INT_IN_RANGE(1,5)(4)
684 (4, None)
685 >>> IS_INT_IN_RANGE(1,5)(1)
686 (1, None)
687 >>> IS_INT_IN_RANGE(1,5)(5)
688 (5, 'enter an integer between 1 and 4')
689 >>> IS_INT_IN_RANGE(1,5)(5)
690 (5, 'enter an integer between 1 and 4')
691 >>> IS_INT_IN_RANGE(1,5)(3.5)
692 (3, 'enter an integer between 1 and 4')
693 >>> IS_INT_IN_RANGE(None,5)('4')
694 (4, None)
695 >>> IS_INT_IN_RANGE(None,5)('6')
696 (6, 'enter an integer less than or equal to 4')
697 >>> IS_INT_IN_RANGE(1,None)('4')
698 (4, None)
699 >>> IS_INT_IN_RANGE(1,None)('0')
700 (0, 'enter an integer greater than or equal to 1')
701 >>> IS_INT_IN_RANGE()(6)
702 (6, None)
703 >>> IS_INT_IN_RANGE()('abc')
704 ('abc', 'enter an integer')
705 """
706
707 - def __init__(
708 self,
709 minimum=None,
710 maximum=None,
711 error_message=None,
712 ):
713 self.minimum = self.maximum = None
714 if minimum is None:
715 if maximum is None:
716 self.error_message = translate(
717 error_message or 'enter an integer')
718 else:
719 self.maximum = int(maximum)
720 if error_message is None:
721 error_message = \
722 'enter an integer less than or equal to %(max)g'
723 self.error_message = translate(
724 error_message) % dict(max=self.maximum - 1)
725 elif maximum is None:
726 self.minimum = int(minimum)
727 if error_message is None:
728 error_message = \
729 'enter an integer greater than or equal to %(min)g'
730 self.error_message = translate(
731 error_message) % dict(min=self.minimum)
732 else:
733 self.minimum = int(minimum)
734 self.maximum = int(maximum)
735 if error_message is None:
736 error_message = 'enter an integer between %(min)g and %(max)g'
737 self.error_message = translate(error_message) \
738 % dict(min=self.minimum, max=self.maximum - 1)
739
741 try:
742 fvalue = float(value)
743 value = int(value)
744 if value != fvalue:
745 return (value, self.error_message)
746 if self.minimum is None:
747 if self.maximum is None or value < self.maximum:
748 return (value, None)
749 elif self.maximum is None:
750 if value >= self.minimum:
751 return (value, None)
752 elif self.minimum <= value < self.maximum:
753 return (value, None)
754 except ValueError:
755 pass
756 return (value, self.error_message)
757
760 s = str(number)
761 if not '.' in s:
762 s += '.00'
763 else:
764 s += '0' * (2 - len(s.split('.')[1]))
765 return s
766
769 """
770 Determine that the argument is (or can be represented as) a float,
771 and that it falls within the specified inclusive range.
772 The comparison is made with native arithmetic.
773
774 The minimum and maximum limits can be None, meaning no lower or upper limit,
775 respectively.
776
777 example::
778
779 INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10))
780
781 >>> IS_FLOAT_IN_RANGE(1,5)('4')
782 (4.0, None)
783 >>> IS_FLOAT_IN_RANGE(1,5)(4)
784 (4.0, None)
785 >>> IS_FLOAT_IN_RANGE(1,5)(1)
786 (1.0, None)
787 >>> IS_FLOAT_IN_RANGE(1,5)(5.25)
788 (5.25, 'enter a number between 1 and 5')
789 >>> IS_FLOAT_IN_RANGE(1,5)(6.0)
790 (6.0, 'enter a number between 1 and 5')
791 >>> IS_FLOAT_IN_RANGE(1,5)(3.5)
792 (3.5, None)
793 >>> IS_FLOAT_IN_RANGE(1,None)(3.5)
794 (3.5, None)
795 >>> IS_FLOAT_IN_RANGE(None,5)(3.5)
796 (3.5, None)
797 >>> IS_FLOAT_IN_RANGE(1,None)(0.5)
798 (0.5, 'enter a number greater than or equal to 1')
799 >>> IS_FLOAT_IN_RANGE(None,5)(6.5)
800 (6.5, 'enter a number less than or equal to 5')
801 >>> IS_FLOAT_IN_RANGE()(6.5)
802 (6.5, None)
803 >>> IS_FLOAT_IN_RANGE()('abc')
804 ('abc', 'enter a number')
805 """
806
807 - def __init__(
808 self,
809 minimum=None,
810 maximum=None,
811 error_message=None,
812 dot='.'
813 ):
814 self.minimum = self.maximum = None
815 self.dot = dot
816 if minimum is None:
817 if maximum is None:
818 if error_message is None:
819 error_message = 'enter a number'
820 else:
821 self.maximum = float(maximum)
822 if error_message is None:
823 error_message = 'enter a number less than or equal to %(max)g'
824 elif maximum is None:
825 self.minimum = float(minimum)
826 if error_message is None:
827 error_message = 'enter a number greater than or equal to %(min)g'
828 else:
829 self.minimum = float(minimum)
830 self.maximum = float(maximum)
831 if error_message is None:
832 error_message = 'enter a number between %(min)g and %(max)g'
833 self.error_message = translate(error_message) \
834 % dict(min=self.minimum, max=self.maximum)
835
837 try:
838 if self.dot == '.':
839 fvalue = float(value)
840 else:
841 fvalue = float(str(value).replace(self.dot, '.'))
842 if self.minimum is None:
843 if self.maximum is None or fvalue <= self.maximum:
844 return (fvalue, None)
845 elif self.maximum is None:
846 if fvalue >= self.minimum:
847 return (fvalue, None)
848 elif self.minimum <= fvalue <= self.maximum:
849 return (fvalue, None)
850 except (ValueError, TypeError):
851 pass
852 return (value, self.error_message)
853
858
861 """
862 Determine that the argument is (or can be represented as) a Python Decimal,
863 and that it falls within the specified inclusive range.
864 The comparison is made with Python Decimal arithmetic.
865
866 The minimum and maximum limits can be None, meaning no lower or upper limit,
867 respectively.
868
869 example::
870
871 INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10))
872
873 >>> IS_DECIMAL_IN_RANGE(1,5)('4')
874 (Decimal('4'), None)
875 >>> IS_DECIMAL_IN_RANGE(1,5)(4)
876 (Decimal('4'), None)
877 >>> IS_DECIMAL_IN_RANGE(1,5)(1)
878 (Decimal('1'), None)
879 >>> IS_DECIMAL_IN_RANGE(1,5)(5.25)
880 (5.25, 'enter a number between 1 and 5')
881 >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25)
882 (Decimal('5.25'), None)
883 >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25')
884 (Decimal('5.25'), None)
885 >>> IS_DECIMAL_IN_RANGE(1,5)(6.0)
886 (6.0, 'enter a number between 1 and 5')
887 >>> IS_DECIMAL_IN_RANGE(1,5)(3.5)
888 (Decimal('3.5'), None)
889 >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5)
890 (Decimal('3.5'), None)
891 >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5)
892 (6.5, 'enter a number between 1.5 and 5.5')
893 >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5)
894 (Decimal('6.5'), None)
895 >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5)
896 (0.5, 'enter a number greater than or equal to 1.5')
897 >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5)
898 (Decimal('4.5'), None)
899 >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5)
900 (6.5, 'enter a number less than or equal to 5.5')
901 >>> IS_DECIMAL_IN_RANGE()(6.5)
902 (Decimal('6.5'), None)
903 >>> IS_DECIMAL_IN_RANGE(0,99)(123.123)
904 (123.123, 'enter a number between 0 and 99')
905 >>> IS_DECIMAL_IN_RANGE(0,99)('123.123')
906 ('123.123', 'enter a number between 0 and 99')
907 >>> IS_DECIMAL_IN_RANGE(0,99)('12.34')
908 (Decimal('12.34'), None)
909 >>> IS_DECIMAL_IN_RANGE()('abc')
910 ('abc', 'enter a decimal number')
911 """
912
913 - def __init__(
914 self,
915 minimum=None,
916 maximum=None,
917 error_message=None,
918 dot='.'
919 ):
920 self.minimum = self.maximum = None
921 self.dot = dot
922 if minimum is None:
923 if maximum is None:
924 if error_message is None:
925 error_message = 'enter a decimal number'
926 else:
927 self.maximum = decimal.Decimal(str(maximum))
928 if error_message is None:
929 error_message = 'enter a number less than or equal to %(max)g'
930 elif maximum is None:
931 self.minimum = decimal.Decimal(str(minimum))
932 if error_message is None:
933 error_message = 'enter a number greater than or equal to %(min)g'
934 else:
935 self.minimum = decimal.Decimal(str(minimum))
936 self.maximum = decimal.Decimal(str(maximum))
937 if error_message is None:
938 error_message = 'enter a number between %(min)g and %(max)g'
939 self.error_message = translate(error_message) \
940 % dict(min=self.minimum, max=self.maximum)
941
943 try:
944 if isinstance(value, decimal.Decimal):
945 v = value
946 else:
947 v = decimal.Decimal(str(value).replace(self.dot, '.'))
948 if self.minimum is None:
949 if self.maximum is None or v <= self.maximum:
950 return (v, None)
951 elif self.maximum is None:
952 if v >= self.minimum:
953 return (v, None)
954 elif self.minimum <= v <= self.maximum:
955 return (v, None)
956 except (ValueError, TypeError, decimal.InvalidOperation):
957 pass
958 return (value, self.error_message)
959
964
965
966 -def is_empty(value, empty_regex=None):
967 "test empty field"
968 if isinstance(value, (str, unicode)):
969 value = value.strip()
970 if empty_regex is not None and empty_regex.match(value):
971 value = ''
972 if value is None or value == '' or value == []:
973 return (value, True)
974 return (value, False)
975
978 """
979 example::
980
981 INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY())
982
983 >>> IS_NOT_EMPTY()(1)
984 (1, None)
985 >>> IS_NOT_EMPTY()(0)
986 (0, None)
987 >>> IS_NOT_EMPTY()('x')
988 ('x', None)
989 >>> IS_NOT_EMPTY()(' x ')
990 ('x', None)
991 >>> IS_NOT_EMPTY()(None)
992 (None, 'enter a value')
993 >>> IS_NOT_EMPTY()('')
994 ('', 'enter a value')
995 >>> IS_NOT_EMPTY()(' ')
996 ('', 'enter a value')
997 >>> IS_NOT_EMPTY()(' \\n\\t')
998 ('', 'enter a value')
999 >>> IS_NOT_EMPTY()([])
1000 ([], 'enter a value')
1001 >>> IS_NOT_EMPTY(empty_regex='def')('def')
1002 ('', 'enter a value')
1003 >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
1004 ('', 'enter a value')
1005 >>> IS_NOT_EMPTY(empty_regex='def')('abc')
1006 ('abc', None)
1007 """
1008
1009 - def __init__(self, error_message='enter a value', empty_regex=None):
1010 self.error_message = error_message
1011 if empty_regex is not None:
1012 self.empty_regex = re.compile(empty_regex)
1013 else:
1014 self.empty_regex = None
1015
1017 value, empty = is_empty(value, empty_regex=self.empty_regex)
1018 if empty:
1019 return (value, translate(self.error_message))
1020 return (value, None)
1021
1024 """
1025 example::
1026
1027 INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC())
1028
1029 >>> IS_ALPHANUMERIC()('1')
1030 ('1', None)
1031 >>> IS_ALPHANUMERIC()('')
1032 ('', None)
1033 >>> IS_ALPHANUMERIC()('A_a')
1034 ('A_a', None)
1035 >>> IS_ALPHANUMERIC()('!')
1036 ('!', 'enter only letters, numbers, and underscore')
1037 """
1038
1039 - def __init__(self, error_message='enter only letters, numbers, and underscore'):
1041
1044 """
1045 Checks if field's value is a valid email address. Can be set to disallow
1046 or force addresses from certain domain(s).
1047
1048 Email regex adapted from
1049 http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx,
1050 generally following the RFCs, except that we disallow quoted strings
1051 and permit underscores and leading numerics in subdomain labels
1052
1053 Arguments:
1054
1055 - banned: regex text for disallowed address domains
1056 - forced: regex text for required address domains
1057
1058 Both arguments can also be custom objects with a match(value) method.
1059
1060 Examples::
1061
1062 #Check for valid email address:
1063 INPUT(_type='text', _name='name',
1064 requires=IS_EMAIL())
1065
1066 #Check for valid email address that can't be from a .com domain:
1067 INPUT(_type='text', _name='name',
1068 requires=IS_EMAIL(banned='^.*\.com(|\..*)$'))
1069
1070 #Check for valid email address that must be from a .edu domain:
1071 INPUT(_type='text', _name='name',
1072 requires=IS_EMAIL(forced='^.*\.edu(|\..*)$'))
1073
1074 >>> IS_EMAIL()('a@b.com')
1075 ('a@b.com', None)
1076 >>> IS_EMAIL()('abc@def.com')
1077 ('abc@def.com', None)
1078 >>> IS_EMAIL()('abc@3def.com')
1079 ('abc@3def.com', None)
1080 >>> IS_EMAIL()('abc@def.us')
1081 ('abc@def.us', None)
1082 >>> IS_EMAIL()('abc@d_-f.us')
1083 ('abc@d_-f.us', None)
1084 >>> IS_EMAIL()('@def.com') # missing name
1085 ('@def.com', 'enter a valid email address')
1086 >>> IS_EMAIL()('"abc@def".com') # quoted name
1087 ('"abc@def".com', 'enter a valid email address')
1088 >>> IS_EMAIL()('abc+def.com') # no @
1089 ('abc+def.com', 'enter a valid email address')
1090 >>> IS_EMAIL()('abc@def.x') # one-char TLD
1091 ('abc@def.x', 'enter a valid email address')
1092 >>> IS_EMAIL()('abc@def.12') # numeric TLD
1093 ('abc@def.12', 'enter a valid email address')
1094 >>> IS_EMAIL()('abc@def..com') # double-dot in domain
1095 ('abc@def..com', 'enter a valid email address')
1096 >>> IS_EMAIL()('abc@.def.com') # dot starts domain
1097 ('abc@.def.com', 'enter a valid email address')
1098 >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD
1099 ('abc@def.c_m', 'enter a valid email address')
1100 >>> IS_EMAIL()('NotAnEmail') # missing @
1101 ('NotAnEmail', 'enter a valid email address')
1102 >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD
1103 ('abc@NotAnEmail', 'enter a valid email address')
1104 >>> IS_EMAIL()('customer/department@example.com')
1105 ('customer/department@example.com', None)
1106 >>> IS_EMAIL()('$A12345@example.com')
1107 ('$A12345@example.com', None)
1108 >>> IS_EMAIL()('!def!xyz%abc@example.com')
1109 ('!def!xyz%abc@example.com', None)
1110 >>> IS_EMAIL()('_Yosemite.Sam@example.com')
1111 ('_Yosemite.Sam@example.com', None)
1112 >>> IS_EMAIL()('~@example.com')
1113 ('~@example.com', None)
1114 >>> IS_EMAIL()('.wooly@example.com') # dot starts name
1115 ('.wooly@example.com', 'enter a valid email address')
1116 >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name
1117 ('wo..oly@example.com', 'enter a valid email address')
1118 >>> IS_EMAIL()('pootietang.@example.com') # dot ends name
1119 ('pootietang.@example.com', 'enter a valid email address')
1120 >>> IS_EMAIL()('.@example.com') # name is bare dot
1121 ('.@example.com', 'enter a valid email address')
1122 >>> IS_EMAIL()('Ima.Fool@example.com')
1123 ('Ima.Fool@example.com', None)
1124 >>> IS_EMAIL()('Ima Fool@example.com') # space in name
1125 ('Ima Fool@example.com', 'enter a valid email address')
1126 >>> IS_EMAIL()('localguy@localhost') # localhost as domain
1127 ('localguy@localhost', None)
1128
1129 """
1130
1131 regex = re.compile('''
1132 ^(?!\.) # name may not begin with a dot
1133 (
1134 [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
1135 |
1136 (?<!\.)\. # single dots only
1137 )+
1138 (?<!\.) # name may not end with a dot
1139 @
1140 (
1141 localhost
1142 |
1143 (
1144 [a-z0-9]
1145 # [sub]domain begins with alphanumeric
1146 (
1147 [-\w]* # alphanumeric, underscore, dot, hyphen
1148 [a-z0-9] # ending alphanumeric
1149 )?
1150 \. # ending dot
1151 )+
1152 [a-z]{2,} # TLD alpha-only
1153 )$
1154 ''', re.VERBOSE | re.IGNORECASE)
1155
1156 regex_proposed_but_failed = re.compile('^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$', re.VERBOSE | re.IGNORECASE)
1157
1158 - def __init__(self,
1159 banned=None,
1160 forced=None,
1161 error_message='enter a valid email address'):
1162 if isinstance(banned, str):
1163 banned = re.compile(banned)
1164 if isinstance(forced, str):
1165 forced = re.compile(forced)
1166 self.banned = banned
1167 self.forced = forced
1168 self.error_message = error_message
1169
1171 match = self.regex.match(value)
1172 if match:
1173 domain = value.split('@')[1]
1174 if (not self.banned or not self.banned.match(domain)) \
1175 and (not self.forced or self.forced.match(domain)):
1176 return (value, None)
1177 return (value, translate(self.error_message))
1178
1179
1180
1181
1182
1183 official_url_schemes = [
1184 'aaa',
1185 'aaas',
1186 'acap',
1187 'cap',
1188 'cid',
1189 'crid',
1190 'data',
1191 'dav',
1192 'dict',
1193 'dns',
1194 'fax',
1195 'file',
1196 'ftp',
1197 'go',
1198 'gopher',
1199 'h323',
1200 'http',
1201 'https',
1202 'icap',
1203 'im',
1204 'imap',
1205 'info',
1206 'ipp',
1207 'iris',
1208 'iris.beep',
1209 'iris.xpc',
1210 'iris.xpcs',
1211 'iris.lws',
1212 'ldap',
1213 'mailto',
1214 'mid',
1215 'modem',
1216 'msrp',
1217 'msrps',
1218 'mtqp',
1219 'mupdate',
1220 'news',
1221 'nfs',
1222 'nntp',
1223 'opaquelocktoken',
1224 'pop',
1225 'pres',
1226 'prospero',
1227 'rtsp',
1228 'service',
1229 'shttp',
1230 'sip',
1231 'sips',
1232 'snmp',
1233 'soap.beep',
1234 'soap.beeps',
1235 'tag',
1236 'tel',
1237 'telnet',
1238 'tftp',
1239 'thismessage',
1240 'tip',
1241 'tv',
1242 'urn',
1243 'vemmi',
1244 'wais',
1245 'xmlrpc.beep',
1246 'xmlrpc.beep',
1247 'xmpp',
1248 'z39.50r',
1249 'z39.50s',
1250 ]
1251 unofficial_url_schemes = [
1252 'about',
1253 'adiumxtra',
1254 'aim',
1255 'afp',
1256 'aw',
1257 'callto',
1258 'chrome',
1259 'cvs',
1260 'ed2k',
1261 'feed',
1262 'fish',
1263 'gg',
1264 'gizmoproject',
1265 'iax2',
1266 'irc',
1267 'ircs',
1268 'itms',
1269 'jar',
1270 'javascript',
1271 'keyparc',
1272 'lastfm',
1273 'ldaps',
1274 'magnet',
1275 'mms',
1276 'msnim',
1277 'mvn',
1278 'notes',
1279 'nsfw',
1280 'psyc',
1281 'paparazzi:http',
1282 'rmi',
1283 'rsync',
1284 'secondlife',
1285 'sgn',
1286 'skype',
1287 'ssh',
1288 'sftp',
1289 'smb',
1290 'sms',
1291 'soldat',
1292 'steam',
1293 'svn',
1294 'teamspeak',
1295 'unreal',
1296 'ut2004',
1297 'ventrilo',
1298 'view-source',
1299 'webcal',
1300 'wyciwyg',
1301 'xfire',
1302 'xri',
1303 'ymsgr',
1304 ]
1305 all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
1306 http_schemes = [None, 'http', 'https']
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318 url_split_regex = \
1319 re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?')
1320
1321
1322
1323
1324 label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]')
1328 '''
1329 Converts a unicode string into US-ASCII, using a simple conversion scheme.
1330 Each unicode character that does not have a US-ASCII equivalent is
1331 converted into a URL escaped form based on its hexadecimal value.
1332 For example, the unicode character '\u4e86' will become the string '%4e%86'
1333
1334 :param string: unicode string, the unicode string to convert into an
1335 escaped US-ASCII form
1336 :returns: the US-ASCII escaped form of the inputted string
1337 :rtype: string
1338
1339 @author: Jonathan Benn
1340 '''
1341 returnValue = StringIO()
1342
1343 for character in string:
1344 code = ord(character)
1345 if code > 0x7F:
1346 hexCode = hex(code)
1347 returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6])
1348 else:
1349 returnValue.write(character)
1350
1351 return returnValue.getvalue()
1352
1355 '''
1356 Follows the steps in RFC 3490, Section 4 to convert a unicode authority
1357 string into its ASCII equivalent.
1358 For example, u'www.Alliancefran\xe7aise.nu' will be converted into
1359 'www.xn--alliancefranaise-npb.nu'
1360
1361 :param authority: unicode string, the URL authority component to convert,
1362 e.g. u'www.Alliancefran\xe7aise.nu'
1363 :returns: the US-ASCII character equivalent to the inputed authority,
1364 e.g. 'www.xn--alliancefranaise-npb.nu'
1365 :rtype: string
1366 :raises Exception: if the function is not able to convert the inputed
1367 authority
1368
1369 @author: Jonathan Benn
1370 '''
1371
1372
1373
1374
1375 labels = label_split_regex.split(authority)
1376
1377
1378
1379
1380
1381
1382
1383 asciiLabels = []
1384 try:
1385 import encodings.idna
1386 for label in labels:
1387 if label:
1388 asciiLabels.append(encodings.idna.ToASCII(label))
1389 else:
1390
1391
1392
1393 asciiLabels.append('')
1394 except:
1395 asciiLabels = [str(label) for label in labels]
1396
1397 return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
1398
1401 '''
1402 Converts the inputed unicode url into a US-ASCII equivalent. This function
1403 goes a little beyond RFC 3490, which is limited in scope to the domain name
1404 (authority) only. Here, the functionality is expanded to what was observed
1405 on Wikipedia on 2009-Jan-22:
1406
1407 Component Can Use Unicode?
1408 --------- ----------------
1409 scheme No
1410 authority Yes
1411 path Yes
1412 query Yes
1413 fragment No
1414
1415 The authority component gets converted to punycode, but occurrences of
1416 unicode in other components get converted into a pair of URI escapes (we
1417 assume 4-byte unicode). E.g. the unicode character U+4E2D will be
1418 converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can
1419 understand this kind of URI encoding.
1420
1421 :param url: unicode string, the URL to convert from unicode into US-ASCII
1422 :param prepend_scheme: string, a protocol scheme to prepend to the URL if
1423 we're having trouble parsing it.
1424 e.g. "http". Input None to disable this functionality
1425 :returns: a US-ASCII equivalent of the inputed url
1426 :rtype: string
1427
1428 @author: Jonathan Benn
1429 '''
1430
1431
1432
1433 groups = url_split_regex.match(url).groups()
1434
1435 if not groups[3]:
1436
1437 scheme_to_prepend = prepend_scheme or 'http'
1438 groups = url_split_regex.match(
1439 unicode(scheme_to_prepend) + u'://' + url).groups()
1440
1441 if not groups[3]:
1442 raise Exception('No authority component found, ' +
1443 'could not decode unicode to US-ASCII')
1444
1445
1446 scheme = groups[1]
1447 authority = groups[3]
1448 path = groups[4] or ''
1449 query = groups[5] or ''
1450 fragment = groups[7] or ''
1451
1452 if prepend_scheme:
1453 scheme = str(scheme) + '://'
1454 else:
1455 scheme = ''
1456 return scheme + unicode_to_ascii_authority(authority) +\
1457 escape_unicode(path) + escape_unicode(query) + str(fragment)
1458
1461 """
1462 Rejects a URL string if any of the following is true:
1463 * The string is empty or None
1464 * The string uses characters that are not allowed in a URL
1465 * The URL scheme specified (if one is specified) is not valid
1466
1467 Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html
1468
1469 This function only checks the URL's syntax. It does not check that the URL
1470 points to a real document, for example, or that it otherwise makes sense
1471 semantically. This function does automatically prepend 'http://' in front
1472 of a URL if and only if that's necessary to successfully parse the URL.
1473 Please note that a scheme will be prepended only for rare cases
1474 (e.g. 'google.ca:80')
1475
1476 The list of allowed schemes is customizable with the allowed_schemes
1477 parameter. If you exclude None from the list, then abbreviated URLs
1478 (lacking a scheme such as 'http') will be rejected.
1479
1480 The default prepended scheme is customizable with the prepend_scheme
1481 parameter. If you set prepend_scheme to None then prepending will be
1482 disabled. URLs that require prepending to parse will still be accepted,
1483 but the return value will not be modified.
1484
1485 @author: Jonathan Benn
1486
1487 >>> IS_GENERIC_URL()('http://user@abc.com')
1488 ('http://user@abc.com', None)
1489
1490 """
1491
1492 - def __init__(
1493 self,
1494 error_message='enter a valid URL',
1495 allowed_schemes=None,
1496 prepend_scheme=None,
1497 ):
1498 """
1499 :param error_message: a string, the error message to give the end user
1500 if the URL does not validate
1501 :param allowed_schemes: a list containing strings or None. Each element
1502 is a scheme the inputed URL is allowed to use
1503 :param prepend_scheme: a string, this scheme is prepended if it's
1504 necessary to make the URL valid
1505 """
1506
1507 self.error_message = error_message
1508 if allowed_schemes is None:
1509 self.allowed_schemes = all_url_schemes
1510 else:
1511 self.allowed_schemes = allowed_schemes
1512 self.prepend_scheme = prepend_scheme
1513 if self.prepend_scheme not in self.allowed_schemes:
1514 raise SyntaxError("prepend_scheme='%s' is not in allowed_schemes=%s"
1515 % (self.prepend_scheme, self.allowed_schemes))
1516
1517 GENERIC_URL = re.compile(r"%[^0-9A-Fa-f]{2}|%[^0-9A-Fa-f][0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]|%$|%[0-9A-Fa-f]$|%[^0-9A-Fa-f]$")
1518 GENERIC_URL_VALID = re.compile(r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$")
1519
1521 """
1522 :param value: a string, the URL to validate
1523 :returns: a tuple, where tuple[0] is the inputed value (possible
1524 prepended with prepend_scheme), and tuple[1] is either
1525 None (success!) or the string error_message
1526 """
1527 try:
1528
1529 if not self.GENERIC_URL.search(value):
1530
1531 if self.GENERIC_URL_VALID.match(value):
1532
1533
1534 scheme = url_split_regex.match(value).group(2)
1535
1536 if not scheme is None:
1537 scheme = urllib.unquote(scheme).lower()
1538
1539 if scheme in self.allowed_schemes:
1540
1541 return (value, None)
1542 else:
1543
1544
1545
1546
1547 if value.find('://') < 0 and None in self.allowed_schemes:
1548 schemeToUse = self.prepend_scheme or 'http'
1549 prependTest = self.__call__(
1550 schemeToUse + '://' + value)
1551
1552 if prependTest[1] is None:
1553
1554 if self.prepend_scheme:
1555 return prependTest
1556 else:
1557
1558
1559 return (value, None)
1560 except:
1561 pass
1562
1563 return (value, translate(self.error_message))
1564
1565
1566
1567
1568
1569 official_top_level_domains = [
1570 'ac',
1571 'ad',
1572 'ae',
1573 'aero',
1574 'af',
1575 'ag',
1576 'ai',
1577 'al',
1578 'am',
1579 'an',
1580 'ao',
1581 'aq',
1582 'ar',
1583 'arpa',
1584 'as',
1585 'asia',
1586 'at',
1587 'au',
1588 'aw',
1589 'ax',
1590 'az',
1591 'ba',
1592 'bb',
1593 'bd',
1594 'be',
1595 'bf',
1596 'bg',
1597 'bh',
1598 'bi',
1599 'biz',
1600 'bj',
1601 'bl',
1602 'bm',
1603 'bn',
1604 'bo',
1605 'br',
1606 'bs',
1607 'bt',
1608 'bv',
1609 'bw',
1610 'by',
1611 'bz',
1612 'ca',
1613 'cat',
1614 'cc',
1615 'cd',
1616 'cf',
1617 'cg',
1618 'ch',
1619 'ci',
1620 'ck',
1621 'cl',
1622 'cm',
1623 'cn',
1624 'co',
1625 'com',
1626 'coop',
1627 'cr',
1628 'cu',
1629 'cv',
1630 'cx',
1631 'cy',
1632 'cz',
1633 'de',
1634 'dj',
1635 'dk',
1636 'dm',
1637 'do',
1638 'dz',
1639 'ec',
1640 'edu',
1641 'ee',
1642 'eg',
1643 'eh',
1644 'er',
1645 'es',
1646 'et',
1647 'eu',
1648 'example',
1649 'fi',
1650 'fj',
1651 'fk',
1652 'fm',
1653 'fo',
1654 'fr',
1655 'ga',
1656 'gb',
1657 'gd',
1658 'ge',
1659 'gf',
1660 'gg',
1661 'gh',
1662 'gi',
1663 'gl',
1664 'gm',
1665 'gn',
1666 'gov',
1667 'gp',
1668 'gq',
1669 'gr',
1670 'gs',
1671 'gt',
1672 'gu',
1673 'gw',
1674 'gy',
1675 'hk',
1676 'hm',
1677 'hn',
1678 'hr',
1679 'ht',
1680 'hu',
1681 'id',
1682 'ie',
1683 'il',
1684 'im',
1685 'in',
1686 'info',
1687 'int',
1688 'invalid',
1689 'io',
1690 'iq',
1691 'ir',
1692 'is',
1693 'it',
1694 'je',
1695 'jm',
1696 'jo',
1697 'jobs',
1698 'jp',
1699 'ke',
1700 'kg',
1701 'kh',
1702 'ki',
1703 'km',
1704 'kn',
1705 'kp',
1706 'kr',
1707 'kw',
1708 'ky',
1709 'kz',
1710 'la',
1711 'lb',
1712 'lc',
1713 'li',
1714 'lk',
1715 'localhost',
1716 'lr',
1717 'ls',
1718 'lt',
1719 'lu',
1720 'lv',
1721 'ly',
1722 'ma',
1723 'mc',
1724 'md',
1725 'me',
1726 'mf',
1727 'mg',
1728 'mh',
1729 'mil',
1730 'mk',
1731 'ml',
1732 'mm',
1733 'mn',
1734 'mo',
1735 'mobi',
1736 'mp',
1737 'mq',
1738 'mr',
1739 'ms',
1740 'mt',
1741 'mu',
1742 'museum',
1743 'mv',
1744 'mw',
1745 'mx',
1746 'my',
1747 'mz',
1748 'na',
1749 'name',
1750 'nc',
1751 'ne',
1752 'net',
1753 'nf',
1754 'ng',
1755 'ni',
1756 'nl',
1757 'no',
1758 'np',
1759 'nr',
1760 'nu',
1761 'nz',
1762 'om',
1763 'org',
1764 'pa',
1765 'pe',
1766 'pf',
1767 'pg',
1768 'ph',
1769 'pk',
1770 'pl',
1771 'pm',
1772 'pn',
1773 'pr',
1774 'pro',
1775 'ps',
1776 'pt',
1777 'pw',
1778 'py',
1779 'qa',
1780 're',
1781 'ro',
1782 'rs',
1783 'ru',
1784 'rw',
1785 'sa',
1786 'sb',
1787 'sc',
1788 'sd',
1789 'se',
1790 'sg',
1791 'sh',
1792 'si',
1793 'sj',
1794 'sk',
1795 'sl',
1796 'sm',
1797 'sn',
1798 'so',
1799 'sr',
1800 'st',
1801 'su',
1802 'sv',
1803 'sy',
1804 'sz',
1805 'tc',
1806 'td',
1807 'tel',
1808 'test',
1809 'tf',
1810 'tg',
1811 'th',
1812 'tj',
1813 'tk',
1814 'tl',
1815 'tm',
1816 'tn',
1817 'to',
1818 'tp',
1819 'tr',
1820 'travel',
1821 'tt',
1822 'tv',
1823 'tw',
1824 'tz',
1825 'ua',
1826 'ug',
1827 'uk',
1828 'um',
1829 'us',
1830 'uy',
1831 'uz',
1832 'va',
1833 'vc',
1834 've',
1835 'vg',
1836 'vi',
1837 'vn',
1838 'vu',
1839 'wf',
1840 'ws',
1841 'xn--0zwm56d',
1842 'xn--11b5bs3a9aj6g',
1843 'xn--80akhbyknj4f',
1844 'xn--9t4b11yi5a',
1845 'xn--deba0ad',
1846 'xn--g6w251d',
1847 'xn--hgbk6aj7f53bba',
1848 'xn--hlcj6aya9esc7a',
1849 'xn--jxalpdlp',
1850 'xn--kgbechtv',
1851 'xn--p1ai',
1852 'xn--zckzah',
1853 'ye',
1854 'yt',
1855 'yu',
1856 'za',
1857 'zm',
1858 'zw',
1859 ]
1863 """
1864 Rejects a URL string if any of the following is true:
1865 * The string is empty or None
1866 * The string uses characters that are not allowed in a URL
1867 * The string breaks any of the HTTP syntactic rules
1868 * The URL scheme specified (if one is specified) is not 'http' or 'https'
1869 * The top-level domain (if a host name is specified) does not exist
1870
1871 Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html
1872
1873 This function only checks the URL's syntax. It does not check that the URL
1874 points to a real document, for example, or that it otherwise makes sense
1875 semantically. This function does automatically prepend 'http://' in front
1876 of a URL in the case of an abbreviated URL (e.g. 'google.ca').
1877
1878 The list of allowed schemes is customizable with the allowed_schemes
1879 parameter. If you exclude None from the list, then abbreviated URLs
1880 (lacking a scheme such as 'http') will be rejected.
1881
1882 The default prepended scheme is customizable with the prepend_scheme
1883 parameter. If you set prepend_scheme to None then prepending will be
1884 disabled. URLs that require prepending to parse will still be accepted,
1885 but the return value will not be modified.
1886
1887 @author: Jonathan Benn
1888
1889 >>> IS_HTTP_URL()('http://1.2.3.4')
1890 ('http://1.2.3.4', None)
1891 >>> IS_HTTP_URL()('http://abc.com')
1892 ('http://abc.com', None)
1893 >>> IS_HTTP_URL()('https://abc.com')
1894 ('https://abc.com', None)
1895 >>> IS_HTTP_URL()('httpx://abc.com')
1896 ('httpx://abc.com', 'enter a valid URL')
1897 >>> IS_HTTP_URL()('http://abc.com:80')
1898 ('http://abc.com:80', None)
1899 >>> IS_HTTP_URL()('http://user@abc.com')
1900 ('http://user@abc.com', None)
1901 >>> IS_HTTP_URL()('http://user@1.2.3.4')
1902 ('http://user@1.2.3.4', None)
1903
1904 """
1905
1906 GENERIC_VALID_IP = re.compile(
1907 "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$")
1908 GENERIC_VALID_DOMAIN = re.compile("([\w.!~*'|;:&=+$,-]+@)?(([A-Za-z0-9]+[A-Za-z0-9\-]*[A-Za-z0-9]+\.)*([A-Za-z0-9]+\.)*)*([A-Za-z]+[A-Za-z0-9\-]*[A-Za-z0-9]+)\.?(:\d*)*$")
1909
1910 - def __init__(
1911 self,
1912 error_message='enter a valid URL',
1913 allowed_schemes=None,
1914 prepend_scheme='http',
1915 ):
1916 """
1917 :param error_message: a string, the error message to give the end user
1918 if the URL does not validate
1919 :param allowed_schemes: a list containing strings or None. Each element
1920 is a scheme the inputed URL is allowed to use
1921 :param prepend_scheme: a string, this scheme is prepended if it's
1922 necessary to make the URL valid
1923 """
1924
1925 self.error_message = error_message
1926 if allowed_schemes is None:
1927 self.allowed_schemes = http_schemes
1928 else:
1929 self.allowed_schemes = allowed_schemes
1930 self.prepend_scheme = prepend_scheme
1931
1932 for i in self.allowed_schemes:
1933 if i not in http_schemes:
1934 raise SyntaxError("allowed_scheme value '%s' is not in %s" %
1935 (i, http_schemes))
1936
1937 if self.prepend_scheme not in self.allowed_schemes:
1938 raise SyntaxError("prepend_scheme='%s' is not in allowed_schemes=%s" %
1939 (self.prepend_scheme, self.allowed_schemes))
1940
1942 """
1943 :param value: a string, the URL to validate
1944 :returns: a tuple, where tuple[0] is the inputed value
1945 (possible prepended with prepend_scheme), and tuple[1] is either
1946 None (success!) or the string error_message
1947 """
1948
1949 try:
1950
1951 x = IS_GENERIC_URL(error_message=self.error_message,
1952 allowed_schemes=self.allowed_schemes,
1953 prepend_scheme=self.prepend_scheme)
1954 if x(value)[1] is None:
1955 componentsMatch = url_split_regex.match(value)
1956 authority = componentsMatch.group(4)
1957
1958 if authority:
1959
1960 if self.GENERIC_VALID_IP.match(authority):
1961
1962 return (value, None)
1963 else:
1964
1965 domainMatch = self.GENERIC_VALID_DOMAIN.match(
1966 authority)
1967 if domainMatch:
1968
1969 if domainMatch.group(5).lower()\
1970 in official_top_level_domains:
1971
1972 return (value, None)
1973 else:
1974
1975
1976 path = componentsMatch.group(5)
1977
1978
1979 if path.startswith('/'):
1980
1981 return (value, None)
1982 else:
1983
1984
1985 if value.find('://') < 0:
1986 schemeToUse = self.prepend_scheme or 'http'
1987 prependTest = self.__call__(schemeToUse
1988 + '://' + value)
1989
1990 if prependTest[1] is None:
1991
1992 if self.prepend_scheme:
1993 return prependTest
1994 else:
1995
1996
1997 return (value, None)
1998 except:
1999 pass
2000
2001 return (value, translate(self.error_message))
2002
2003
2004 -class IS_URL(Validator):
2005 """
2006 Rejects a URL string if any of the following is true:
2007 * The string is empty or None
2008 * The string uses characters that are not allowed in a URL
2009 * The string breaks any of the HTTP syntactic rules
2010 * The URL scheme specified (if one is specified) is not 'http' or 'https'
2011 * The top-level domain (if a host name is specified) does not exist
2012
2013 (These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html)
2014
2015 This function only checks the URL's syntax. It does not check that the URL
2016 points to a real document, for example, or that it otherwise makes sense
2017 semantically. This function does automatically prepend 'http://' in front
2018 of a URL in the case of an abbreviated URL (e.g. 'google.ca').
2019
2020 If the parameter mode='generic' is used, then this function's behavior
2021 changes. It then rejects a URL string if any of the following is true:
2022 * The string is empty or None
2023 * The string uses characters that are not allowed in a URL
2024 * The URL scheme specified (if one is specified) is not valid
2025
2026 (These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html)
2027
2028 The list of allowed schemes is customizable with the allowed_schemes
2029 parameter. If you exclude None from the list, then abbreviated URLs
2030 (lacking a scheme such as 'http') will be rejected.
2031
2032 The default prepended scheme is customizable with the prepend_scheme
2033 parameter. If you set prepend_scheme to None then prepending will be
2034 disabled. URLs that require prepending to parse will still be accepted,
2035 but the return value will not be modified.
2036
2037 IS_URL is compatible with the Internationalized Domain Name (IDN) standard
2038 specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result,
2039 URLs can be regular strings or unicode strings.
2040 If the URL's domain component (e.g. google.ca) contains non-US-ASCII
2041 letters, then the domain will be converted into Punycode (defined in
2042 RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond
2043 the standards, and allows non-US-ASCII characters to be present in the path
2044 and query components of the URL as well. These non-US-ASCII characters will
2045 be escaped using the standard '%20' type syntax. e.g. the unicode
2046 character with hex code 0x4e86 will become '%4e%86'
2047
2048 Code Examples::
2049
2050 INPUT(_type='text', _name='name', requires=IS_URL())
2051 >>> IS_URL()('abc.com')
2052 ('http://abc.com', None)
2053
2054 INPUT(_type='text', _name='name', requires=IS_URL(mode='generic'))
2055 >>> IS_URL(mode='generic')('abc.com')
2056 ('abc.com', None)
2057
2058 INPUT(_type='text', _name='name',
2059 requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https'))
2060 >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com')
2061 ('https://abc.com', None)
2062
2063 INPUT(_type='text', _name='name',
2064 requires=IS_URL(prepend_scheme='https'))
2065 >>> IS_URL(prepend_scheme='https')('abc.com')
2066 ('https://abc.com', None)
2067
2068 INPUT(_type='text', _name='name',
2069 requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'],
2070 prepend_scheme='https'))
2071 >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com')
2072 ('https://abc.com', None)
2073 >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com')
2074 ('abc.com', None)
2075
2076 @author: Jonathan Benn
2077 """
2078
2079 - def __init__(
2080 self,
2081 error_message='enter a valid URL',
2082 mode='http',
2083 allowed_schemes=None,
2084 prepend_scheme='http',
2085 ):
2086 """
2087 :param error_message: a string, the error message to give the end user
2088 if the URL does not validate
2089 :param allowed_schemes: a list containing strings or None. Each element
2090 is a scheme the inputed URL is allowed to use
2091 :param prepend_scheme: a string, this scheme is prepended if it's
2092 necessary to make the URL valid
2093 """
2094
2095 self.error_message = error_message
2096 self.mode = mode.lower()
2097 if not self.mode in ['generic', 'http']:
2098 raise SyntaxError("invalid mode '%s' in IS_URL" % self.mode)
2099 self.allowed_schemes = allowed_schemes
2100
2101 if self.allowed_schemes:
2102 if prepend_scheme not in self.allowed_schemes:
2103 raise SyntaxError("prepend_scheme='%s' is not in allowed_schemes=%s"
2104 % (prepend_scheme, self.allowed_schemes))
2105
2106
2107
2108
2109 self.prepend_scheme = prepend_scheme
2110
2112 """
2113 :param value: a unicode or regular string, the URL to validate
2114 :returns: a (string, string) tuple, where tuple[0] is the modified
2115 input value and tuple[1] is either None (success!) or the
2116 string error_message. The input value will never be modified in the
2117 case of an error. However, if there is success then the input URL
2118 may be modified to (1) prepend a scheme, and/or (2) convert a
2119 non-compliant unicode URL into a compliant US-ASCII version.
2120 """
2121
2122 if self.mode == 'generic':
2123 subMethod = IS_GENERIC_URL(error_message=self.error_message,
2124 allowed_schemes=self.allowed_schemes,
2125 prepend_scheme=self.prepend_scheme)
2126 elif self.mode == 'http':
2127 subMethod = IS_HTTP_URL(error_message=self.error_message,
2128 allowed_schemes=self.allowed_schemes,
2129 prepend_scheme=self.prepend_scheme)
2130 else:
2131 raise SyntaxError("invalid mode '%s' in IS_URL" % self.mode)
2132
2133 if type(value) != unicode:
2134 return subMethod(value)
2135 else:
2136 try:
2137 asciiValue = unicode_to_ascii_url(value, self.prepend_scheme)
2138 except Exception:
2139
2140
2141 return (value, translate(self.error_message))
2142
2143 methodResult = subMethod(asciiValue)
2144
2145 if not methodResult[1] is None:
2146
2147 return (value, methodResult[1])
2148 else:
2149 return methodResult
2150
2151
2152 regex_time = re.compile(
2153 '((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]+))?([^0-9ap ]+(?P<s>[0-9]*))?((?P<d>[ap]m))?')
2154
2155
2156 -class IS_TIME(Validator):
2157 """
2158 example::
2159
2160 INPUT(_type='text', _name='name', requires=IS_TIME())
2161
2162 understands the following formats
2163 hh:mm:ss [am/pm]
2164 hh:mm [am/pm]
2165 hh [am/pm]
2166
2167 [am/pm] is optional, ':' can be replaced by any other non-space non-digit
2168
2169 >>> IS_TIME()('21:30')
2170 (datetime.time(21, 30), None)
2171 >>> IS_TIME()('21-30')
2172 (datetime.time(21, 30), None)
2173 >>> IS_TIME()('21.30')
2174 (datetime.time(21, 30), None)
2175 >>> IS_TIME()('21:30:59')
2176 (datetime.time(21, 30, 59), None)
2177 >>> IS_TIME()('5:30')
2178 (datetime.time(5, 30), None)
2179 >>> IS_TIME()('5:30 am')
2180 (datetime.time(5, 30), None)
2181 >>> IS_TIME()('5:30 pm')
2182 (datetime.time(17, 30), None)
2183 >>> IS_TIME()('5:30 whatever')
2184 ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2185 >>> IS_TIME()('5:30 20')
2186 ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2187 >>> IS_TIME()('24:30')
2188 ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2189 >>> IS_TIME()('21:60')
2190 ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2191 >>> IS_TIME()('21:30::')
2192 ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2193 >>> IS_TIME()('')
2194 ('', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2195 """
2196
2197 - def __init__(self, error_message='enter time as hh:mm:ss (seconds, am, pm optional)'):
2198 self.error_message = error_message
2199
2201 try:
2202 ivalue = value
2203 value = regex_time.match(value.lower())
2204 (h, m, s) = (int(value.group('h')), 0, 0)
2205 if not value.group('m') is None:
2206 m = int(value.group('m'))
2207 if not value.group('s') is None:
2208 s = int(value.group('s'))
2209 if value.group('d') == 'pm' and 0 < h < 12:
2210 h = h + 12
2211 if not (h in range(24) and m in range(60) and s
2212 in range(60)):
2213 raise ValueError('Hours or minutes or seconds are outside of allowed range')
2214 value = datetime.time(h, m, s)
2215 return (value, None)
2216 except AttributeError:
2217 pass
2218 except ValueError:
2219 pass
2220 return (ivalue, translate(self.error_message))
2221
2222
2223 -class UTC(datetime.tzinfo):
2224 """UTC"""
2225 ZERO = datetime.timedelta(0)
2230 - def dst(self, dt):
2232 utc = UTC()
2235 """
2236 example::
2237
2238 INPUT(_type='text', _name='name', requires=IS_DATE())
2239
2240 date has to be in the ISO8960 format YYYY-MM-DD
2241 """
2242
2243 - def __init__(self, format='%Y-%m-%d',
2244 error_message='enter date as %(format)s',
2245 timezone = None):
2246 """
2247 timezome must be None or a pytz.timezone("America/Chicago") object
2248 """
2249 self.format = translate(format)
2250 self.error_message = str(error_message)
2251 self.timezone = timezone
2252 self.extremes = {}
2253
2255 ovalue = value
2256 if isinstance(value, datetime.date):
2257 if self.timezone is not None:
2258 value = value - datetime.timedelta(seconds=self.timezone*3600)
2259 return (value, None)
2260 try:
2261 (y, m, d, hh, mm, ss, t0, t1, t2) = \
2262 time.strptime(value, str(self.format))
2263 value = datetime.date(y, m, d)
2264 if self.timezone is not None:
2265 value = self.timezone.localize(value).astimezone(utc)
2266 return (value, None)
2267 except:
2268 self.extremes.update(IS_DATETIME.nice(self.format))
2269 return (ovalue, translate(self.error_message) % self.extremes)
2270
2285
2288 """
2289 example::
2290
2291 INPUT(_type='text', _name='name', requires=IS_DATETIME())
2292
2293 datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss
2294 """
2295
2296 isodatetime = '%Y-%m-%d %H:%M:%S'
2297
2298 @staticmethod
2300 code = (('%Y', '1963'),
2301 ('%y', '63'),
2302 ('%d', '28'),
2303 ('%m', '08'),
2304 ('%b', 'Aug'),
2305 ('%B', 'August'),
2306 ('%H', '14'),
2307 ('%I', '02'),
2308 ('%p', 'PM'),
2309 ('%M', '30'),
2310 ('%S', '59'))
2311 for (a, b) in code:
2312 format = format.replace(a, b)
2313 return dict(format=format)
2314
2315 - def __init__(self, format='%Y-%m-%d %H:%M:%S',
2316 error_message='enter date and time as %(format)s',
2317 timezone=None):
2318 """
2319 timezome must be None or a pytz.timezone("America/Chicago") object
2320 """
2321 self.format = translate(format)
2322 self.error_message = str(error_message)
2323 self.extremes = {}
2324 self.timezone = timezone
2325
2327 ovalue = value
2328 if isinstance(value, datetime.datetime):
2329 return (value, None)
2330 try:
2331 (y, m, d, hh, mm, ss, t0, t1, t2) = \
2332 time.strptime(value, str(self.format))
2333 value = datetime.datetime(y, m, d, hh, mm, ss)
2334 if self.timezone is not None:
2335 value = self.timezone.localize(value).astimezone(utc)
2336 return (value, None)
2337 except:
2338 self.extremes.update(IS_DATETIME.nice(self.format))
2339 return (ovalue, translate(self.error_message) % self.extremes)
2340
2356
2359 """
2360 example::
2361
2362 >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \
2363 maximum=datetime.date(2009,12,31), \
2364 format="%m/%d/%Y",error_message="oops")
2365
2366 >>> v('03/03/2008')
2367 (datetime.date(2008, 3, 3), None)
2368
2369 >>> v('03/03/2010')
2370 ('03/03/2010', 'oops')
2371
2372 >>> v(datetime.date(2008,3,3))
2373 (datetime.date(2008, 3, 3), None)
2374
2375 >>> v(datetime.date(2010,3,3))
2376 (datetime.date(2010, 3, 3), 'oops')
2377
2378 """
2379 - def __init__(self,
2380 minimum=None,
2381 maximum=None,
2382 format='%Y-%m-%d',
2383 error_message=None,
2384 timezone=None):
2385 self.minimum = minimum
2386 self.maximum = maximum
2387 if error_message is None:
2388 if minimum is None:
2389 error_message = "enter date on or before %(max)s"
2390 elif maximum is None:
2391 error_message = "enter date on or after %(min)s"
2392 else:
2393 error_message = "enter date in range %(min)s %(max)s"
2394 IS_DATE.__init__(self,
2395 format=format,
2396 error_message=error_message,
2397 timezone=timezone)
2398 self.extremes = dict(min=minimum, max=maximum)
2399
2401 ovalue = value
2402 (value, msg) = IS_DATE.__call__(self, value)
2403 if msg is not None:
2404 return (value, msg)
2405 if self.minimum and self.minimum > value:
2406 return (ovalue, translate(self.error_message) % self.extremes)
2407 if self.maximum and value > self.maximum:
2408 return (ovalue, translate(self.error_message) % self.extremes)
2409 return (value, None)
2410
2413 """
2414 example::
2415
2416 >>> v = IS_DATETIME_IN_RANGE(\
2417 minimum=datetime.datetime(2008,1,1,12,20), \
2418 maximum=datetime.datetime(2009,12,31,12,20), \
2419 format="%m/%d/%Y %H:%M",error_message="oops")
2420 >>> v('03/03/2008 12:40')
2421 (datetime.datetime(2008, 3, 3, 12, 40), None)
2422
2423 >>> v('03/03/2010 10:34')
2424 ('03/03/2010 10:34', 'oops')
2425
2426 >>> v(datetime.datetime(2008,3,3,0,0))
2427 (datetime.datetime(2008, 3, 3, 0, 0), None)
2428
2429 >>> v(datetime.datetime(2010,3,3,0,0))
2430 (datetime.datetime(2010, 3, 3, 0, 0), 'oops')
2431 """
2432 - def __init__(self,
2433 minimum=None,
2434 maximum=None,
2435 format='%Y-%m-%d %H:%M:%S',
2436 error_message=None,
2437 timezone=None):
2438 self.minimum = minimum
2439 self.maximum = maximum
2440 if error_message is None:
2441 if minimum is None:
2442 error_message = "enter date and time on or before %(max)s"
2443 elif maximum is None:
2444 error_message = "enter date and time on or after %(min)s"
2445 else:
2446 error_message = "enter date and time in range %(min)s %(max)s"
2447 IS_DATETIME.__init__(self,
2448 format=format,
2449 error_message=error_message,
2450 timezone=timezone)
2451 self.extremes = dict(min=minimum, max=maximum)
2452
2454 ovalue = value
2455 (value, msg) = IS_DATETIME.__call__(self, value)
2456 if msg is not None:
2457 return (value, msg)
2458 if self.minimum and self.minimum > value:
2459 return (ovalue, translate(self.error_message) % self.extremes)
2460 if self.maximum and value > self.maximum:
2461 return (ovalue, translate(self.error_message) % self.extremes)
2462 return (value, None)
2463
2466
2467 - def __init__(self, other=None, minimum=0, maximum=100,
2468 error_message=None):
2469 self.other = other
2470 self.minimum = minimum
2471 self.maximum = maximum
2472 self.error_message = error_message or "enter between %(min)g and %(max)g values"
2473
2475 ivalue = value
2476 if not isinstance(value, list):
2477 ivalue = [ivalue]
2478 if not self.minimum is None and len(ivalue) < self.minimum:
2479 return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum))
2480 if not self.maximum is None and len(ivalue) > self.maximum:
2481 return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum))
2482 new_value = []
2483 if self.other:
2484 for item in ivalue:
2485 if item.strip():
2486 (v, e) = self.other(item)
2487 if e:
2488 return (ivalue, e)
2489 else:
2490 new_value.append(v)
2491 ivalue = new_value
2492 return (ivalue, None)
2493
2496 """
2497 convert to lower case
2498
2499 >>> IS_LOWER()('ABC')
2500 ('abc', None)
2501 >>> IS_LOWER()('Ñ')
2502 ('\\xc3\\xb1', None)
2503 """
2504
2507
2510 """
2511 convert to upper case
2512
2513 >>> IS_UPPER()('abc')
2514 ('ABC', None)
2515 >>> IS_UPPER()('ñ')
2516 ('\\xc3\\x91', None)
2517 """
2518
2521
2522
2523 -def urlify(s, maxlen=80, keep_underscores=False):
2524 """
2525 Convert incoming string to a simplified ASCII subset.
2526 if (keep_underscores): underscores are retained in the string
2527 else: underscores are translated to hyphens (default)
2528 """
2529 if isinstance(s, str):
2530 s = s.decode('utf-8')
2531 s = s.lower()
2532 s = unicodedata.normalize('NFKD', s)
2533 s = s.encode('ascii', 'ignore')
2534 s = re.sub('&\w+?;', '', s)
2535 if keep_underscores:
2536 s = re.sub('\s+', '-', s)
2537 s = re.sub('[^\w\-]', '', s)
2538
2539 else:
2540 s = re.sub('[\s_]+', '-', s)
2541 s = re.sub('[^a-z0-9\-]', '', s)
2542 s = re.sub('[-_][-_]+', '-', s)
2543 s = s.strip('-')
2544 return s[:maxlen]
2545
2548 """
2549 convert arbitrary text string to a slug
2550
2551 >>> IS_SLUG()('abc123')
2552 ('abc123', None)
2553 >>> IS_SLUG()('ABC123')
2554 ('abc123', None)
2555 >>> IS_SLUG()('abc-123')
2556 ('abc-123', None)
2557 >>> IS_SLUG()('abc--123')
2558 ('abc-123', None)
2559 >>> IS_SLUG()('abc 123')
2560 ('abc-123', None)
2561 >>> IS_SLUG()('abc\t_123')
2562 ('abc-123', None)
2563 >>> IS_SLUG()('-abc-')
2564 ('abc', None)
2565 >>> IS_SLUG()('--a--b--_ -c--')
2566 ('a-b-c', None)
2567 >>> IS_SLUG()('abc&123')
2568 ('abc123', None)
2569 >>> IS_SLUG()('abc&123&def')
2570 ('abc123def', None)
2571 >>> IS_SLUG()('ñ')
2572 ('n', None)
2573 >>> IS_SLUG(maxlen=4)('abc123')
2574 ('abc1', None)
2575 >>> IS_SLUG()('abc_123')
2576 ('abc-123', None)
2577 >>> IS_SLUG(keep_underscores=False)('abc_123')
2578 ('abc-123', None)
2579 >>> IS_SLUG(keep_underscores=True)('abc_123')
2580 ('abc_123', None)
2581 >>> IS_SLUG(check=False)('abc')
2582 ('abc', None)
2583 >>> IS_SLUG(check=True)('abc')
2584 ('abc', None)
2585 >>> IS_SLUG(check=False)('a bc')
2586 ('a-bc', None)
2587 >>> IS_SLUG(check=True)('a bc')
2588 ('a bc', 'must be slug')
2589 """
2590
2591 @staticmethod
2592 - def urlify(value, maxlen=80, keep_underscores=False):
2593 return urlify(value, maxlen, keep_underscores)
2594
2595 - def __init__(self, maxlen=80, check=False, error_message='must be slug', keep_underscores=False):
2596 self.maxlen = maxlen
2597 self.check = check
2598 self.error_message = error_message
2599 self.keep_underscores = keep_underscores
2600
2602 if self.check and value != urlify(value, self.maxlen, self.keep_underscores):
2603 return (value, translate(self.error_message))
2604 return (urlify(value, self.maxlen, self.keep_underscores), None)
2605
2606
2607 -class ANY_OF(Validator):
2608 """
2609 test if any of the validators in a list return successfully
2610
2611 >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('a@b.co')
2612 ('a@b.co', None)
2613 >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('abco')
2614 ('abco', None)
2615 >>> ANY_OF([IS_EMAIL(),IS_ALPHANUMERIC()])('@ab.co')
2616 ('@ab.co', 'enter only letters, numbers, and underscore')
2617 >>> ANY_OF([IS_ALPHANUMERIC(),IS_EMAIL()])('@ab.co')
2618 ('@ab.co', 'enter a valid email address')
2619 """
2620
2623
2625 for validator in self.subs:
2626 value, error = validator(value)
2627 if error == None:
2628 break
2629 return value, error
2630
2637
2640 """
2641 dummy class for testing IS_EMPTY_OR
2642
2643 >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com')
2644 ('abc@def.com', None)
2645 >>> IS_EMPTY_OR(IS_EMAIL())(' ')
2646 (None, None)
2647 >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ')
2648 ('abc', None)
2649 >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def')
2650 ('abc', None)
2651 >>> IS_EMPTY_OR(IS_EMAIL())('abc')
2652 ('abc', 'enter a valid email address')
2653 >>> IS_EMPTY_OR(IS_EMAIL())(' abc ')
2654 ('abc', 'enter a valid email address')
2655 """
2656
2657 - def __init__(self, other, null=None, empty_regex=None):
2658 (self.other, self.null) = (other, null)
2659 if empty_regex is not None:
2660 self.empty_regex = re.compile(empty_regex)
2661 else:
2662 self.empty_regex = None
2663 if hasattr(other, 'multiple'):
2664 self.multiple = other.multiple
2665 if hasattr(other, 'options'):
2666 self.options = self._options
2667
2673
2675 if isinstance(self.other, (list, tuple)):
2676 for item in self.other:
2677 if hasattr(item, 'set_self_id'):
2678 item.set_self_id(id)
2679 else:
2680 if hasattr(self.other, 'set_self_id'):
2681 self.other.set_self_id(id)
2682
2684 value, empty = is_empty(value, empty_regex=self.empty_regex)
2685 if empty:
2686 return (self.null, None)
2687 if isinstance(self.other, (list, tuple)):
2688 error = None
2689 for item in self.other:
2690 value, error = item(value)
2691 if error:
2692 break
2693 return value, error
2694 else:
2695 return self.other(value)
2696
2701
2702 IS_NULL_OR = IS_EMPTY_OR
2703
2704
2705 -class CLEANUP(Validator):
2706 """
2707 example::
2708
2709 INPUT(_type='text', _name='name', requires=CLEANUP())
2710
2711 removes special characters on validation
2712 """
2713 REGEX_CLEANUP = re.compile('[^\x09\x0a\x0d\x20-\x7e]')
2714
2718
2720 v = self.regex.sub('', str(value).strip())
2721 return (v, None)
2722
2725 """
2726 Stores a lazy password hash
2727 """
2729 """
2730 crypt is an instance of the CRYPT validator,
2731 password is the password as inserted by the user
2732 """
2733 self.crypt = crypt
2734 self.password = password
2735 self.crypted = None
2736
2738 """
2739 Encrypted self.password and caches it in self.crypted.
2740 If self.crypt.salt the output is in the format <algorithm>$<salt>$<hash>
2741
2742 Try get the digest_alg from the key (if it exists)
2743 else assume the default digest_alg. If not key at all, set key=''
2744
2745 If a salt is specified use it, if salt is True, set salt to uuid
2746 (this should all be backward compatible)
2747
2748 Options:
2749 key = 'uuid'
2750 key = 'md5:uuid'
2751 key = 'sha512:uuid'
2752 ...
2753 key = 'pbkdf2(1000,64,sha512):uuid' 1000 iterations and 64 chars length
2754 """
2755 if self.crypted:
2756 return self.crypted
2757 if self.crypt.key:
2758 if ':' in self.crypt.key:
2759 digest_alg, key = self.crypt.key.split(':', 1)
2760 else:
2761 digest_alg, key = self.crypt.digest_alg, self.crypt.key
2762 else:
2763 digest_alg, key = self.crypt.digest_alg, ''
2764 if self.crypt.salt:
2765 if self.crypt.salt == True:
2766 salt = str(web2py_uuid()).replace('-', '')[-16:]
2767 else:
2768 salt = self.crypt.salt
2769 else:
2770 salt = ''
2771 hashed = simple_hash(self.password, key, salt, digest_alg)
2772 self.crypted = '%s$%s$%s' % (digest_alg, salt, hashed)
2773 return self.crypted
2774
2775 - def __eq__(self, stored_password):
2776 """
2777 compares the current lazy crypted password with a stored password
2778 """
2779
2780
2781 if isinstance(stored_password, self.__class__):
2782 return ((self is stored_password) or
2783 ((self.crypt.key == stored_password.crypt.key) and
2784 (self.password == stored_password.password)))
2785
2786 if self.crypt.key:
2787 if ':' in self.crypt.key:
2788 key = self.crypt.key.split(':')[1]
2789 else:
2790 key = self.crypt.key
2791 else:
2792 key = ''
2793 if stored_password is None:
2794 return False
2795 elif stored_password.count('$') == 2:
2796 (digest_alg, salt, hash) = stored_password.split('$')
2797 h = simple_hash(self.password, key, salt, digest_alg)
2798 temp_pass = '%s$%s$%s' % (digest_alg, salt, h)
2799 else:
2800
2801 digest_alg = DIGEST_ALG_BY_SIZE.get(len(stored_password), None)
2802 if not digest_alg:
2803 return False
2804 else:
2805 temp_pass = simple_hash(self.password, key, '', digest_alg)
2806 return temp_pass == stored_password
2807
2808
2809 -class CRYPT(object):
2810 """
2811 example::
2812
2813 INPUT(_type='text', _name='name', requires=CRYPT())
2814
2815 encodes the value on validation with a digest.
2816
2817 If no arguments are provided CRYPT uses the MD5 algorithm.
2818 If the key argument is provided the HMAC+MD5 algorithm is used.
2819 If the digest_alg is specified this is used to replace the
2820 MD5 with, for example, SHA512. The digest_alg can be
2821 the name of a hashlib algorithm as a string or the algorithm itself.
2822
2823 min_length is the minimal password length (default 4) - IS_STRONG for serious security
2824 error_message is the message if password is too short
2825
2826 Notice that an empty password is accepted but invalid. It will not allow login back.
2827 Stores junk as hashed password.
2828
2829 Specify an algorithm or by default we will use sha512.
2830
2831 Typical available algorithms:
2832 md5, sha1, sha224, sha256, sha384, sha512
2833
2834 If salt, it hashes a password with a salt.
2835 If salt is True, this method will automatically generate one.
2836 Either case it returns an encrypted password string in the following format:
2837
2838 <algorithm>$<salt>$<hash>
2839
2840 Important: hashed password is returned as a LazyCrypt object and computed only if needed.
2841 The LasyCrypt object also knows how to compare itself with an existing salted password
2842
2843 Supports standard algorithms
2844
2845 >>> for alg in ('md5','sha1','sha256','sha384','sha512'):
2846 ... print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
2847 md5$...$...
2848 sha1$...$...
2849 sha256$...$...
2850 sha384$...$...
2851 sha512$...$...
2852
2853 The syntax is always alg$salt$hash
2854
2855 Supports for pbkdf2
2856
2857 >>> alg = 'pbkdf2(1000,20,sha512)'
2858 >>> print str(CRYPT(digest_alg=alg,salt=True)('test')[0])
2859 pbkdf2(1000,20,sha512)$...$...
2860
2861 An optional hmac_key can be specified and it is used as salt prefix
2862
2863 >>> a = str(CRYPT(digest_alg='md5',key='mykey',salt=True)('test')[0])
2864 >>> print a
2865 md5$...$...
2866
2867 Even if the algorithm changes the hash can still be validated
2868
2869 >>> CRYPT(digest_alg='sha1',key='mykey',salt=True)('test')[0] == a
2870 True
2871
2872 If no salt is specified CRYPT can guess the algorithms from length:
2873
2874 >>> a = str(CRYPT(digest_alg='sha1',salt=False)('test')[0])
2875 >>> a
2876 'sha1$$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
2877 >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a
2878 True
2879 >>> CRYPT(digest_alg='sha1',salt=False)('test')[0] == a[6:]
2880 True
2881 >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a
2882 True
2883 >>> CRYPT(digest_alg='md5',salt=False)('test')[0] == a[6:]
2884 True
2885 """
2886
2887 - def __init__(self,
2888 key=None,
2889 digest_alg='pbkdf2(1000,20,sha512)',
2890 min_length=0,
2891 error_message='too short', salt=True):
2892 """
2893 important, digest_alg='md5' is not the default hashing algorithm for
2894 web2py. This is only an example of usage of this function.
2895
2896 The actual hash algorithm is determined from the key which is
2897 generated by web2py in tools.py. This defaults to hmac+sha512.
2898 """
2899 self.key = key
2900 self.digest_alg = digest_alg
2901 self.min_length = min_length
2902 self.error_message = error_message
2903 self.salt = salt
2904
2906 if len(value) < self.min_length:
2907 return ('', translate(self.error_message))
2908 return (LazyCrypt(self, value), None)
2909
2910
2911
2912 lowerset = frozenset(unicode('abcdefghijklmnopqrstuvwxyz'))
2913 upperset = frozenset(unicode('ABCDEFGHIJKLMNOPQRSTUVWXYZ'))
2914 numberset = frozenset(unicode('0123456789'))
2915 sym1set = frozenset(unicode('!@#$%^&*()'))
2916 sym2set = frozenset(unicode('~`-_=+[]{}\\|;:\'",.<>?/'))
2917 otherset = frozenset(
2918 unicode('0123456789abcdefghijklmnopqrstuvwxyz'))
2922 " calculate a simple entropy for a given string "
2923 import math
2924 alphabet = 0
2925 other = set()
2926 seen = set()
2927 lastset = None
2928 if isinstance(string, str):
2929 string = unicode(string, encoding='utf8')
2930 for c in string:
2931
2932 inset = otherset
2933 for cset in (lowerset, upperset, numberset, sym1set, sym2set):
2934 if c in cset:
2935 inset = cset
2936 break
2937
2938 if inset not in seen:
2939 seen.add(inset)
2940 alphabet += len(inset)
2941 elif c not in other:
2942 alphabet += 1
2943 other.add(c)
2944 if inset is not lastset:
2945 alphabet += 1
2946 lastset = cset
2947 entropy = len(
2948 string) * math.log(alphabet) / 0.6931471805599453
2949 return round(entropy, 2)
2950
2953 """
2954 example::
2955
2956 INPUT(_type='password', _name='passwd',
2957 requires=IS_STRONG(min=10, special=2, upper=2))
2958
2959 enforces complexity requirements on a field
2960
2961 >>> IS_STRONG(es=True)('Abcd1234')
2962 ('Abcd1234',
2963 'Must include at least 1 of the following: ~!@#$%^&*()_+-=?<>,.:;{}[]|')
2964 >>> IS_STRONG(es=True)('Abcd1234!')
2965 ('Abcd1234!', None)
2966 >>> IS_STRONG(es=True, entropy=1)('a')
2967 ('a', None)
2968 >>> IS_STRONG(es=True, entropy=1, min=2)('a')
2969 ('a', 'Minimum length is 2')
2970 >>> IS_STRONG(es=True, entropy=100)('abc123')
2971 ('abc123', 'Entropy (32.35) less than required (100)')
2972 >>> IS_STRONG(es=True, entropy=100)('and')
2973 ('and', 'Entropy (14.57) less than required (100)')
2974 >>> IS_STRONG(es=True, entropy=100)('aaa')
2975 ('aaa', 'Entropy (14.42) less than required (100)')
2976 >>> IS_STRONG(es=True, entropy=100)('a1d')
2977 ('a1d', 'Entropy (15.97) less than required (100)')
2978 >>> IS_STRONG(es=True, entropy=100)('añd')
2979 ('a\\xc3\\xb1d', 'Entropy (18.13) less than required (100)')
2980
2981 """
2982
2983 - def __init__(self, min=None, max=None, upper=None, lower=None, number=None,
2984 entropy=None,
2985 special=None, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
2986 invalid=' "', error_message=None, es=False):
2987 self.entropy = entropy
2988 if entropy is None:
2989
2990 self.min = 8 if min is None else min
2991 self.max = max
2992 self.upper = 1 if upper is None else upper
2993 self.lower = 1 if lower is None else lower
2994 self.number = 1 if number is None else number
2995 self.special = 1 if special is None else special
2996 else:
2997
2998 self.min = min
2999 self.max = max
3000 self.upper = upper
3001 self.lower = lower
3002 self.number = number
3003 self.special = special
3004 self.specials = specials
3005 self.invalid = invalid
3006 self.error_message = error_message
3007 self.estring = es
3008
3010 failures = []
3011 if value and len(value) == value.count('*') > 4:
3012 return (value, None)
3013 if self.entropy is not None:
3014 entropy = calc_entropy(value)
3015 if entropy < self.entropy:
3016 failures.append(translate("Entropy (%(have)s) less than required (%(need)s)")
3017 % dict(have=entropy, need=self.entropy))
3018 if type(self.min) == int and self.min > 0:
3019 if not len(value) >= self.min:
3020 failures.append(translate("Minimum length is %s") % self.min)
3021 if type(self.max) == int and self.max > 0:
3022 if not len(value) <= self.max:
3023 failures.append(translate("Maximum length is %s") % self.max)
3024 if type(self.special) == int:
3025 all_special = [ch in value for ch in self.specials]
3026 if self.special > 0:
3027 if not all_special.count(True) >= self.special:
3028 failures.append(translate("Must include at least %s of the following: %s")
3029 % (self.special, self.specials))
3030 if self.invalid:
3031 all_invalid = [ch in value for ch in self.invalid]
3032 if all_invalid.count(True) > 0:
3033 failures.append(translate("May not contain any of the following: %s")
3034 % self.invalid)
3035 if type(self.upper) == int:
3036 all_upper = re.findall("[A-Z]", value)
3037 if self.upper > 0:
3038 if not len(all_upper) >= self.upper:
3039 failures.append(translate("Must include at least %s upper case")
3040 % str(self.upper))
3041 else:
3042 if len(all_upper) > 0:
3043 failures.append(
3044 translate("May not include any upper case letters"))
3045 if type(self.lower) == int:
3046 all_lower = re.findall("[a-z]", value)
3047 if self.lower > 0:
3048 if not len(all_lower) >= self.lower:
3049 failures.append(translate("Must include at least %s lower case")
3050 % str(self.lower))
3051 else:
3052 if len(all_lower) > 0:
3053 failures.append(
3054 translate("May not include any lower case letters"))
3055 if type(self.number) == int:
3056 all_number = re.findall("[0-9]", value)
3057 if self.number > 0:
3058 numbers = "number"
3059 if self.number > 1:
3060 numbers = "numbers"
3061 if not len(all_number) >= self.number:
3062 failures.append(translate("Must include at least %s %s")
3063 % (str(self.number), numbers))
3064 else:
3065 if len(all_number) > 0:
3066 failures.append(translate("May not include any numbers"))
3067 if len(failures) == 0:
3068 return (value, None)
3069 if not self.error_message:
3070 if self.estring:
3071 return (value, '|'.join(failures))
3072 from html import XML
3073 return (value, XML('<br />'.join(failures)))
3074 else:
3075 return (value, translate(self.error_message))
3076
3079
3080 REGEX_W = re.compile('\w+')
3081
3084
3091
3094 """
3095 Checks if file uploaded through file input was saved in one of selected
3096 image formats and has dimensions (width and height) within given boundaries.
3097
3098 Does *not* check for maximum file size (use IS_LENGTH for that). Returns
3099 validation failure if no data was uploaded.
3100
3101 Supported file formats: BMP, GIF, JPEG, PNG.
3102
3103 Code parts taken from
3104 http://mail.python.org/pipermail/python-list/2007-June/617126.html
3105
3106 Arguments:
3107
3108 extensions: iterable containing allowed *lowercase* image file extensions
3109 ('jpg' extension of uploaded file counts as 'jpeg')
3110 maxsize: iterable containing maximum width and height of the image
3111 minsize: iterable containing minimum width and height of the image
3112
3113 Use (-1, -1) as minsize to pass image size check.
3114
3115 Examples::
3116
3117 #Check if uploaded file is in any of supported image formats:
3118 INPUT(_type='file', _name='name', requires=IS_IMAGE())
3119
3120 #Check if uploaded file is either JPEG or PNG:
3121 INPUT(_type='file', _name='name',
3122 requires=IS_IMAGE(extensions=('jpeg', 'png')))
3123
3124 #Check if uploaded file is PNG with maximum size of 200x200 pixels:
3125 INPUT(_type='file', _name='name',
3126 requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200)))
3127 """
3128
3129 - def __init__(self,
3130 extensions=('bmp', 'gif', 'jpeg', 'png'),
3131 maxsize=(10000, 10000),
3132 minsize=(0, 0),
3133 error_message='invalid image'):
3134
3135 self.extensions = extensions
3136 self.maxsize = maxsize
3137 self.minsize = minsize
3138 self.error_message = error_message
3139
3141 try:
3142 extension = value.filename.rfind('.')
3143 assert extension >= 0
3144 extension = value.filename[extension + 1:].lower()
3145 if extension == 'jpg':
3146 extension = 'jpeg'
3147 assert extension in self.extensions
3148 if extension == 'bmp':
3149 width, height = self.__bmp(value.file)
3150 elif extension == 'gif':
3151 width, height = self.__gif(value.file)
3152 elif extension == 'jpeg':
3153 width, height = self.__jpeg(value.file)
3154 elif extension == 'png':
3155 width, height = self.__png(value.file)
3156 else:
3157 width = -1
3158 height = -1
3159 assert self.minsize[0] <= width <= self.maxsize[0] \
3160 and self.minsize[1] <= height <= self.maxsize[1]
3161 value.file.seek(0)
3162 return (value, None)
3163 except:
3164 return (value, translate(self.error_message))
3165
3166 - def __bmp(self, stream):
3171
3172 - def __gif(self, stream):
3178
3180 if stream.read(2) == '\xFF\xD8':
3181 while True:
3182 (marker, code, length) = struct.unpack("!BBH", stream.read(4))
3183 if marker != 0xFF:
3184 break
3185 elif code >= 0xC0 and code <= 0xC3:
3186 return tuple(reversed(
3187 struct.unpack("!xHH", stream.read(5))))
3188 else:
3189 stream.read(length - 2)
3190 return (-1, -1)
3191
3192 - def __png(self, stream):
3198
3201 """
3202 Checks if name and extension of file uploaded through file input matches
3203 given criteria.
3204
3205 Does *not* ensure the file type in any way. Returns validation failure
3206 if no data was uploaded.
3207
3208 Arguments::
3209
3210 filename: filename (before dot) regex
3211 extension: extension (after dot) regex
3212 lastdot: which dot should be used as a filename / extension separator:
3213 True means last dot, eg. file.png -> file / png
3214 False means first dot, eg. file.tar.gz -> file / tar.gz
3215 case: 0 - keep the case, 1 - transform the string into lowercase (default),
3216 2 - transform the string into uppercase
3217
3218 If there is no dot present, extension checks will be done against empty
3219 string and filename checks against whole value.
3220
3221 Examples::
3222
3223 #Check if file has a pdf extension (case insensitive):
3224 INPUT(_type='file', _name='name',
3225 requires=IS_UPLOAD_FILENAME(extension='pdf'))
3226
3227 #Check if file has a tar.gz extension and name starting with backup:
3228 INPUT(_type='file', _name='name',
3229 requires=IS_UPLOAD_FILENAME(filename='backup.*',
3230 extension='tar.gz', lastdot=False))
3231
3232 #Check if file has no extension and name matching README
3233 #(case sensitive):
3234 INPUT(_type='file', _name='name',
3235 requires=IS_UPLOAD_FILENAME(filename='^README$',
3236 extension='^$', case=0))
3237 """
3238
3239 - def __init__(self, filename=None, extension=None, lastdot=True, case=1,
3240 error_message='enter valid filename'):
3241 if isinstance(filename, str):
3242 filename = re.compile(filename)
3243 if isinstance(extension, str):
3244 extension = re.compile(extension)
3245 self.filename = filename
3246 self.extension = extension
3247 self.lastdot = lastdot
3248 self.case = case
3249 self.error_message = error_message
3250
3252 try:
3253 string = value.filename
3254 except:
3255 return (value, translate(self.error_message))
3256 if self.case == 1:
3257 string = string.lower()
3258 elif self.case == 2:
3259 string = string.upper()
3260 if self.lastdot:
3261 dot = string.rfind('.')
3262 else:
3263 dot = string.find('.')
3264 if dot == -1:
3265 dot = len(string)
3266 if self.filename and not self.filename.match(string[:dot]):
3267 return (value, translate(self.error_message))
3268 elif self.extension and not self.extension.match(string[dot + 1:]):
3269 return (value, translate(self.error_message))
3270 else:
3271 return (value, None)
3272
3275 """
3276 Checks if field's value is an IP version 4 address in decimal form. Can
3277 be set to force addresses from certain range.
3278
3279 IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411
3280
3281 Arguments:
3282
3283 minip: lowest allowed address; accepts:
3284 str, eg. 192.168.0.1
3285 list or tuple of octets, eg. [192, 168, 0, 1]
3286 maxip: highest allowed address; same as above
3287 invert: True to allow addresses only from outside of given range; note
3288 that range boundaries are not matched this way
3289 is_localhost: localhost address treatment:
3290 None (default): indifferent
3291 True (enforce): query address must match localhost address
3292 (127.0.0.1)
3293 False (forbid): query address must not match localhost
3294 address
3295 is_private: same as above, except that query address is checked against
3296 two address ranges: 172.16.0.0 - 172.31.255.255 and
3297 192.168.0.0 - 192.168.255.255
3298 is_automatic: same as above, except that query address is checked against
3299 one address range: 169.254.0.0 - 169.254.255.255
3300
3301 Minip and maxip may also be lists or tuples of addresses in all above
3302 forms (str, int, list / tuple), allowing setup of multiple address ranges:
3303
3304 minip = (minip1, minip2, ... minipN)
3305 | | |
3306 | | |
3307 maxip = (maxip1, maxip2, ... maxipN)
3308
3309 Longer iterable will be truncated to match length of shorter one.
3310
3311 Examples::
3312
3313 #Check for valid IPv4 address:
3314 INPUT(_type='text', _name='name', requires=IS_IPV4())
3315
3316 #Check for valid IPv4 address belonging to specific range:
3317 INPUT(_type='text', _name='name',
3318 requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255'))
3319
3320 #Check for valid IPv4 address belonging to either 100.110.0.0 -
3321 #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range:
3322 INPUT(_type='text', _name='name',
3323 requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'),
3324 maxip=('100.110.255.255', '200.50.0.255')))
3325
3326 #Check for valid IPv4 address belonging to private address space:
3327 INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True))
3328
3329 #Check for valid IPv4 address that is not a localhost address:
3330 INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False))
3331
3332 >>> IS_IPV4()('1.2.3.4')
3333 ('1.2.3.4', None)
3334 >>> IS_IPV4()('255.255.255.255')
3335 ('255.255.255.255', None)
3336 >>> IS_IPV4()('1.2.3.4 ')
3337 ('1.2.3.4 ', 'enter valid IPv4 address')
3338 >>> IS_IPV4()('1.2.3.4.5')
3339 ('1.2.3.4.5', 'enter valid IPv4 address')
3340 >>> IS_IPV4()('123.123')
3341 ('123.123', 'enter valid IPv4 address')
3342 >>> IS_IPV4()('1111.2.3.4')
3343 ('1111.2.3.4', 'enter valid IPv4 address')
3344 >>> IS_IPV4()('0111.2.3.4')
3345 ('0111.2.3.4', 'enter valid IPv4 address')
3346 >>> IS_IPV4()('256.2.3.4')
3347 ('256.2.3.4', 'enter valid IPv4 address')
3348 >>> IS_IPV4()('300.2.3.4')
3349 ('300.2.3.4', 'enter valid IPv4 address')
3350 >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4')
3351 ('1.2.3.4', None)
3352 >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
3353 ('1.2.3.4', 'bad ip')
3354 >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1')
3355 ('127.0.0.1', None)
3356 >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4')
3357 ('1.2.3.4', 'enter valid IPv4 address')
3358 >>> IS_IPV4(is_localhost=True)('127.0.0.1')
3359 ('127.0.0.1', None)
3360 >>> IS_IPV4(is_localhost=True)('1.2.3.4')
3361 ('1.2.3.4', 'enter valid IPv4 address')
3362 >>> IS_IPV4(is_localhost=False)('127.0.0.1')
3363 ('127.0.0.1', 'enter valid IPv4 address')
3364 >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
3365 ('127.0.0.1', 'enter valid IPv4 address')
3366 """
3367
3368 regex = re.compile(
3369 '^(([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]?\d|1\d\d|2[0-4]\d|25[0-5])$')
3370 numbers = (16777216, 65536, 256, 1)
3371 localhost = 2130706433
3372 private = ((2886729728L, 2886795263L), (3232235520L, 3232301055L))
3373 automatic = (2851995648L, 2852061183L)
3374
3375 - def __init__(
3376 self,
3377 minip='0.0.0.0',
3378 maxip='255.255.255.255',
3379 invert=False,
3380 is_localhost=None,
3381 is_private=None,
3382 is_automatic=None,
3383 error_message='enter valid IPv4 address'):
3384 for n, value in enumerate((minip, maxip)):
3385 temp = []
3386 if isinstance(value, str):
3387 temp.append(value.split('.'))
3388 elif isinstance(value, (list, tuple)):
3389 if len(value) == len(filter(lambda item: isinstance(item, int), value)) == 4:
3390 temp.append(value)
3391 else:
3392 for item in value:
3393 if isinstance(item, str):
3394 temp.append(item.split('.'))
3395 elif isinstance(item, (list, tuple)):
3396 temp.append(item)
3397 numbers = []
3398 for item in temp:
3399 number = 0
3400 for i, j in zip(self.numbers, item):
3401 number += i * int(j)
3402 numbers.append(number)
3403 if n == 0:
3404 self.minip = numbers
3405 else:
3406 self.maxip = numbers
3407 self.invert = invert
3408 self.is_localhost = is_localhost
3409 self.is_private = is_private
3410 self.is_automatic = is_automatic
3411 self.error_message = error_message
3412
3414 if self.regex.match(value):
3415 number = 0
3416 for i, j in zip(self.numbers, value.split('.')):
3417 number += i * int(j)
3418 ok = False
3419 for bottom, top in zip(self.minip, self.maxip):
3420 if self.invert != (bottom <= number <= top):
3421 ok = True
3422 if not (self.is_localhost is None or self.is_localhost ==
3423 (number == self.localhost)):
3424 ok = False
3425 if not (self.is_private is None or self.is_private ==
3426 (sum([number[0] <= number <= number[1] for number in self.private]) > 0)):
3427 ok = False
3428 if not (self.is_automatic is None or self.is_automatic ==
3429 (self.automatic[0] <= number <= self.automatic[1])):
3430 ok = False
3431 if ok:
3432 return (value, None)
3433 return (value, translate(self.error_message))
3434
3436 """
3437 Checks if field's value is an IP version 6 address. First attempts to
3438 use the ipaddress library and falls back to contrib/ipaddr.py from Google
3439 (https://code.google.com/p/ipaddr-py/)
3440
3441 Arguments:
3442 is_private: None (default): indifferent
3443 True (enforce): address must be in fc00::/7 range
3444 False (forbid): address must NOT be in fc00::/7 range
3445 is_link_local: Same as above but uses fe80::/10 range
3446 is_reserved: Same as above but uses IETF reserved range
3447 is_mulicast: Same as above but uses ff00::/8 range
3448 is_routeable: Similar to above but enforces not private, link_local,
3449 reserved or multicast
3450 is_6to4: Same as above but uses 2002::/16 range
3451 is_teredo: Same as above but uses 2001::/32 range
3452 subnets: value must be a member of at least one from list of subnets
3453
3454 Examples:
3455
3456 #Check for valid IPv6 address:
3457 INPUT(_type='text', _name='name', requires=IS_IPV6())
3458
3459 #Check for valid IPv6 address is a link_local address:
3460 INPUT(_type='text', _name='name', requires=IS_IPV6(is_link_local=True))
3461
3462 #Check for valid IPv6 address that is Internet routeable:
3463 INPUT(_type='text', _name='name', requires=IS_IPV6(is_routeable=True))
3464
3465 #Check for valid IPv6 address in specified subnet:
3466 INPUT(_type='text', _name='name', requires=IS_IPV6(subnets=['2001::/32'])
3467
3468 >>> IS_IPV6()('fe80::126c:8ffa:fe22:b3af')
3469 ('fe80::126c:8ffa:fe22:b3af', None)
3470 >>> IS_IPV6()('192.168.1.1')
3471 ('192.168.1.1', 'enter valid IPv6 address')
3472 >>> IS_IPV6(error_message='bad ip')('192.168.1.1')
3473 ('192.168.1.1', 'bad ip')
3474 >>> IS_IPV6(is_link_local=True)('fe80::126c:8ffa:fe22:b3af')
3475 ('fe80::126c:8ffa:fe22:b3af', None)
3476 >>> IS_IPV6(is_link_local=False)('fe80::126c:8ffa:fe22:b3af')
3477 ('fe80::126c:8ffa:fe22:b3af', 'enter valid IPv6 address')
3478 >>> IS_IPV6(is_link_local=True)('2001::126c:8ffa:fe22:b3af')
3479 ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address')
3480 >>> IS_IPV6(is_multicast=True)('2001::126c:8ffa:fe22:b3af')
3481 ('2001::126c:8ffa:fe22:b3af', 'enter valid IPv6 address')
3482 >>> IS_IPV6(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
3483 ('ff00::126c:8ffa:fe22:b3af', None)
3484 >>> IS_IPV6(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
3485 ('2001::126c:8ffa:fe22:b3af', None)
3486 >>> IS_IPV6(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
3487 ('ff00::126c:8ffa:fe22:b3af', 'enter valid IPv6 address')
3488 >>> IS_IPV6(subnets='2001::/32')('2001::8ffa:fe22:b3af')
3489 ('2001::8ffa:fe22:b3af', None)
3490 >>> IS_IPV6(subnets='fb00::/8')('2001::8ffa:fe22:b3af')
3491 ('2001::8ffa:fe22:b3af', 'enter valid IPv6 address')
3492 >>> IS_IPV6(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af')
3493 ('2001::8ffa:fe22:b3af', None)
3494 >>> IS_IPV6(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
3495 ('2001::8ffa:fe22:b3af', 'invalid subnet provided')
3496
3497 """
3498
3499 - def __init__(
3500 self,
3501 is_private=None,
3502 is_link_local=None,
3503 is_reserved=None,
3504 is_multicast=None,
3505 is_routeable=None,
3506 is_6to4=None,
3507 is_teredo=None,
3508 subnets=None,
3509 error_message='enter valid IPv6 address'):
3510 self.is_private = is_private
3511 self.is_link_local = is_link_local
3512 self.is_reserved = is_reserved
3513 self.is_multicast = is_multicast
3514 self.is_routeable = is_routeable
3515 self.is_6to4 = is_6to4
3516 self.is_teredo = is_teredo
3517 self.subnets = subnets
3518 self.error_message = error_message
3519
3521 try:
3522 import ipaddress
3523 except ImportError:
3524 from contrib import ipaddr as ipaddress
3525
3526 try:
3527 ip = ipaddress.IPv6Address(value)
3528 ok = True
3529 except ipaddress.AddressValueError:
3530 return (value, translate(self.error_message))
3531
3532 if self.subnets:
3533
3534 ok = False
3535 if isinstance(self.subnets, str):
3536 self.subnets = [self.subnets]
3537 for network in self.subnets:
3538 try:
3539 ipnet = ipaddress.IPv6Network(network)
3540 except (ipaddress.NetmaskValueError, ipaddress.AddressValueError):
3541 return (value, translate('invalid subnet provided'))
3542 if ip in ipnet:
3543 ok = True
3544
3545 if self.is_routeable:
3546 self.is_private = False
3547 self.is_link_local = False
3548 self.is_reserved = False
3549 self.is_multicast = False
3550
3551 if not (self.is_private is None or self.is_private ==
3552 ip.is_private):
3553 ok = False
3554 if not (self.is_link_local is None or self.is_link_local ==
3555 ip.is_link_local):
3556 ok = False
3557 if not (self.is_reserved is None or self.is_reserved ==
3558 ip.is_reserved):
3559 ok = False
3560 if not (self.is_multicast is None or self.is_multicast ==
3561 ip.is_multicast):
3562 ok = False
3563 if not (self.is_6to4 is None or self.is_6to4 ==
3564 ip.is_6to4):
3565 ok = False
3566 if not (self.is_teredo is None or self.is_teredo ==
3567 ip.is_teredo):
3568 ok = False
3569
3570 if ok:
3571 return (value, None)
3572
3573 return (value, translate(self.error_message))
3574
3577 """
3578 Checks if field's value is an IP Address (v4 or v6). Can be set to force
3579 addresses from within a specific range. Checks are done with the correct
3580 IS_IPV4 and IS_IPV6 validators.
3581
3582 Uses ipaddress library if found, falls back to PEP-3144 ipaddr.py from
3583 Google (in contrib).
3584
3585 Universal arguments:
3586
3587 minip: lowest allowed address; accepts:
3588 str, eg. 192.168.0.1
3589 list or tuple of octets, eg. [192, 168, 0, 1]
3590 maxip: highest allowed address; same as above
3591 invert: True to allow addresses only from outside of given range; note
3592 that range boundaries are not matched this way
3593
3594 IPv4 specific arguments:
3595
3596 is_localhost: localhost address treatment:
3597 None (default): indifferent
3598 True (enforce): query address must match localhost address
3599 (127.0.0.1)
3600 False (forbid): query address must not match localhost
3601 address
3602 is_private: same as above, except that query address is checked against
3603 two address ranges: 172.16.0.0 - 172.31.255.255 and
3604 192.168.0.0 - 192.168.255.255
3605 is_automatic: same as above, except that query address is checked against
3606 one address range: 169.254.0.0 - 169.254.255.255
3607 is_ipv4: None (default): indifferent
3608 True (enforce): must be an IPv4 address
3609 False (forbid): must NOT be an IPv4 address
3610
3611 IPv6 specific arguments:
3612
3613 is_link_local: Same as above but uses fe80::/10 range
3614 is_reserved: Same as above but uses IETF reserved range
3615 is_mulicast: Same as above but uses ff00::/8 range
3616 is_routeable: Similar to above but enforces not private, link_local,
3617 reserved or multicast
3618 is_6to4: Same as above but uses 2002::/16 range
3619 is_teredo: Same as above but uses 2001::/32 range
3620 subnets: value must be a member of at least one from list of subnets
3621 is_ipv6: None (default): indifferent
3622 True (enforce): must be an IPv6 address
3623 False (forbid): must NOT be an IPv6 address
3624
3625 Minip and maxip may also be lists or tuples of addresses in all above
3626 forms (str, int, list / tuple), allowing setup of multiple address ranges:
3627
3628 minip = (minip1, minip2, ... minipN)
3629 | | |
3630 | | |
3631 maxip = (maxip1, maxip2, ... maxipN)
3632
3633 Longer iterable will be truncated to match length of shorter one.
3634
3635 >>> IS_IPADDRESS()('192.168.1.5')
3636 ('192.168.1.5', None)
3637 >>> IS_IPADDRESS(is_ipv6=False)('192.168.1.5')
3638 ('192.168.1.5', None)
3639 >>> IS_IPADDRESS()('255.255.255.255')
3640 ('255.255.255.255', None)
3641 >>> IS_IPADDRESS()('192.168.1.5 ')
3642 ('192.168.1.5 ', 'enter valid IP address')
3643 >>> IS_IPADDRESS()('192.168.1.1.5')
3644 ('192.168.1.1.5', 'enter valid IP address')
3645 >>> IS_IPADDRESS()('123.123')
3646 ('123.123', 'enter valid IP address')
3647 >>> IS_IPADDRESS()('1111.2.3.4')
3648 ('1111.2.3.4', 'enter valid IP address')
3649 >>> IS_IPADDRESS()('0111.2.3.4')
3650 ('0111.2.3.4', 'enter valid IP address')
3651 >>> IS_IPADDRESS()('256.2.3.4')
3652 ('256.2.3.4', 'enter valid IP address')
3653 >>> IS_IPADDRESS()('300.2.3.4')
3654 ('300.2.3.4', 'enter valid IP address')
3655 >>> IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100')
3656 ('192.168.1.100', None)
3657 >>> IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
3658 ('1.2.3.4', 'bad ip')
3659 >>> IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1')
3660 ('127.0.0.1', None)
3661 >>> IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4')
3662 ('192.168.1.4', 'enter valid IP address')
3663 >>> IS_IPADDRESS(is_localhost=True)('127.0.0.1')
3664 ('127.0.0.1', None)
3665 >>> IS_IPADDRESS(is_localhost=True)('192.168.1.10')
3666 ('192.168.1.10', 'enter valid IP address')
3667 >>> IS_IPADDRESS(is_localhost=False)('127.0.0.1')
3668 ('127.0.0.1', 'enter valid IP address')
3669 >>> IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
3670 ('127.0.0.1', 'enter valid IP address')
3671
3672 >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af')
3673 ('fe80::126c:8ffa:fe22:b3af', None)
3674 >>> IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af')
3675 ('fe80::126c:8ffa:fe22:b3af', None)
3676 >>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af ')
3677 ('fe80::126c:8ffa:fe22:b3af ', 'enter valid IP address')
3678 >>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af')
3679 ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
3680 >>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1')
3681 ('192.168.1.1', 'enter valid IP address')
3682 >>> IS_IPADDRESS(is_ipv6=True, error_message='bad ip')('192.168.1.1')
3683 ('192.168.1.1', 'bad ip')
3684 >>> IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af')
3685 ('fe80::126c:8ffa:fe22:b3af', None)
3686 >>> IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af')
3687 ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
3688 >>> IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af')
3689 ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address')
3690 >>> IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af')
3691 ('2001::126c:8ffa:fe22:b3af', 'enter valid IP address')
3692 >>> IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
3693 ('ff00::126c:8ffa:fe22:b3af', None)
3694 >>> IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
3695 ('2001::126c:8ffa:fe22:b3af', None)
3696 >>> IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
3697 ('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address')
3698 >>> IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af')
3699 ('2001::8ffa:fe22:b3af', None)
3700 >>> IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af')
3701 ('2001::8ffa:fe22:b3af', 'enter valid IP address')
3702 >>> IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af')
3703 ('2001::8ffa:fe22:b3af', None)
3704 >>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
3705 ('2001::8ffa:fe22:b3af', 'invalid subnet provided')
3706 """
3707 - def __init__(
3708 self,
3709 minip='0.0.0.0',
3710 maxip='255.255.255.255',
3711 invert=False,
3712 is_localhost=None,
3713 is_private=None,
3714 is_automatic=None,
3715 is_ipv4=None,
3716 is_link_local=None,
3717 is_reserved=None,
3718 is_multicast=None,
3719 is_routeable=None,
3720 is_6to4=None,
3721 is_teredo=None,
3722 subnets=None,
3723 is_ipv6=None,
3724 error_message='enter valid IP address'):
3725 self.minip = minip,
3726 self.maxip = maxip,
3727 self.invert = invert
3728 self.is_localhost = is_localhost
3729 self.is_private = is_private
3730 self.is_automatic = is_automatic
3731 self.is_ipv4 = is_ipv4
3732 self.is_private = is_private
3733 self.is_link_local = is_link_local
3734 self.is_reserved = is_reserved
3735 self.is_multicast = is_multicast
3736 self.is_routeable = is_routeable
3737 self.is_6to4 = is_6to4
3738 self.is_teredo = is_teredo
3739 self.subnets = subnets
3740 self.is_ipv6 = is_ipv6
3741 self.error_message = error_message
3742
3744 try:
3745 import ipaddress
3746 except ImportError:
3747 from contrib import ipaddr as ipaddress
3748
3749 try:
3750 ip = ipaddress.ip_address(value)
3751 except ValueError, e:
3752 return (value, translate(self.error_message))
3753
3754 if self.is_ipv4 and isinstance(ip, ipaddress.IPv6Address):
3755 retval = (value, translate(self.error_message))
3756 elif self.is_ipv6 and isinstance(ip, ipaddress.IPv4Address):
3757 retval = (value, translate(self.error_message))
3758 elif self.is_ipv4 or isinstance(ip, ipaddress.IPv4Address):
3759 retval = IS_IPV4(
3760 minip=self.minip,
3761 maxip=self.maxip,
3762 invert=self.invert,
3763 is_localhost=self.is_localhost,
3764 is_private=self.is_private,
3765 is_automatic=self.is_automatic,
3766 error_message=self.error_message
3767 )(value)
3768 elif self.is_ipv6 or isinstance(ip, ipaddress.IPv6Address):
3769 retval = IS_IPV6(
3770 is_private=self.is_private,
3771 is_link_local=self.is_link_local,
3772 is_reserved=self.is_reserved,
3773 is_multicast=self.is_multicast,
3774 is_routeable=self.is_routeable,
3775 is_6to4=self.is_6to4,
3776 is_teredo=self.is_teredo,
3777 subnets=self.subnets,
3778 error_message=self.error_message
3779 )(value)
3780 else:
3781 retval = (value, translate(self.error_message))
3782
3783 return retval
3784
3785
3786 if __name__ == '__main__':
3787 import doctest
3788 doctest.testmod(
3789 optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS)
3790