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