1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Thanks to ga2arch for help with IS_IN_DB and IS_NOT_IN_DB on GAE
10 """
11
12 import os
13 import re
14 import datetime
15 import time
16 import cgi
17 import urllib
18 import struct
19 import decimal
20 import unicodedata
21 from cStringIO import StringIO
22 from utils import simple_hash, hmac_hash
23
24 __all__ = [
25 'CLEANUP',
26 'CRYPT',
27 'IS_ALPHANUMERIC',
28 'IS_DATE_IN_RANGE',
29 'IS_DATE',
30 'IS_DATETIME_IN_RANGE',
31 'IS_DATETIME',
32 'IS_DECIMAL_IN_RANGE',
33 'IS_EMAIL',
34 'IS_EMPTY_OR',
35 'IS_EXPR',
36 'IS_FLOAT_IN_RANGE',
37 'IS_IMAGE',
38 'IS_IN_DB',
39 'IS_IN_SET',
40 'IS_INT_IN_RANGE',
41 'IS_IPV4',
42 'IS_LENGTH',
43 'IS_LIST_OF',
44 'IS_LOWER',
45 'IS_MATCH',
46 'IS_EQUAL_TO',
47 'IS_NOT_EMPTY',
48 'IS_NOT_IN_DB',
49 'IS_NULL_OR',
50 'IS_SLUG',
51 'IS_STRONG',
52 'IS_TIME',
53 'IS_UPLOAD_FILENAME',
54 'IS_UPPER',
55 'IS_URL',
56 ]
57
64
66 return (str(x[1]).upper()>str(y[1]).upper() and 1) or -1
67
69 """
70 Root for all validators, mainly for documentation purposes.
71
72 Validators are classes used to validate input fields (including forms
73 generated from database tables).
74
75 Here is an example of using a validator with a FORM::
76
77 INPUT(_name='a', requires=IS_INT_IN_RANGE(0, 10))
78
79 Here is an example of how to require a validator for a table field::
80
81 db.define_table('person', SQLField('name'))
82 db.person.name.requires=IS_NOT_EMPTY()
83
84 Validators are always assigned using the requires attribute of a field. A
85 field can have a single validator or multiple validators. Multiple
86 validators are made part of a list::
87
88 db.person.name.requires=[IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'person.id')]
89
90 Validators are called by the function accepts on a FORM or other HTML
91 helper object that contains a form. They are always called in the order in
92 which they are listed.
93
94 Built-in validators have constructors that take the optional argument error
95 message which allows you to change the default error message.
96 Here is an example of a validator on a database table::
97
98 db.person.name.requires=IS_NOT_EMPTY(error_message=T('fill this'))
99
100 where we have used the translation operator T to allow for
101 internationalization.
102
103 Notice that default error messages are not translated.
104 """
105
112
113
115 """
116 example::
117
118 INPUT(_type='text', _name='name', requires=IS_MATCH('.+'))
119
120 the argument of IS_MATCH is a regular expression::
121
122 >>> IS_MATCH('.+')('hello')
123 ('hello', None)
124
125 >>> IS_MATCH('.+')('')
126 ('', 'invalid expression')
127 """
128
129 - def __init__(self, expression, error_message='invalid expression', strict=True):
130 if strict:
131 if not expression.endswith('$'):
132 expression = '(%s)$' % expression
133 self.regex = re.compile(expression)
134 self.error_message = error_message
135
137 match = self.regex.match(value)
138 if match:
139 return (match.group(), None)
140 return (value, translate(self.error_message))
141
142
144 """
145 example::
146
147 INPUT(_type='text', _name='password')
148 INPUT(_type='text', _name='password2',
149 requires=IS_EQUAL_TO(request.vars.password))
150
151 the argument of IS_EQUAL_TO is a string
152
153 >>> IS_EQUAL_TO('aaa')('aaa')
154 ('aaa', None)
155
156 >>> IS_EQUAL_TO('aaa')('aab')
157 ('aab', 'no match')
158 """
159
160 - def __init__(self, expression, error_message='no match'):
161 self.expression = expression
162 self.error_message = error_message
163
165 if value == self.expression:
166 return (value, None)
167 return (value, translate(self.error_message))
168
169
171 """
172 example::
173
174 INPUT(_type='text', _name='name',
175 requires=IS_EXPR('5 < int(value) < 10'))
176
177 the argument of IS_EXPR must be python condition::
178
179 >>> IS_EXPR('int(value) < 2')('1')
180 ('1', None)
181
182 >>> IS_EXPR('int(value) < 2')('2')
183 ('2', 'invalid expression')
184 """
185
186 - def __init__(self, expression, error_message='invalid expression'):
187 self.expression = expression
188 self.error_message = error_message
189
191 environment = {'value': value}
192 exec '__ret__=' + self.expression in environment
193 if environment['__ret__']:
194 return (value, None)
195 return (value, translate(self.error_message))
196
197
199 """
200 Checks if length of field's value fits between given boundaries. Works
201 for both text and file inputs.
202
203 Arguments:
204
205 maxsize: maximum allowed length / size
206 minsize: minimum allowed length / size
207
208 Examples::
209
210 #Check if text string is shorter than 33 characters:
211 INPUT(_type='text', _name='name', requires=IS_LENGTH(32))
212
213 #Check if password string is longer than 5 characters:
214 INPUT(_type='password', _name='name', requires=IS_LENGTH(minsize=6))
215
216 #Check if uploaded file has size between 1KB and 1MB:
217 INPUT(_type='file', _name='name', requires=IS_LENGTH(1048576, 1024))
218
219 >>> IS_LENGTH()('')
220 ('', None)
221 >>> IS_LENGTH()('1234567890')
222 ('1234567890', None)
223 >>> IS_LENGTH(maxsize=5, minsize=0)('1234567890') # too long
224 ('1234567890', 'enter from 0 to 5 characters')
225 >>> IS_LENGTH(maxsize=50, minsize=20)('1234567890') # too short
226 ('1234567890', 'enter from 20 to 50 characters')
227 """
228
229 - def __init__(self, maxsize=255, minsize=0,
230 error_message='enter from %(min)g to %(max)g characters'):
231 self.maxsize = maxsize
232 self.minsize = minsize
233 self.error_message = error_message
234
236 if isinstance(value, cgi.FieldStorage):
237 if value.file:
238 value.file.seek(0, os.SEEK_END)
239 length = value.file.tell()
240 value.file.seek(0, os.SEEK_SET)
241 else:
242 val = value.value
243 if val:
244 length = len(val)
245 else:
246 length = 0
247 if self.minsize <= length <= self.maxsize:
248 return (value, None)
249 elif isinstance(value, (str, unicode, list)):
250 if self.minsize <= len(value) <= self.maxsize:
251 return (value, None)
252 elif self.minsize <= len(str(value)) <= self.maxsize:
253 try:
254 value.decode('utf8')
255 return (value, None)
256 except:
257 pass
258 return (value, translate(self.error_message) \
259 % dict(min=self.minsize, max=self.maxsize))
260
261
263 """
264 example::
265
266 INPUT(_type='text', _name='name',
267 requires=IS_IN_SET(['max', 'john'],zero=''))
268
269 the argument of IS_IN_SET must be a list or set
270
271 >>> IS_IN_SET(['max', 'john'])('max')
272 ('max', None)
273 >>> IS_IN_SET(['max', 'john'])('massimo')
274 ('massimo', 'value not allowed')
275 >>> IS_IN_SET(['max', 'john'], multiple=True)(('max', 'john'))
276 (('max', 'john'), None)
277 >>> IS_IN_SET(['max', 'john'], multiple=True)(('bill', 'john'))
278 (('bill', 'john'), 'value not allowed')
279 >>> IS_IN_SET(('id1','id2'), ['first label','second label'])('id1') # Traditional way
280 ('id1', None)
281 >>> IS_IN_SET({'id1':'first label', 'id2':'second label'})('id1')
282 ('id1', None)
283 >>> import itertools
284 >>> IS_IN_SET(itertools.chain(['1','3','5'],['2','4','6']))('1')
285 ('1', None)
286 >>> IS_IN_SET([('id1','first label'), ('id2','second label')])('id1') # Redundant way
287 ('id1', None)
288 """
289
290 - def __init__(
291 self,
292 theset,
293 labels=None,
294 error_message='value not allowed',
295 multiple=False,
296 zero='',
297 sort=False,
298 ):
299 self.multiple = multiple
300 if isinstance(theset, dict):
301 self.theset = [str(item) for item in theset]
302 self.labels = theset.values()
303 elif theset and isinstance(theset, (tuple,list)) \
304 and isinstance(theset[0], (tuple,list)) and len(theset[0])==2:
305 self.theset = [str(item) for item,label in theset]
306 self.labels = [str(label) for item,label in theset]
307 else:
308 self.theset = [str(item) for item in theset]
309 self.labels = labels
310 self.error_message = error_message
311 self.zero = zero
312 self.sort = sort
313
315 if not self.labels:
316 items = [(k, k) for (i, k) in enumerate(self.theset)]
317 else:
318 items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
319 if self.sort:
320 items.sort(options_sorter)
321 if zero and self.zero != None and not self.multiple:
322 items.insert(0,('',self.zero))
323 return items
324
326 if self.multiple:
327
328 if isinstance(value, (str,unicode)):
329 values = [value]
330 elif isinstance(value, (tuple, list)):
331 values = value
332 elif not value:
333 values = []
334 else:
335 values = [value]
336 failures = [x for x in values if not x in self.theset]
337 if failures and self.theset:
338 if self.multiple and (value == None or value == ''):
339 return ([], None)
340 return (value, translate(self.error_message))
341 if self.multiple:
342 if isinstance(self.multiple,(tuple,list)) and \
343 not self.multiple[0]<=len(values)<self.multiple[1]:
344 return (values, translate(self.error_message))
345 return (values, None)
346 return (value, None)
347
348
349 regex1 = re.compile('[\w_]+\.[\w_]+')
350 regex2 = re.compile('%\((?P<name>[^\)]+)\)s')
351
352
354 """
355 example::
356
357 INPUT(_type='text', _name='name',
358 requires=IS_IN_DB(db, db.mytable.myfield, zero=''))
359
360 used for reference fields, rendered as a dropbox
361 """
362
363 - def __init__(
364 self,
365 dbset,
366 field,
367 label=None,
368 error_message='value not in database',
369 orderby=None,
370 groupby=None,
371 cache=None,
372 multiple=False,
373 zero='',
374 sort=False,
375 _and=None,
376 ):
377 from dal import Table
378 if isinstance(field,Table): field = field._id
379
380 if hasattr(dbset, 'define_table'):
381 self.dbset = dbset()
382 else:
383 self.dbset = dbset
384 self.field = field
385 (ktable, kfield) = str(self.field).split('.')
386 if not label:
387 label = '%%(%s)s' % kfield
388 if isinstance(label,str):
389 if regex1.match(str(label)):
390 label = '%%(%s)s' % str(label).split('.')[-1]
391 ks = regex2.findall(label)
392 if not kfield in ks:
393 ks += [kfield]
394 fields = ks
395 else:
396 ks = [kfield]
397 fields = 'all'
398 self.fields = fields
399 self.label = label
400 self.ktable = ktable
401 self.kfield = kfield
402 self.ks = ks
403 self.error_message = error_message
404 self.theset = None
405 self.orderby = orderby
406 self.groupby = groupby
407 self.cache = cache
408 self.multiple = multiple
409 self.zero = zero
410 self.sort = sort
411 self._and = _and
412
414 if self._and:
415 self._and.record_id = id
416
418 if self.fields == 'all':
419 fields = [f for f in self.dbset.db[self.ktable]]
420 else:
421 fields = [self.dbset.db[self.ktable][k] for k in self.fields]
422 if self.dbset.db._dbname != 'gae':
423 orderby = self.orderby or reduce(lambda a,b:a|b,fields)
424 groupby = self.groupby
425 dd = dict(orderby=orderby, groupby=groupby, cache=self.cache)
426 records = self.dbset.select(*fields, **dd)
427 else:
428 orderby = self.orderby or reduce(lambda a,b:a|b,(f for f in fields if not f.name=='id'))
429 dd = dict(orderby=orderby, cache=self.cache)
430 records = self.dbset.select(self.dbset.db[self.ktable].ALL, **dd)
431 self.theset = [str(r[self.kfield]) for r in records]
432 if isinstance(self.label,str):
433 self.labels = [self.label % dict(r) for r in records]
434 else:
435 self.labels = [self.label(r) for r in records]
436
445
447 if self.multiple:
448 if isinstance(value,list):
449 values=value
450 elif value:
451 values = [value]
452 else:
453 values = []
454 if isinstance(self.multiple,(tuple,list)) and \
455 not self.multiple[0]<=len(values)<self.multiple[1]:
456 return (values, translate(self.error_message))
457 if not [x for x in values if not x in self.theset]:
458 return (values, None)
459 elif self.theset:
460 if value in self.theset:
461 if self._and:
462 return self._and(value)
463 else:
464 return (value, None)
465 else:
466 (ktable, kfield) = str(self.field).split('.')
467 field = self.dbset.db[ktable][kfield]
468 if self.dbset(field == value).count():
469 if self._and:
470 return self._and(value)
471 else:
472 return (value, None)
473 return (value, translate(self.error_message))
474
475
477 """
478 example::
479
480 INPUT(_type='text', _name='name', requires=IS_NOT_IN_DB(db, db.table))
481
482 makes the field unique
483 """
484
485 - def __init__(
486 self,
487 dbset,
488 field,
489 error_message='value already in database or empty',
490 allowed_override=[],
491 ):
492
493 from dal import Table
494 if isinstance(field,Table): field = field._id
495
496 if hasattr(dbset, 'define_table'):
497 self.dbset = dbset()
498 else:
499 self.dbset = dbset
500 self.field = field
501 self.error_message = error_message
502 self.record_id = 0
503 self.allowed_override = allowed_override
504
507
509 value=str(value)
510 if not value.strip():
511 return (value, translate(self.error_message))
512 if value in self.allowed_override:
513 return (value, None)
514 (tablename, fieldname) = str(self.field).split('.')
515 field = self.dbset.db[tablename][fieldname]
516 rows = self.dbset(field == value).select(limitby=(0, 1))
517 if len(rows) > 0:
518 if isinstance(self.record_id, dict):
519 for f in self.record_id:
520 if str(getattr(rows[0], f)) != str(self.record_id[f]):
521 return (value, translate(self.error_message))
522 elif str(rows[0].id) != str(self.record_id):
523 return (value, translate(self.error_message))
524 return (value, None)
525
526
528 """
529 Determine that the argument is (or can be represented as) an int,
530 and that it falls within the specified range. The range is interpreted
531 in the Pythonic way, so the test is: min <= value < max.
532
533 The minimum and maximum limits can be None, meaning no lower or upper limit,
534 respectively.
535
536 example::
537
538 INPUT(_type='text', _name='name', requires=IS_INT_IN_RANGE(0, 10))
539
540 >>> IS_INT_IN_RANGE(1,5)('4')
541 (4, None)
542 >>> IS_INT_IN_RANGE(1,5)(4)
543 (4, None)
544 >>> IS_INT_IN_RANGE(1,5)(1)
545 (1, None)
546 >>> IS_INT_IN_RANGE(1,5)(5)
547 (5, 'enter an integer between 1 and 4')
548 >>> IS_INT_IN_RANGE(1,5)(5)
549 (5, 'enter an integer between 1 and 4')
550 >>> IS_INT_IN_RANGE(1,5)(3.5)
551 (3, 'enter an integer between 1 and 4')
552 >>> IS_INT_IN_RANGE(None,5)('4')
553 (4, None)
554 >>> IS_INT_IN_RANGE(None,5)('6')
555 (6, 'enter an integer less than or equal to 4')
556 >>> IS_INT_IN_RANGE(1,None)('4')
557 (4, None)
558 >>> IS_INT_IN_RANGE(1,None)('0')
559 (0, 'enter an integer greater than or equal to 1')
560 >>> IS_INT_IN_RANGE()(6)
561 (6, None)
562 >>> IS_INT_IN_RANGE()('abc')
563 ('abc', 'enter an integer')
564 """
565
566 - def __init__(
567 self,
568 minimum=None,
569 maximum=None,
570 error_message=None,
571 ):
572 self.minimum = self.maximum = None
573 if minimum is None:
574 if maximum is None:
575 self.error_message = error_message or 'enter an integer'
576 else:
577 self.maximum = int(maximum)
578 if error_message is None:
579 error_message = 'enter an integer less than or equal to %(max)g'
580 self.error_message = translate(error_message) % dict(max=self.maximum-1)
581 elif maximum is None:
582 self.minimum = int(minimum)
583 if error_message is None:
584 error_message = 'enter an integer greater than or equal to %(min)g'
585 self.error_message = translate(error_message) % dict(min=self.minimum)
586 else:
587 self.minimum = int(minimum)
588 self.maximum = int(maximum)
589 if error_message is None:
590 error_message = 'enter an integer between %(min)g and %(max)g'
591 self.error_message = translate(error_message) \
592 % dict(min=self.minimum, max=self.maximum-1)
593
595 try:
596 fvalue = float(value)
597 value = int(value)
598 if value != fvalue:
599 return (value, self.error_message)
600 if self.minimum is None:
601 if self.maximum is None or value < self.maximum:
602 return (value, None)
603 elif self.maximum is None:
604 if value >= self.minimum:
605 return (value, None)
606 elif self.minimum <= value < self.maximum:
607 return (value, None)
608 except ValueError:
609 pass
610 return (value, self.error_message)
611
612
614 """
615 Determine that the argument is (or can be represented as) a float,
616 and that it falls within the specified inclusive range.
617 The comparison is made with native arithmetic.
618
619 The minimum and maximum limits can be None, meaning no lower or upper limit,
620 respectively.
621
622 example::
623
624 INPUT(_type='text', _name='name', requires=IS_FLOAT_IN_RANGE(0, 10))
625
626 >>> IS_FLOAT_IN_RANGE(1,5)('4')
627 (4.0, None)
628 >>> IS_FLOAT_IN_RANGE(1,5)(4)
629 (4.0, None)
630 >>> IS_FLOAT_IN_RANGE(1,5)(1)
631 (1.0, None)
632 >>> IS_FLOAT_IN_RANGE(1,5)(5.25)
633 (5.25, 'enter a number between 1 and 5')
634 >>> IS_FLOAT_IN_RANGE(1,5)(6.0)
635 (6.0, 'enter a number between 1 and 5')
636 >>> IS_FLOAT_IN_RANGE(1,5)(3.5)
637 (3.5, None)
638 >>> IS_FLOAT_IN_RANGE(1,None)(3.5)
639 (3.5, None)
640 >>> IS_FLOAT_IN_RANGE(None,5)(3.5)
641 (3.5, None)
642 >>> IS_FLOAT_IN_RANGE(1,None)(0.5)
643 (0.5, 'enter a number greater than or equal to 1')
644 >>> IS_FLOAT_IN_RANGE(None,5)(6.5)
645 (6.5, 'enter a number less than or equal to 5')
646 >>> IS_FLOAT_IN_RANGE()(6.5)
647 (6.5, None)
648 >>> IS_FLOAT_IN_RANGE()('abc')
649 ('abc', 'enter a number')
650 """
651
652 - def __init__(
653 self,
654 minimum=None,
655 maximum=None,
656 error_message=None,
657 dot='.'
658 ):
659 self.minimum = self.maximum = None
660 self.dot = dot
661 if minimum is None:
662 if maximum is None:
663 if error_message is None:
664 error_message = 'enter a number'
665 else:
666 self.maximum = float(maximum)
667 if error_message is None:
668 error_message = 'enter a number less than or equal to %(max)g'
669 elif maximum is None:
670 self.minimum = float(minimum)
671 if error_message is None:
672 error_message = 'enter a number greater than or equal to %(min)g'
673 else:
674 self.minimum = float(minimum)
675 self.maximum = float(maximum)
676 if error_message is None:
677 error_message = 'enter a number between %(min)g and %(max)g'
678 self.error_message = translate(error_message) \
679 % dict(min=self.minimum, max=self.maximum)
680
682 try:
683 if self.dot=='.':
684 fvalue = float(value)
685 else:
686 fvalue = float(str(value).replace(self.dot,'.'))
687 if self.minimum is None:
688 if self.maximum is None or fvalue <= self.maximum:
689 return (fvalue, None)
690 elif self.maximum is None:
691 if fvalue >= self.minimum:
692 return (fvalue, None)
693 elif self.minimum <= fvalue <= self.maximum:
694 return (fvalue, None)
695 except (ValueError, TypeError):
696 pass
697 return (value, self.error_message)
698
704
705
707 """
708 Determine that the argument is (or can be represented as) a Python Decimal,
709 and that it falls within the specified inclusive range.
710 The comparison is made with Python Decimal arithmetic.
711
712 The minimum and maximum limits can be None, meaning no lower or upper limit,
713 respectively.
714
715 example::
716
717 INPUT(_type='text', _name='name', requires=IS_DECIMAL_IN_RANGE(0, 10))
718
719 >>> IS_DECIMAL_IN_RANGE(1,5)('4')
720 (Decimal('4'), None)
721 >>> IS_DECIMAL_IN_RANGE(1,5)(4)
722 (Decimal('4'), None)
723 >>> IS_DECIMAL_IN_RANGE(1,5)(1)
724 (Decimal('1'), None)
725 >>> IS_DECIMAL_IN_RANGE(1,5)(5.25)
726 (5.25, 'enter a number between 1 and 5')
727 >>> IS_DECIMAL_IN_RANGE(5.25,6)(5.25)
728 (Decimal('5.25'), None)
729 >>> IS_DECIMAL_IN_RANGE(5.25,6)('5.25')
730 (Decimal('5.25'), None)
731 >>> IS_DECIMAL_IN_RANGE(1,5)(6.0)
732 (6.0, 'enter a number between 1 and 5')
733 >>> IS_DECIMAL_IN_RANGE(1,5)(3.5)
734 (Decimal('3.5'), None)
735 >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(3.5)
736 (Decimal('3.5'), None)
737 >>> IS_DECIMAL_IN_RANGE(1.5,5.5)(6.5)
738 (6.5, 'enter a number between 1.5 and 5.5')
739 >>> IS_DECIMAL_IN_RANGE(1.5,None)(6.5)
740 (Decimal('6.5'), None)
741 >>> IS_DECIMAL_IN_RANGE(1.5,None)(0.5)
742 (0.5, 'enter a number greater than or equal to 1.5')
743 >>> IS_DECIMAL_IN_RANGE(None,5.5)(4.5)
744 (Decimal('4.5'), None)
745 >>> IS_DECIMAL_IN_RANGE(None,5.5)(6.5)
746 (6.5, 'enter a number less than or equal to 5.5')
747 >>> IS_DECIMAL_IN_RANGE()(6.5)
748 (Decimal('6.5'), None)
749 >>> IS_DECIMAL_IN_RANGE(0,99)(123.123)
750 (123.123, 'enter a number between 0 and 99')
751 >>> IS_DECIMAL_IN_RANGE(0,99)('123.123')
752 ('123.123', 'enter a number between 0 and 99')
753 >>> IS_DECIMAL_IN_RANGE(0,99)('12.34')
754 (Decimal('12.34'), None)
755 >>> IS_DECIMAL_IN_RANGE()('abc')
756 ('abc', 'enter a decimal number')
757 """
758
759 - def __init__(
760 self,
761 minimum=None,
762 maximum=None,
763 error_message=None,
764 dot='.'
765 ):
766 self.minimum = self.maximum = None
767 self.dot = dot
768 if minimum is None:
769 if maximum is None:
770 if error_message is None:
771 error_message = 'enter a decimal number'
772 else:
773 self.maximum = decimal.Decimal(str(maximum))
774 if error_message is None:
775 error_message = 'enter a number less than or equal to %(max)g'
776 elif maximum is None:
777 self.minimum = decimal.Decimal(str(minimum))
778 if error_message is None:
779 error_message = 'enter a number greater than or equal to %(min)g'
780 else:
781 self.minimum = decimal.Decimal(str(minimum))
782 self.maximum = decimal.Decimal(str(maximum))
783 if error_message is None:
784 error_message = 'enter a number between %(min)g and %(max)g'
785 self.error_message = translate(error_message) \
786 % dict(min=self.minimum, max=self.maximum)
787
789 try:
790 if isinstance(value,decimal.Decimal):
791 v = value
792 else:
793 v = decimal.Decimal(str(value).replace(self.dot,'.'))
794 if self.minimum is None:
795 if self.maximum is None or v <= self.maximum:
796 return (v, None)
797 elif self.maximum is None:
798 if v >= self.minimum:
799 return (v, None)
800 elif self.minimum <= v <= self.maximum:
801 return (v, None)
802 except (ValueError, TypeError, decimal.InvalidOperation):
803 pass
804 return (value, self.error_message)
805
808
810 "test empty field"
811 if isinstance(value, (str, unicode)):
812 value = value.strip()
813 if empty_regex is not None and empty_regex.match(value):
814 value = ''
815 if value == None or value == '' or value == []:
816 return (value, True)
817 return (value, False)
818
820 """
821 example::
822
823 INPUT(_type='text', _name='name', requires=IS_NOT_EMPTY())
824
825 >>> IS_NOT_EMPTY()(1)
826 (1, None)
827 >>> IS_NOT_EMPTY()(0)
828 (0, None)
829 >>> IS_NOT_EMPTY()('x')
830 ('x', None)
831 >>> IS_NOT_EMPTY()(' x ')
832 ('x', None)
833 >>> IS_NOT_EMPTY()(None)
834 (None, 'enter a value')
835 >>> IS_NOT_EMPTY()('')
836 ('', 'enter a value')
837 >>> IS_NOT_EMPTY()(' ')
838 ('', 'enter a value')
839 >>> IS_NOT_EMPTY()(' \\n\\t')
840 ('', 'enter a value')
841 >>> IS_NOT_EMPTY()([])
842 ([], 'enter a value')
843 >>> IS_NOT_EMPTY(empty_regex='def')('def')
844 ('', 'enter a value')
845 >>> IS_NOT_EMPTY(empty_regex='de[fg]')('deg')
846 ('', 'enter a value')
847 >>> IS_NOT_EMPTY(empty_regex='def')('abc')
848 ('abc', None)
849 """
850
851 - def __init__(self, error_message='enter a value', empty_regex=None):
852 self.error_message = error_message
853 if empty_regex is not None:
854 self.empty_regex = re.compile(empty_regex)
855 else:
856 self.empty_regex = None
857
859 value, empty = is_empty(value, empty_regex=self.empty_regex)
860 if empty:
861 return (value, translate(self.error_message))
862 return (value, None)
863
864
866 """
867 example::
868
869 INPUT(_type='text', _name='name', requires=IS_ALPHANUMERIC())
870
871 >>> IS_ALPHANUMERIC()('1')
872 ('1', None)
873 >>> IS_ALPHANUMERIC()('')
874 ('', None)
875 >>> IS_ALPHANUMERIC()('A_a')
876 ('A_a', None)
877 >>> IS_ALPHANUMERIC()('!')
878 ('!', 'enter only letters, numbers, and underscore')
879 """
880
881 - def __init__(self, error_message='enter only letters, numbers, and underscore'):
883
884
886 """
887 Checks if field's value is a valid email address. Can be set to disallow
888 or force addresses from certain domain(s).
889
890 Email regex adapted from
891 http://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx,
892 generally following the RFCs, except that we disallow quoted strings
893 and permit underscores and leading numerics in subdomain labels
894
895 Arguments:
896
897 - banned: regex text for disallowed address domains
898 - forced: regex text for required address domains
899
900 Both arguments can also be custom objects with a match(value) method.
901
902 Examples::
903
904 #Check for valid email address:
905 INPUT(_type='text', _name='name',
906 requires=IS_EMAIL())
907
908 #Check for valid email address that can't be from a .com domain:
909 INPUT(_type='text', _name='name',
910 requires=IS_EMAIL(banned='^.*\.com(|\..*)$'))
911
912 #Check for valid email address that must be from a .edu domain:
913 INPUT(_type='text', _name='name',
914 requires=IS_EMAIL(forced='^.*\.edu(|\..*)$'))
915
916 >>> IS_EMAIL()('a@b.com')
917 ('a@b.com', None)
918 >>> IS_EMAIL()('abc@def.com')
919 ('abc@def.com', None)
920 >>> IS_EMAIL()('abc@3def.com')
921 ('abc@3def.com', None)
922 >>> IS_EMAIL()('abc@def.us')
923 ('abc@def.us', None)
924 >>> IS_EMAIL()('abc@d_-f.us')
925 ('abc@d_-f.us', None)
926 >>> IS_EMAIL()('@def.com') # missing name
927 ('@def.com', 'enter a valid email address')
928 >>> IS_EMAIL()('"abc@def".com') # quoted name
929 ('"abc@def".com', 'enter a valid email address')
930 >>> IS_EMAIL()('abc+def.com') # no @
931 ('abc+def.com', 'enter a valid email address')
932 >>> IS_EMAIL()('abc@def.x') # one-char TLD
933 ('abc@def.x', 'enter a valid email address')
934 >>> IS_EMAIL()('abc@def.12') # numeric TLD
935 ('abc@def.12', 'enter a valid email address')
936 >>> IS_EMAIL()('abc@def..com') # double-dot in domain
937 ('abc@def..com', 'enter a valid email address')
938 >>> IS_EMAIL()('abc@.def.com') # dot starts domain
939 ('abc@.def.com', 'enter a valid email address')
940 >>> IS_EMAIL()('abc@def.c_m') # underscore in TLD
941 ('abc@def.c_m', 'enter a valid email address')
942 >>> IS_EMAIL()('NotAnEmail') # missing @
943 ('NotAnEmail', 'enter a valid email address')
944 >>> IS_EMAIL()('abc@NotAnEmail') # missing TLD
945 ('abc@NotAnEmail', 'enter a valid email address')
946 >>> IS_EMAIL()('customer/department@example.com')
947 ('customer/department@example.com', None)
948 >>> IS_EMAIL()('$A12345@example.com')
949 ('$A12345@example.com', None)
950 >>> IS_EMAIL()('!def!xyz%abc@example.com')
951 ('!def!xyz%abc@example.com', None)
952 >>> IS_EMAIL()('_Yosemite.Sam@example.com')
953 ('_Yosemite.Sam@example.com', None)
954 >>> IS_EMAIL()('~@example.com')
955 ('~@example.com', None)
956 >>> IS_EMAIL()('.wooly@example.com') # dot starts name
957 ('.wooly@example.com', 'enter a valid email address')
958 >>> IS_EMAIL()('wo..oly@example.com') # adjacent dots in name
959 ('wo..oly@example.com', 'enter a valid email address')
960 >>> IS_EMAIL()('pootietang.@example.com') # dot ends name
961 ('pootietang.@example.com', 'enter a valid email address')
962 >>> IS_EMAIL()('.@example.com') # name is bare dot
963 ('.@example.com', 'enter a valid email address')
964 >>> IS_EMAIL()('Ima.Fool@example.com')
965 ('Ima.Fool@example.com', None)
966 >>> IS_EMAIL()('Ima Fool@example.com') # space in name
967 ('Ima Fool@example.com', 'enter a valid email address')
968 >>> IS_EMAIL()('localguy@localhost') # localhost as domain
969 ('localguy@localhost', None)
970
971 """
972
973 regex = re.compile('''
974 ^(?!\.) # name may not begin with a dot
975 (
976 [-a-z0-9!\#$%&'*+/=?^_`{|}~] # all legal characters except dot
977 |
978 (?<!\.)\. # single dots only
979 )+
980 (?<!\.) # name may not end with a dot
981 @
982 (
983 localhost
984 |
985 (
986 [a-z0-9] # [sub]domain begins with alphanumeric
987 (
988 [-\w]* # alphanumeric, underscore, dot, hyphen
989 [a-z0-9] # ending alphanumeric
990 )?
991 \. # ending dot
992 )+
993 [a-z]{2,} # TLD alpha-only
994 )$
995 ''', re.VERBOSE|re.IGNORECASE)
996
997 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)
998
999 - def __init__(self,
1000 banned=None,
1001 forced=None,
1002 error_message='enter a valid email address'):
1003 if isinstance(banned, str):
1004 banned = re.compile(banned)
1005 if isinstance(forced, str):
1006 forced = re.compile(forced)
1007 self.banned = banned
1008 self.forced = forced
1009 self.error_message = error_message
1010
1012 match = self.regex.match(value)
1013 if match:
1014 domain = value.split('@')[1]
1015 if (not self.banned or not self.banned.match(domain)) \
1016 and (not self.forced or self.forced.match(domain)):
1017 return (value, None)
1018 return (value, translate(self.error_message))
1019
1020
1021
1022
1023
1024 official_url_schemes = [
1025 'aaa',
1026 'aaas',
1027 'acap',
1028 'cap',
1029 'cid',
1030 'crid',
1031 'data',
1032 'dav',
1033 'dict',
1034 'dns',
1035 'fax',
1036 'file',
1037 'ftp',
1038 'go',
1039 'gopher',
1040 'h323',
1041 'http',
1042 'https',
1043 'icap',
1044 'im',
1045 'imap',
1046 'info',
1047 'ipp',
1048 'iris',
1049 'iris.beep',
1050 'iris.xpc',
1051 'iris.xpcs',
1052 'iris.lws',
1053 'ldap',
1054 'mailto',
1055 'mid',
1056 'modem',
1057 'msrp',
1058 'msrps',
1059 'mtqp',
1060 'mupdate',
1061 'news',
1062 'nfs',
1063 'nntp',
1064 'opaquelocktoken',
1065 'pop',
1066 'pres',
1067 'prospero',
1068 'rtsp',
1069 'service',
1070 'shttp',
1071 'sip',
1072 'sips',
1073 'snmp',
1074 'soap.beep',
1075 'soap.beeps',
1076 'tag',
1077 'tel',
1078 'telnet',
1079 'tftp',
1080 'thismessage',
1081 'tip',
1082 'tv',
1083 'urn',
1084 'vemmi',
1085 'wais',
1086 'xmlrpc.beep',
1087 'xmlrpc.beep',
1088 'xmpp',
1089 'z39.50r',
1090 'z39.50s',
1091 ]
1092 unofficial_url_schemes = [
1093 'about',
1094 'adiumxtra',
1095 'aim',
1096 'afp',
1097 'aw',
1098 'callto',
1099 'chrome',
1100 'cvs',
1101 'ed2k',
1102 'feed',
1103 'fish',
1104 'gg',
1105 'gizmoproject',
1106 'iax2',
1107 'irc',
1108 'ircs',
1109 'itms',
1110 'jar',
1111 'javascript',
1112 'keyparc',
1113 'lastfm',
1114 'ldaps',
1115 'magnet',
1116 'mms',
1117 'msnim',
1118 'mvn',
1119 'notes',
1120 'nsfw',
1121 'psyc',
1122 'paparazzi:http',
1123 'rmi',
1124 'rsync',
1125 'secondlife',
1126 'sgn',
1127 'skype',
1128 'ssh',
1129 'sftp',
1130 'smb',
1131 'sms',
1132 'soldat',
1133 'steam',
1134 'svn',
1135 'teamspeak',
1136 'unreal',
1137 'ut2004',
1138 'ventrilo',
1139 'view-source',
1140 'webcal',
1141 'wyciwyg',
1142 'xfire',
1143 'xri',
1144 'ymsgr',
1145 ]
1146 all_url_schemes = [None] + official_url_schemes + unofficial_url_schemes
1147 http_schemes = [None, 'http', 'https']
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159 url_split_regex = \
1160 re.compile('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?')
1161
1162
1163
1164
1165 label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]')
1166
1167
1169 '''
1170 Converts a unicode string into US-ASCII, using a simple conversion scheme.
1171 Each unicode character that does not have a US-ASCII equivalent is
1172 converted into a URL escaped form based on its hexadecimal value.
1173 For example, the unicode character '\u4e86' will become the string '%4e%86'
1174
1175 :param string: unicode string, the unicode string to convert into an
1176 escaped US-ASCII form
1177 :returns: the US-ASCII escaped form of the inputted string
1178 :rtype: string
1179
1180 @author: Jonathan Benn
1181 '''
1182 returnValue = StringIO()
1183
1184 for character in string:
1185 code = ord(character)
1186 if code > 0x7F:
1187 hexCode = hex(code)
1188 returnValue.write('%' + hexCode[2:4] + '%' + hexCode[4:6])
1189 else:
1190 returnValue.write(character)
1191
1192 return returnValue.getvalue()
1193
1194
1196 '''
1197 Follows the steps in RFC 3490, Section 4 to convert a unicode authority
1198 string into its ASCII equivalent.
1199 For example, u'www.Alliancefran\xe7aise.nu' will be converted into
1200 'www.xn--alliancefranaise-npb.nu'
1201
1202 :param authority: unicode string, the URL authority component to convert,
1203 e.g. u'www.Alliancefran\xe7aise.nu'
1204 :returns: the US-ASCII character equivalent to the inputed authority,
1205 e.g. 'www.xn--alliancefranaise-npb.nu'
1206 :rtype: string
1207 :raises Exception: if the function is not able to convert the inputed
1208 authority
1209
1210 @author: Jonathan Benn
1211 '''
1212
1213
1214
1215
1216 labels = label_split_regex.split(authority)
1217
1218
1219
1220
1221
1222
1223
1224 asciiLabels = []
1225 try:
1226 import encodings.idna
1227 for label in labels:
1228 if label:
1229 asciiLabels.append(encodings.idna.ToASCII(label))
1230 else:
1231
1232
1233
1234 asciiLabels.append('')
1235 except:
1236 asciiLabels=[str(label) for label in labels]
1237
1238 return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels))
1239
1240
1242 '''
1243 Converts the inputed unicode url into a US-ASCII equivalent. This function
1244 goes a little beyond RFC 3490, which is limited in scope to the domain name
1245 (authority) only. Here, the functionality is expanded to what was observed
1246 on Wikipedia on 2009-Jan-22:
1247
1248 Component Can Use Unicode?
1249 --------- ----------------
1250 scheme No
1251 authority Yes
1252 path Yes
1253 query Yes
1254 fragment No
1255
1256 The authority component gets converted to punycode, but occurrences of
1257 unicode in other components get converted into a pair of URI escapes (we
1258 assume 4-byte unicode). E.g. the unicode character U+4E2D will be
1259 converted into '%4E%2D'. Testing with Firefox v3.0.5 has shown that it can
1260 understand this kind of URI encoding.
1261
1262 :param url: unicode string, the URL to convert from unicode into US-ASCII
1263 :param prepend_scheme: string, a protocol scheme to prepend to the URL if
1264 we're having trouble parsing it.
1265 e.g. "http". Input None to disable this functionality
1266 :returns: a US-ASCII equivalent of the inputed url
1267 :rtype: string
1268
1269 @author: Jonathan Benn
1270 '''
1271
1272
1273
1274 groups = url_split_regex.match(url).groups()
1275
1276 if not groups[3]:
1277
1278 scheme_to_prepend = prepend_scheme or 'http'
1279 groups = url_split_regex.match(
1280 unicode(scheme_to_prepend) + u'://' + url).groups()
1281
1282 if not groups[3]:
1283 raise Exception('No authority component found, '+ \
1284 'could not decode unicode to US-ASCII')
1285
1286
1287 scheme = groups[1]
1288 authority = groups[3]
1289 path = groups[4] or ''
1290 query = groups[5] or ''
1291 fragment = groups[7] or ''
1292
1293 if prepend_scheme:
1294 scheme = str(scheme) + '://'
1295 else:
1296 scheme = ''
1297 return scheme + unicode_to_ascii_authority(authority) +\
1298 escape_unicode(path) + escape_unicode(query) + str(fragment)
1299
1300
1302 """
1303 Rejects a URL string if any of the following is true:
1304 * The string is empty or None
1305 * The string uses characters that are not allowed in a URL
1306 * The URL scheme specified (if one is specified) is not valid
1307
1308 Based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html
1309
1310 This function only checks the URL's syntax. It does not check that the URL
1311 points to a real document, for example, or that it otherwise makes sense
1312 semantically. This function does automatically prepend 'http://' in front
1313 of a URL if and only if that's necessary to successfully parse the URL.
1314 Please note that a scheme will be prepended only for rare cases
1315 (e.g. 'google.ca:80')
1316
1317 The list of allowed schemes is customizable with the allowed_schemes
1318 parameter. If you exclude None from the list, then abbreviated URLs
1319 (lacking a scheme such as 'http') will be rejected.
1320
1321 The default prepended scheme is customizable with the prepend_scheme
1322 parameter. If you set prepend_scheme to None then prepending will be
1323 disabled. URLs that require prepending to parse will still be accepted,
1324 but the return value will not be modified.
1325
1326 @author: Jonathan Benn
1327
1328 >>> IS_GENERIC_URL()('http://user@abc.com')
1329 ('http://user@abc.com', None)
1330
1331 """
1332
1333 - def __init__(
1334 self,
1335 error_message='enter a valid URL',
1336 allowed_schemes=None,
1337 prepend_scheme=None,
1338 ):
1339 """
1340 :param error_message: a string, the error message to give the end user
1341 if the URL does not validate
1342 :param allowed_schemes: a list containing strings or None. Each element
1343 is a scheme the inputed URL is allowed to use
1344 :param prepend_scheme: a string, this scheme is prepended if it's
1345 necessary to make the URL valid
1346 """
1347
1348 self.error_message = error_message
1349 if allowed_schemes == None:
1350 self.allowed_schemes = all_url_schemes
1351 else:
1352 self.allowed_schemes = allowed_schemes
1353 self.prepend_scheme = prepend_scheme
1354 if self.prepend_scheme not in self.allowed_schemes:
1355 raise SyntaxError, \
1356 "prepend_scheme='%s' is not in allowed_schemes=%s" \
1357 % (self.prepend_scheme, self.allowed_schemes)
1358
1360 """
1361 :param value: a string, the URL to validate
1362 :returns: a tuple, where tuple[0] is the inputed value (possible
1363 prepended with prepend_scheme), and tuple[1] is either
1364 None (success!) or the string error_message
1365 """
1366 try:
1367
1368 if not re.compile(
1369 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]$"
1370 ).search(value):
1371
1372 if re.compile(
1373 r"[A-Za-z0-9;/?:@&=+$,\-_\.!~*'\(\)%#]+$").match(value):
1374
1375
1376 scheme = url_split_regex.match(value).group(2)
1377
1378 if scheme != None:
1379 scheme = urllib.unquote(scheme).lower()
1380
1381 if scheme in self.allowed_schemes:
1382
1383 return (value, None)
1384 else:
1385
1386
1387
1388
1389 if not re.compile('://').search(value) and None\
1390 in self.allowed_schemes:
1391 schemeToUse = self.prepend_scheme or 'http'
1392 prependTest = self.__call__(schemeToUse
1393 + '://' + value)
1394
1395 if prependTest[1] == None:
1396
1397 if self.prepend_scheme:
1398 return prependTest
1399 else:
1400
1401
1402 return (value, None)
1403 except:
1404 pass
1405
1406 return (value, translate(self.error_message))
1407
1408
1409
1410
1411
1412 official_top_level_domains = [
1413 'ac',
1414 'ad',
1415 'ae',
1416 'aero',
1417 'af',
1418 'ag',
1419 'ai',
1420 'al',
1421 'am',
1422 'an',
1423 'ao',
1424 'aq',
1425 'ar',
1426 'arpa',
1427 'as',
1428 'asia',
1429 'at',
1430 'au',
1431 'aw',
1432 'ax',
1433 'az',
1434 'ba',
1435 'bb',
1436 'bd',
1437 'be',
1438 'bf',
1439 'bg',
1440 'bh',
1441 'bi',
1442 'biz',
1443 'bj',
1444 'bl',
1445 'bm',
1446 'bn',
1447 'bo',
1448 'br',
1449 'bs',
1450 'bt',
1451 'bv',
1452 'bw',
1453 'by',
1454 'bz',
1455 'ca',
1456 'cat',
1457 'cc',
1458 'cd',
1459 'cf',
1460 'cg',
1461 'ch',
1462 'ci',
1463 'ck',
1464 'cl',
1465 'cm',
1466 'cn',
1467 'co',
1468 'com',
1469 'coop',
1470 'cr',
1471 'cu',
1472 'cv',
1473 'cx',
1474 'cy',
1475 'cz',
1476 'de',
1477 'dj',
1478 'dk',
1479 'dm',
1480 'do',
1481 'dz',
1482 'ec',
1483 'edu',
1484 'ee',
1485 'eg',
1486 'eh',
1487 'er',
1488 'es',
1489 'et',
1490 'eu',
1491 'example',
1492 'fi',
1493 'fj',
1494 'fk',
1495 'fm',
1496 'fo',
1497 'fr',
1498 'ga',
1499 'gb',
1500 'gd',
1501 'ge',
1502 'gf',
1503 'gg',
1504 'gh',
1505 'gi',
1506 'gl',
1507 'gm',
1508 'gn',
1509 'gov',
1510 'gp',
1511 'gq',
1512 'gr',
1513 'gs',
1514 'gt',
1515 'gu',
1516 'gw',
1517 'gy',
1518 'hk',
1519 'hm',
1520 'hn',
1521 'hr',
1522 'ht',
1523 'hu',
1524 'id',
1525 'ie',
1526 'il',
1527 'im',
1528 'in',
1529 'info',
1530 'int',
1531 'invalid',
1532 'io',
1533 'iq',
1534 'ir',
1535 'is',
1536 'it',
1537 'je',
1538 'jm',
1539 'jo',
1540 'jobs',
1541 'jp',
1542 'ke',
1543 'kg',
1544 'kh',
1545 'ki',
1546 'km',
1547 'kn',
1548 'kp',
1549 'kr',
1550 'kw',
1551 'ky',
1552 'kz',
1553 'la',
1554 'lb',
1555 'lc',
1556 'li',
1557 'lk',
1558 'localhost',
1559 'lr',
1560 'ls',
1561 'lt',
1562 'lu',
1563 'lv',
1564 'ly',
1565 'ma',
1566 'mc',
1567 'md',
1568 'me',
1569 'mf',
1570 'mg',
1571 'mh',
1572 'mil',
1573 'mk',
1574 'ml',
1575 'mm',
1576 'mn',
1577 'mo',
1578 'mobi',
1579 'mp',
1580 'mq',
1581 'mr',
1582 'ms',
1583 'mt',
1584 'mu',
1585 'museum',
1586 'mv',
1587 'mw',
1588 'mx',
1589 'my',
1590 'mz',
1591 'na',
1592 'name',
1593 'nc',
1594 'ne',
1595 'net',
1596 'nf',
1597 'ng',
1598 'ni',
1599 'nl',
1600 'no',
1601 'np',
1602 'nr',
1603 'nu',
1604 'nz',
1605 'om',
1606 'org',
1607 'pa',
1608 'pe',
1609 'pf',
1610 'pg',
1611 'ph',
1612 'pk',
1613 'pl',
1614 'pm',
1615 'pn',
1616 'pr',
1617 'pro',
1618 'ps',
1619 'pt',
1620 'pw',
1621 'py',
1622 'qa',
1623 're',
1624 'ro',
1625 'rs',
1626 'ru',
1627 'rw',
1628 'sa',
1629 'sb',
1630 'sc',
1631 'sd',
1632 'se',
1633 'sg',
1634 'sh',
1635 'si',
1636 'sj',
1637 'sk',
1638 'sl',
1639 'sm',
1640 'sn',
1641 'so',
1642 'sr',
1643 'st',
1644 'su',
1645 'sv',
1646 'sy',
1647 'sz',
1648 'tc',
1649 'td',
1650 'tel',
1651 'test',
1652 'tf',
1653 'tg',
1654 'th',
1655 'tj',
1656 'tk',
1657 'tl',
1658 'tm',
1659 'tn',
1660 'to',
1661 'tp',
1662 'tr',
1663 'travel',
1664 'tt',
1665 'tv',
1666 'tw',
1667 'tz',
1668 'ua',
1669 'ug',
1670 'uk',
1671 'um',
1672 'us',
1673 'uy',
1674 'uz',
1675 'va',
1676 'vc',
1677 've',
1678 'vg',
1679 'vi',
1680 'vn',
1681 'vu',
1682 'wf',
1683 'ws',
1684 'xn--0zwm56d',
1685 'xn--11b5bs3a9aj6g',
1686 'xn--80akhbyknj4f',
1687 'xn--9t4b11yi5a',
1688 'xn--deba0ad',
1689 'xn--g6w251d',
1690 'xn--hgbk6aj7f53bba',
1691 'xn--hlcj6aya9esc7a',
1692 'xn--jxalpdlp',
1693 'xn--kgbechtv',
1694 'xn--zckzah',
1695 'ye',
1696 'yt',
1697 'yu',
1698 'za',
1699 'zm',
1700 'zw',
1701 ]
1702
1703
1705 """
1706 Rejects a URL string if any of the following is true:
1707 * The string is empty or None
1708 * The string uses characters that are not allowed in a URL
1709 * The string breaks any of the HTTP syntactic rules
1710 * The URL scheme specified (if one is specified) is not 'http' or 'https'
1711 * The top-level domain (if a host name is specified) does not exist
1712
1713 Based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html
1714
1715 This function only checks the URL's syntax. It does not check that the URL
1716 points to a real document, for example, or that it otherwise makes sense
1717 semantically. This function does automatically prepend 'http://' in front
1718 of a URL in the case of an abbreviated URL (e.g. 'google.ca').
1719
1720 The list of allowed schemes is customizable with the allowed_schemes
1721 parameter. If you exclude None from the list, then abbreviated URLs
1722 (lacking a scheme such as 'http') will be rejected.
1723
1724 The default prepended scheme is customizable with the prepend_scheme
1725 parameter. If you set prepend_scheme to None then prepending will be
1726 disabled. URLs that require prepending to parse will still be accepted,
1727 but the return value will not be modified.
1728
1729 @author: Jonathan Benn
1730
1731 >>> IS_HTTP_URL()('http://1.2.3.4')
1732 ('http://1.2.3.4', None)
1733 >>> IS_HTTP_URL()('http://abc.com')
1734 ('http://abc.com', None)
1735 >>> IS_HTTP_URL()('https://abc.com')
1736 ('https://abc.com', None)
1737 >>> IS_HTTP_URL()('httpx://abc.com')
1738 ('httpx://abc.com', 'enter a valid URL')
1739 >>> IS_HTTP_URL()('http://abc.com:80')
1740 ('http://abc.com:80', None)
1741 >>> IS_HTTP_URL()('http://user@abc.com')
1742 ('http://user@abc.com', None)
1743 >>> IS_HTTP_URL()('http://user@1.2.3.4')
1744 ('http://user@1.2.3.4', None)
1745
1746 """
1747
1748 - def __init__(
1749 self,
1750 error_message='enter a valid URL',
1751 allowed_schemes=None,
1752 prepend_scheme='http',
1753 ):
1754 """
1755 :param error_message: a string, the error message to give the end user
1756 if the URL does not validate
1757 :param allowed_schemes: a list containing strings or None. Each element
1758 is a scheme the inputed URL is allowed to use
1759 :param prepend_scheme: a string, this scheme is prepended if it's
1760 necessary to make the URL valid
1761 """
1762
1763 self.error_message = error_message
1764 if allowed_schemes == None:
1765 self.allowed_schemes = http_schemes
1766 else:
1767 self.allowed_schemes = allowed_schemes
1768 self.prepend_scheme = prepend_scheme
1769
1770 for i in self.allowed_schemes:
1771 if i not in http_schemes:
1772 raise SyntaxError, \
1773 "allowed_scheme value '%s' is not in %s" % \
1774 (i, http_schemes)
1775
1776 if self.prepend_scheme not in self.allowed_schemes:
1777 raise SyntaxError, \
1778 "prepend_scheme='%s' is not in allowed_schemes=%s" % \
1779 (self.prepend_scheme, self.allowed_schemes)
1780
1782 """
1783 :param value: a string, the URL to validate
1784 :returns: a tuple, where tuple[0] is the inputed value
1785 (possible prepended with prepend_scheme), and tuple[1] is either
1786 None (success!) or the string error_message
1787 """
1788
1789 try:
1790
1791 x = IS_GENERIC_URL(error_message=self.error_message,
1792 allowed_schemes=self.allowed_schemes,
1793 prepend_scheme=self.prepend_scheme)
1794 if x(value)[1] == None:
1795 componentsMatch = url_split_regex.match(value)
1796 authority = componentsMatch.group(4)
1797
1798 if authority:
1799
1800 if re.compile(
1801 "([\w.!~*'|;:&=+$,-]+@)?\d+\.\d+\.\d+\.\d+(:\d*)*$").match(authority):
1802
1803 return (value, None)
1804 else:
1805
1806 domainMatch = \
1807 re.compile(
1808 "([\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*)*$"
1809 ).match(authority)
1810 if domainMatch:
1811
1812 if domainMatch.group(5).lower()\
1813 in official_top_level_domains:
1814
1815 return (value, None)
1816 else:
1817
1818
1819 path = componentsMatch.group(5)
1820
1821
1822 if re.compile('/').match(path):
1823
1824 return (value, None)
1825 else:
1826
1827
1828 if not re.compile('://').search(value):
1829 schemeToUse = self.prepend_scheme or 'http'
1830 prependTest = self.__call__(schemeToUse
1831 + '://' + value)
1832
1833 if prependTest[1] == None:
1834
1835 if self.prepend_scheme:
1836 return prependTest
1837 else:
1838
1839
1840 return (value, None)
1841 except:
1842 pass
1843
1844 return (value, translate(self.error_message))
1845
1846
1848 """
1849 Rejects a URL string if any of the following is true:
1850 * The string is empty or None
1851 * The string uses characters that are not allowed in a URL
1852 * The string breaks any of the HTTP syntactic rules
1853 * The URL scheme specified (if one is specified) is not 'http' or 'https'
1854 * The top-level domain (if a host name is specified) does not exist
1855
1856 (These rules are based on RFC 2616: http://www.faqs.org/rfcs/rfc2616.html)
1857
1858 This function only checks the URL's syntax. It does not check that the URL
1859 points to a real document, for example, or that it otherwise makes sense
1860 semantically. This function does automatically prepend 'http://' in front
1861 of a URL in the case of an abbreviated URL (e.g. 'google.ca').
1862
1863 If the parameter mode='generic' is used, then this function's behavior
1864 changes. It then rejects a URL string if any of the following is true:
1865 * The string is empty or None
1866 * The string uses characters that are not allowed in a URL
1867 * The URL scheme specified (if one is specified) is not valid
1868
1869 (These rules are based on RFC 2396: http://www.faqs.org/rfcs/rfc2396.html)
1870
1871 The list of allowed schemes is customizable with the allowed_schemes
1872 parameter. If you exclude None from the list, then abbreviated URLs
1873 (lacking a scheme such as 'http') will be rejected.
1874
1875 The default prepended scheme is customizable with the prepend_scheme
1876 parameter. If you set prepend_scheme to None then prepending will be
1877 disabled. URLs that require prepending to parse will still be accepted,
1878 but the return value will not be modified.
1879
1880 IS_URL is compatible with the Internationalized Domain Name (IDN) standard
1881 specified in RFC 3490 (http://tools.ietf.org/html/rfc3490). As a result,
1882 URLs can be regular strings or unicode strings.
1883 If the URL's domain component (e.g. google.ca) contains non-US-ASCII
1884 letters, then the domain will be converted into Punycode (defined in
1885 RFC 3492, http://tools.ietf.org/html/rfc3492). IS_URL goes a bit beyond
1886 the standards, and allows non-US-ASCII characters to be present in the path
1887 and query components of the URL as well. These non-US-ASCII characters will
1888 be escaped using the standard '%20' type syntax. e.g. the unicode
1889 character with hex code 0x4e86 will become '%4e%86'
1890
1891 Code Examples::
1892
1893 INPUT(_type='text', _name='name', requires=IS_URL())
1894 >>> IS_URL()('abc.com')
1895 ('http://abc.com', None)
1896
1897 INPUT(_type='text', _name='name', requires=IS_URL(mode='generic'))
1898 >>> IS_URL(mode='generic')('abc.com')
1899 ('abc.com', None)
1900
1901 INPUT(_type='text', _name='name',
1902 requires=IS_URL(allowed_schemes=['https'], prepend_scheme='https'))
1903 >>> IS_URL(allowed_schemes=['https'], prepend_scheme='https')('https://abc.com')
1904 ('https://abc.com', None)
1905
1906 INPUT(_type='text', _name='name',
1907 requires=IS_URL(prepend_scheme='https'))
1908 >>> IS_URL(prepend_scheme='https')('abc.com')
1909 ('https://abc.com', None)
1910
1911 INPUT(_type='text', _name='name',
1912 requires=IS_URL(mode='generic', allowed_schemes=['ftps', 'https'],
1913 prepend_scheme='https'))
1914 >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https'], prepend_scheme='https')('https://abc.com')
1915 ('https://abc.com', None)
1916 >>> IS_URL(mode='generic', allowed_schemes=['ftps', 'https', None], prepend_scheme='https')('abc.com')
1917 ('abc.com', None)
1918
1919 @author: Jonathan Benn
1920 """
1921
1922 - def __init__(
1923 self,
1924 error_message='enter a valid URL',
1925 mode='http',
1926 allowed_schemes=None,
1927 prepend_scheme='http',
1928 ):
1929 """
1930 :param error_message: a string, the error message to give the end user
1931 if the URL does not validate
1932 :param allowed_schemes: a list containing strings or None. Each element
1933 is a scheme the inputed URL is allowed to use
1934 :param prepend_scheme: a string, this scheme is prepended if it's
1935 necessary to make the URL valid
1936 """
1937
1938 self.error_message = error_message
1939 self.mode = mode.lower()
1940 if not self.mode in ['generic', 'http']:
1941 raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode
1942 self.allowed_schemes = allowed_schemes
1943
1944 if self.allowed_schemes:
1945 if prepend_scheme not in self.allowed_schemes:
1946 raise SyntaxError, \
1947 "prepend_scheme='%s' is not in allowed_schemes=%s" \
1948 % (prepend_scheme, self.allowed_schemes)
1949
1950
1951
1952
1953 self.prepend_scheme = prepend_scheme
1954
1956 """
1957 :param value: a unicode or regular string, the URL to validate
1958 :returns: a (string, string) tuple, where tuple[0] is the modified
1959 input value and tuple[1] is either None (success!) or the
1960 string error_message. The input value will never be modified in the
1961 case of an error. However, if there is success then the input URL
1962 may be modified to (1) prepend a scheme, and/or (2) convert a
1963 non-compliant unicode URL into a compliant US-ASCII version.
1964 """
1965
1966 if self.mode == 'generic':
1967 subMethod = IS_GENERIC_URL(error_message=self.error_message,
1968 allowed_schemes=self.allowed_schemes,
1969 prepend_scheme=self.prepend_scheme)
1970 elif self.mode == 'http':
1971 subMethod = IS_HTTP_URL(error_message=self.error_message,
1972 allowed_schemes=self.allowed_schemes,
1973 prepend_scheme=self.prepend_scheme)
1974 else:
1975 raise SyntaxError, "invalid mode '%s' in IS_URL" % self.mode
1976
1977 if type(value) != unicode:
1978 return subMethod(value)
1979 else:
1980 try:
1981 asciiValue = unicode_to_ascii_url(value, self.prepend_scheme)
1982 except Exception:
1983
1984
1985 return (value, translate(self.error_message))
1986
1987 methodResult = subMethod(asciiValue)
1988
1989 if methodResult[1] != None:
1990
1991 return (value, methodResult[1])
1992 else:
1993 return methodResult
1994
1995
1996 regex_time = re.compile(
1997 '((?P<h>[0-9]+))([^0-9 ]+(?P<m>[0-9 ]+))?([^0-9ap ]+(?P<s>[0-9]*))?((?P<d>[ap]m))?')
1998
1999
2001 """
2002 example::
2003
2004 INPUT(_type='text', _name='name', requires=IS_TIME())
2005
2006 understands the following formats
2007 hh:mm:ss [am/pm]
2008 hh:mm [am/pm]
2009 hh [am/pm]
2010
2011 [am/pm] is optional, ':' can be replaced by any other non-space non-digit
2012
2013 >>> IS_TIME()('21:30')
2014 (datetime.time(21, 30), None)
2015 >>> IS_TIME()('21-30')
2016 (datetime.time(21, 30), None)
2017 >>> IS_TIME()('21.30')
2018 (datetime.time(21, 30), None)
2019 >>> IS_TIME()('21:30:59')
2020 (datetime.time(21, 30, 59), None)
2021 >>> IS_TIME()('5:30')
2022 (datetime.time(5, 30), None)
2023 >>> IS_TIME()('5:30 am')
2024 (datetime.time(5, 30), None)
2025 >>> IS_TIME()('5:30 pm')
2026 (datetime.time(17, 30), None)
2027 >>> IS_TIME()('5:30 whatever')
2028 ('5:30 whatever', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2029 >>> IS_TIME()('5:30 20')
2030 ('5:30 20', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2031 >>> IS_TIME()('24:30')
2032 ('24:30', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2033 >>> IS_TIME()('21:60')
2034 ('21:60', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2035 >>> IS_TIME()('21:30::')
2036 ('21:30::', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2037 >>> IS_TIME()('')
2038 ('', 'enter time as hh:mm:ss (seconds, am, pm optional)')
2039 """
2040
2041 - def __init__(self, error_message='enter time as hh:mm:ss (seconds, am, pm optional)'):
2042 self.error_message = error_message
2043
2045 try:
2046 ivalue = value
2047 value = regex_time.match(value.lower())
2048 (h, m, s) = (int(value.group('h')), 0, 0)
2049 if value.group('m') != None:
2050 m = int(value.group('m'))
2051 if value.group('s') != None:
2052 s = int(value.group('s'))
2053 if value.group('d') == 'pm' and 0 < h < 12:
2054 h = h + 12
2055 if not (h in range(24) and m in range(60) and s
2056 in range(60)):
2057 raise ValueError\
2058 ('Hours or minutes or seconds are outside of allowed range')
2059 value = datetime.time(h, m, s)
2060 return (value, None)
2061 except AttributeError:
2062 pass
2063 except ValueError:
2064 pass
2065 return (ivalue, translate(self.error_message))
2066
2067
2069 """
2070 example::
2071
2072 INPUT(_type='text', _name='name', requires=IS_DATE())
2073
2074 date has to be in the ISO8960 format YYYY-MM-DD
2075 """
2076
2077 - def __init__(self, format='%Y-%m-%d',
2078 error_message='enter date as %(format)s'):
2079 self.format = str(format)
2080 self.error_message = str(error_message)
2081
2083 if isinstance(value,datetime.date):
2084 return (value,None)
2085 try:
2086 (y, m, d, hh, mm, ss, t0, t1, t2) = \
2087 time.strptime(value, str(self.format))
2088 value = datetime.date(y, m, d)
2089 return (value, None)
2090 except:
2091 return (value, translate(self.error_message) % IS_DATETIME.nice(self.format))
2092
2103
2104
2106 """
2107 example::
2108
2109 INPUT(_type='text', _name='name', requires=IS_DATETIME())
2110
2111 datetime has to be in the ISO8960 format YYYY-MM-DD hh:mm:ss
2112 """
2113
2114 isodatetime = '%Y-%m-%d %H:%M:%S'
2115
2116 @staticmethod
2118 code=(('%Y','1963'),
2119 ('%y','63'),
2120 ('%d','28'),
2121 ('%m','08'),
2122 ('%b','Aug'),
2123 ('%b','August'),
2124 ('%H','14'),
2125 ('%I','02'),
2126 ('%p','PM'),
2127 ('%M','30'),
2128 ('%S','59'))
2129 for (a,b) in code:
2130 format=format.replace(a,b)
2131 return dict(format=format)
2132
2133 - def __init__(self, format='%Y-%m-%d %H:%M:%S',
2134 error_message='enter date and time as %(format)s'):
2135 self.format = str(format)
2136 self.error_message = str(error_message)
2137
2139 if isinstance(value,datetime.datetime):
2140 return (value,None)
2141 try:
2142 (y, m, d, hh, mm, ss, t0, t1, t2) = \
2143 time.strptime(value, str(self.format))
2144 value = datetime.datetime(y, m, d, hh, mm, ss)
2145 return (value, None)
2146 except:
2147 return (value, translate(self.error_message) % IS_DATETIME.nice(self.format))
2148
2159
2161 """
2162 example::
2163
2164 >>> v = IS_DATE_IN_RANGE(minimum=datetime.date(2008,1,1), \
2165 maximum=datetime.date(2009,12,31), \
2166 format="%m/%d/%Y",error_message="oops")
2167
2168 >>> v('03/03/2008')
2169 (datetime.date(2008, 3, 3), None)
2170
2171 >>> v('03/03/2010')
2172 (datetime.date(2010, 3, 3), 'oops')
2173
2174 >>> v(datetime.date(2008,3,3))
2175 (datetime.date(2008, 3, 3), None)
2176
2177 >>> v(datetime.date(2010,3,3))
2178 (datetime.date(2010, 3, 3), 'oops')
2179
2180 """
2181 - def __init__(self,
2182 minimum = None,
2183 maximum = None,
2184 format='%Y-%m-%d',
2185 error_message = None):
2186 self.minimum = minimum
2187 self.maximum = maximum
2188 if error_message is None:
2189 if minimum is None:
2190 error_message = "enter date on or before %(max)s"
2191 elif maximum is None:
2192 error_message = "enter date on or after %(min)s"
2193 else:
2194 error_message = "enter date in range %(min)s %(max)s"
2195 d = dict(min=minimum, max=maximum)
2196 IS_DATE.__init__(self,
2197 format = format,
2198 error_message = error_message % d)
2199
2201 (value, msg) = IS_DATE.__call__(self,value)
2202 if msg is not None:
2203 return (value, msg)
2204 if self.minimum and self.minimum > value:
2205 return (value, translate(self.error_message))
2206 if self.maximum and value > self.maximum:
2207 return (value, translate(self.error_message))
2208 return (value, None)
2209
2210
2212 """
2213 example::
2214
2215 >>> v = IS_DATETIME_IN_RANGE(\
2216 minimum=datetime.datetime(2008,1,1,12,20), \
2217 maximum=datetime.datetime(2009,12,31,12,20), \
2218 format="%m/%d/%Y %H:%M",error_message="oops")
2219 >>> v('03/03/2008 12:40')
2220 (datetime.datetime(2008, 3, 3, 12, 40), None)
2221
2222 >>> v('03/03/2010 10:34')
2223 (datetime.datetime(2010, 3, 3, 10, 34), 'oops')
2224
2225 >>> v(datetime.datetime(2008,3,3,0,0))
2226 (datetime.datetime(2008, 3, 3, 0, 0), None)
2227
2228 >>> v(datetime.datetime(2010,3,3,0,0))
2229 (datetime.datetime(2010, 3, 3, 0, 0), 'oops')
2230 """
2231 - def __init__(self,
2232 minimum = None,
2233 maximum = None,
2234 format = '%Y-%m-%d %H:%M:%S',
2235 error_message = None):
2236 self.minimum = minimum
2237 self.maximum = maximum
2238 if error_message is None:
2239 if minimum is None:
2240 error_message = "enter date and time on or before %(max)s"
2241 elif maximum is None:
2242 error_message = "enter date and time on or after %(min)s"
2243 else:
2244 error_message = "enter date and time in range %(min)s %(max)s"
2245 d = dict(min = minimum, max = maximum)
2246 IS_DATETIME.__init__(self,
2247 format = format,
2248 error_message = error_message % d)
2249
2251 (value, msg) = IS_DATETIME.__call__(self, value)
2252 if msg is not None:
2253 return (value, msg)
2254 if self.minimum and self.minimum > value:
2255 return (value, translate(self.error_message))
2256 if self.maximum and value > self.maximum:
2257 return (value, translate(self.error_message))
2258 return (value, None)
2259
2260
2262
2265
2267 ivalue = value
2268 if not isinstance(value, list):
2269 ivalue = [ivalue]
2270 new_value = []
2271 for item in ivalue:
2272 (v, e) = self.other(item)
2273 if e:
2274 return (value, e)
2275 else:
2276 new_value.append(v)
2277 return (new_value, None)
2278
2279
2281 """
2282 convert to lower case
2283
2284 >>> IS_LOWER()('ABC')
2285 ('abc', None)
2286 >>> IS_LOWER()('Ñ')
2287 ('\\xc3\\xb1', None)
2288 """
2289
2292
2293
2295 """
2296 convert to upper case
2297
2298 >>> IS_UPPER()('abc')
2299 ('ABC', None)
2300 >>> IS_UPPER()('ñ')
2301 ('\\xc3\\x91', None)
2302 """
2303
2306
2307
2308 -def urlify(value, maxlen=80, keep_underscores=False):
2309 """
2310 Convert incoming string to a simplified ASCII subset.
2311 if (keep_underscores): underscores are retained in the string
2312 else: underscores are translated to hyphens (default)
2313 """
2314 s = value.lower()
2315 s = s.decode('utf-8')
2316 s = unicodedata.normalize('NFKD', s)
2317 s = s.encode('ASCII', 'ignore')
2318 s = re.sub('&\w+;', '', s)
2319 if keep_underscores:
2320 s = re.sub('\s+', '-', s)
2321 s = re.sub('[^\w\-]', '', s)
2322 else:
2323 s = re.sub('[\s_]+', '-', s)
2324 s = re.sub('[^a-z0-9\-]', '', s)
2325 s = re.sub('[-_][-_]+', '-', s)
2326 s = s.strip('-')
2327 return s[:maxlen]
2328
2329
2331 """
2332 convert arbitrary text string to a slug
2333
2334 >>> IS_SLUG()('abc123')
2335 ('abc123', None)
2336 >>> IS_SLUG()('ABC123')
2337 ('abc123', None)
2338 >>> IS_SLUG()('abc-123')
2339 ('abc-123', None)
2340 >>> IS_SLUG()('abc--123')
2341 ('abc-123', None)
2342 >>> IS_SLUG()('abc 123')
2343 ('abc-123', None)
2344 >>> IS_SLUG()('abc\t_123')
2345 ('abc-123', None)
2346 >>> IS_SLUG()('-abc-')
2347 ('abc', None)
2348 >>> IS_SLUG()('--a--b--_ -c--')
2349 ('a-b-c', None)
2350 >>> IS_SLUG()('abc&123')
2351 ('abc123', None)
2352 >>> IS_SLUG()('abc&123&def')
2353 ('abc123def', None)
2354 >>> IS_SLUG()('ñ')
2355 ('n', None)
2356 >>> IS_SLUG(maxlen=4)('abc123')
2357 ('abc1', None)
2358 >>> IS_SLUG()('abc_123')
2359 ('abc-123', None)
2360 >>> IS_SLUG(keep_underscores=False)('abc_123')
2361 ('abc-123', None)
2362 >>> IS_SLUG(keep_underscores=True)('abc_123')
2363 ('abc_123', None)
2364 >>> IS_SLUG(check=False)('abc')
2365 ('abc', None)
2366 >>> IS_SLUG(check=True)('abc')
2367 ('abc', None)
2368 >>> IS_SLUG(check=False)('a bc')
2369 ('a-bc', None)
2370 >>> IS_SLUG(check=True)('a bc')
2371 ('a bc', 'must be slug')
2372 """
2373
2374 @staticmethod
2375 - def urlify(value, maxlen=80, keep_underscores=False):
2376 return urlify(value, maxlen, keep_underscores)
2377
2378 - def __init__(self, maxlen=80, check=False, error_message='must be slug', keep_underscores=False):
2379 self.maxlen = maxlen
2380 self.check = check
2381 self.error_message = error_message
2382 self.keep_underscores = keep_underscores
2383
2385 if self.check and value != urlify(value, self.maxlen, self.keep_underscores):
2386 return (value, translate(self.error_message))
2387 return (urlify(value,self.maxlen, self.keep_underscores), None)
2388
2390 """
2391 dummy class for testing IS_EMPTY_OR
2392
2393 >>> IS_EMPTY_OR(IS_EMAIL())('abc@def.com')
2394 ('abc@def.com', None)
2395 >>> IS_EMPTY_OR(IS_EMAIL())(' ')
2396 (None, None)
2397 >>> IS_EMPTY_OR(IS_EMAIL(), null='abc')(' ')
2398 ('abc', None)
2399 >>> IS_EMPTY_OR(IS_EMAIL(), null='abc', empty_regex='def')('def')
2400 ('abc', None)
2401 >>> IS_EMPTY_OR(IS_EMAIL())('abc')
2402 ('abc', 'enter a valid email address')
2403 >>> IS_EMPTY_OR(IS_EMAIL())(' abc ')
2404 ('abc', 'enter a valid email address')
2405 """
2406
2407 - def __init__(self, other, null=None, empty_regex=None):
2408 (self.other, self.null) = (other, null)
2409 if empty_regex is not None:
2410 self.empty_regex = re.compile(empty_regex)
2411 else:
2412 self.empty_regex = None
2413 if hasattr(other, 'multiple'):
2414 self.multiple = other.multiple
2415 if hasattr(other, 'options'):
2416 self.options=self._options
2417
2423
2425 if isinstance(self.other, (list, tuple)):
2426 for item in self.other:
2427 if hasattr(item, 'set_self_id'):
2428 item.set_self_id(id)
2429 else:
2430 if hasattr(self.other, 'set_self_id'):
2431 self.other.set_self_id(id)
2432
2434 value, empty = is_empty(value, empty_regex=self.empty_regex)
2435 if empty:
2436 return (self.null, None)
2437 if isinstance(self.other, (list, tuple)):
2438 for item in self.other:
2439 value, error = item(value)
2440 if error: break
2441 return value, error
2442 else:
2443 return self.other(value)
2444
2449
2450 IS_NULL_OR = IS_EMPTY_OR
2451
2452
2454 """
2455 example::
2456
2457 INPUT(_type='text', _name='name', requires=CLEANUP())
2458
2459 removes special characters on validation
2460 """
2461
2462 - def __init__(self, regex='[^\x09\x0a\x0d\x20-\x7e]'):
2464
2466 v = self.regex.sub('',str(value).strip())
2467 return (v, None)
2468
2469
2471 """
2472 example::
2473
2474 INPUT(_type='text', _name='name', requires=CRYPT())
2475
2476 encodes the value on validation with a digest.
2477
2478 If no arguments are provided CRYPT uses the MD5 algorithm.
2479 If the key argument is provided the HMAC+MD5 algorithm is used.
2480 If the digest_alg is specified this is used to replace the
2481 MD5 with, for example, SHA512. The digest_alg can be
2482 the name of a hashlib algorithm as a string or the algorithm itself.
2483 """
2484
2485 - def __init__(self, key=None, digest_alg='md5'):
2486 self.key = key
2487 self.digest_alg = digest_alg
2488
2490 if self.key:
2491 return (hmac_hash(value, self.key, self.digest_alg), None)
2492 else:
2493 return (simple_hash(value, self.digest_alg), None)
2494
2495
2497 """
2498 example::
2499
2500 INPUT(_type='password', _name='passwd',
2501 requires=IS_STRONG(min=10, special=2, upper=2))
2502
2503 enforces complexity requirements on a field
2504 """
2505
2506 - def __init__(self, min=8, max=20, upper=1, lower=1, number=1,
2507 special=1, specials=r'~!@#$%^&*()_+-=?<>,.:;{}[]|',
2508 invalid=' "', error_message=None):
2509 self.min = min
2510 self.max = max
2511 self.upper = upper
2512 self.lower = lower
2513 self.number = number
2514 self.special = special
2515 self.specials = specials
2516 self.invalid = invalid
2517 self.error_message = error_message
2518
2520 failures = []
2521 if type(self.min) == int and self.min > 0:
2522 if not len(value) >= self.min:
2523 failures.append("Minimum length is %s" % self.min)
2524 if type(self.max) == int and self.max > 0:
2525 if not len(value) <= self.max:
2526 failures.append("Maximum length is %s" % self.max)
2527 if type(self.special) == int:
2528 all_special = [ch in value for ch in self.specials]
2529 if self.special > 0:
2530 if not all_special.count(True) >= self.special:
2531 failures.append("Must include at least %s of the following : %s" % (self.special, self.specials))
2532 if self.invalid:
2533 all_invalid = [ch in value for ch in self.invalid]
2534 if all_invalid.count(True) > 0:
2535 failures.append("May not contain any of the following: %s" \
2536 % self.invalid)
2537 if type(self.upper) == int:
2538 all_upper = re.findall("[A-Z]", value)
2539 if self.upper > 0:
2540 if not len(all_upper) >= self.upper:
2541 failures.append("Must include at least %s upper case" \
2542 % str(self.upper))
2543 else:
2544 if len(all_upper) > 0:
2545 failures.append("May not include any upper case letters")
2546 if type(self.lower) == int:
2547 all_lower = re.findall("[a-z]", value)
2548 if self.lower > 0:
2549 if not len(all_lower) >= self.lower:
2550 failures.append("Must include at least %s lower case" \
2551 % str(self.lower))
2552 else:
2553 if len(all_lower) > 0:
2554 failures.append("May not include any lower case letters")
2555 if type(self.number) == int:
2556 all_number = re.findall("[0-9]", value)
2557 if self.number > 0:
2558 numbers = "number"
2559 if self.number > 1:
2560 numbers = "numbers"
2561 if not len(all_number) >= self.number:
2562 failures.append("Must include at least %s %s" \
2563 % (str(self.number), numbers))
2564 else:
2565 if len(all_number) > 0:
2566 failures.append("May not include any numbers")
2567 if len(failures) == 0:
2568 return (value, None)
2569 if not translate(self.error_message):
2570 from html import XML
2571 return (value, XML('<br />'.join(failures)))
2572 else:
2573 return (value, translate(self.error_message))
2574
2575
2577
2580
2582 values = re.compile("\w+").findall(str(value))
2583 failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]]
2584 if failures:
2585 return (value, translate(self.error_message))
2586 return (value, None)
2587
2588
2590 """
2591 Checks if file uploaded through file input was saved in one of selected
2592 image formats and has dimensions (width and height) within given boundaries.
2593
2594 Does *not* check for maximum file size (use IS_LENGTH for that). Returns
2595 validation failure if no data was uploaded.
2596
2597 Supported file formats: BMP, GIF, JPEG, PNG.
2598
2599 Code parts taken from
2600 http://mail.python.org/pipermail/python-list/2007-June/617126.html
2601
2602 Arguments:
2603
2604 extensions: iterable containing allowed *lowercase* image file extensions
2605 ('jpg' extension of uploaded file counts as 'jpeg')
2606 maxsize: iterable containing maximum width and height of the image
2607 minsize: iterable containing minimum width and height of the image
2608
2609 Use (-1, -1) as minsize to pass image size check.
2610
2611 Examples::
2612
2613 #Check if uploaded file is in any of supported image formats:
2614 INPUT(_type='file', _name='name', requires=IS_IMAGE())
2615
2616 #Check if uploaded file is either JPEG or PNG:
2617 INPUT(_type='file', _name='name',
2618 requires=IS_IMAGE(extensions=('jpeg', 'png')))
2619
2620 #Check if uploaded file is PNG with maximum size of 200x200 pixels:
2621 INPUT(_type='file', _name='name',
2622 requires=IS_IMAGE(extensions=('png'), maxsize=(200, 200)))
2623 """
2624
2625 - def __init__(self,
2626 extensions=('bmp', 'gif', 'jpeg', 'png'),
2627 maxsize=(10000, 10000),
2628 minsize=(0, 0),
2629 error_message='invalid image'):
2630
2631 self.extensions = extensions
2632 self.maxsize = maxsize
2633 self.minsize = minsize
2634 self.error_message = error_message
2635
2637 try:
2638 extension = value.filename.rfind('.')
2639 assert extension >= 0
2640 extension = value.filename[extension + 1:].lower()
2641 if extension == 'jpg':
2642 extension = 'jpeg'
2643 assert extension in self.extensions
2644 if extension == 'bmp':
2645 width, height = self.__bmp(value.file)
2646 elif extension == 'gif':
2647 width, height = self.__gif(value.file)
2648 elif extension == 'jpeg':
2649 width, height = self.__jpeg(value.file)
2650 elif extension == 'png':
2651 width, height = self.__png(value.file)
2652 else:
2653 width = -1
2654 height = -1
2655 assert self.minsize[0] <= width <= self.maxsize[0] \
2656 and self.minsize[1] <= height <= self.maxsize[1]
2657 value.file.seek(0)
2658 return (value, None)
2659 except:
2660 return (value, translate(self.error_message))
2661
2662 - def __bmp(self, stream):
2667
2668 - def __gif(self, stream):
2674
2676 if stream.read(2) == '\xFF\xD8':
2677 while True:
2678 (marker, code, length) = struct.unpack("!BBH", stream.read(4))
2679 if marker != 0xFF:
2680 break
2681 elif code >= 0xC0 and code <= 0xC3:
2682 return tuple(reversed(
2683 struct.unpack("!xHH", stream.read(5))))
2684 else:
2685 stream.read(length - 2)
2686 return (-1, -1)
2687
2688 - def __png(self, stream):
2694
2695
2697 """
2698 Checks if name and extension of file uploaded through file input matches
2699 given criteria.
2700
2701 Does *not* ensure the file type in any way. Returns validation failure
2702 if no data was uploaded.
2703
2704 Arguments::
2705
2706 filename: filename (before dot) regex
2707 extension: extension (after dot) regex
2708 lastdot: which dot should be used as a filename / extension separator:
2709 True means last dot, eg. file.png -> file / png
2710 False means first dot, eg. file.tar.gz -> file / tar.gz
2711 case: 0 - keep the case, 1 - transform the string into lowercase (default),
2712 2 - transform the string into uppercase
2713
2714 If there is no dot present, extension checks will be done against empty
2715 string and filename checks against whole value.
2716
2717 Examples::
2718
2719 #Check if file has a pdf extension (case insensitive):
2720 INPUT(_type='file', _name='name',
2721 requires=IS_UPLOAD_FILENAME(extension='pdf'))
2722
2723 #Check if file has a tar.gz extension and name starting with backup:
2724 INPUT(_type='file', _name='name',
2725 requires=IS_UPLOAD_FILENAME(filename='backup.*',
2726 extension='tar.gz', lastdot=False))
2727
2728 #Check if file has no extension and name matching README
2729 #(case sensitive):
2730 INPUT(_type='file', _name='name',
2731 requires=IS_UPLOAD_FILENAME(filename='^README$',
2732 extension='^$', case=0))
2733 """
2734
2735 - def __init__(self, filename=None, extension=None, lastdot=True, case=1,
2736 error_message='enter valid filename'):
2737 if isinstance(filename, str):
2738 filename = re.compile(filename)
2739 if isinstance(extension, str):
2740 extension = re.compile(extension)
2741 self.filename = filename
2742 self.extension = extension
2743 self.lastdot = lastdot
2744 self.case = case
2745 self.error_message = error_message
2746
2748 try:
2749 string = value.filename
2750 except:
2751 return (value, translate(self.error_message))
2752 if self.case == 1:
2753 string = string.lower()
2754 elif self.case == 2:
2755 string = string.upper()
2756 if self.lastdot:
2757 dot = string.rfind('.')
2758 else:
2759 dot = string.find('.')
2760 if dot == -1:
2761 dot = len(string)
2762 if self.filename and not self.filename.match(string[:dot]):
2763 return (value, translate(self.error_message))
2764 elif self.extension and not self.extension.match(string[dot + 1:]):
2765 return (value, translate(self.error_message))
2766 else:
2767 return (value, None)
2768
2769
2771 """
2772 Checks if field's value is an IP version 4 address in decimal form. Can
2773 be set to force addresses from certain range.
2774
2775 IPv4 regex taken from: http://regexlib.com/REDetails.aspx?regexp_id=1411
2776
2777 Arguments:
2778
2779 minip: lowest allowed address; accepts:
2780 str, eg. 192.168.0.1
2781 list or tuple of octets, eg. [192, 168, 0, 1]
2782 maxip: highest allowed address; same as above
2783 invert: True to allow addresses only from outside of given range; note
2784 that range boundaries are not matched this way
2785 is_localhost: localhost address treatment:
2786 None (default): indifferent
2787 True (enforce): query address must match localhost address
2788 (127.0.0.1)
2789 False (forbid): query address must not match localhost
2790 address
2791 is_private: same as above, except that query address is checked against
2792 two address ranges: 172.16.0.0 - 172.31.255.255 and
2793 192.168.0.0 - 192.168.255.255
2794 is_automatic: same as above, except that query address is checked against
2795 one address range: 169.254.0.0 - 169.254.255.255
2796
2797 Minip and maxip may also be lists or tuples of addresses in all above
2798 forms (str, int, list / tuple), allowing setup of multiple address ranges:
2799
2800 minip = (minip1, minip2, ... minipN)
2801 | | |
2802 | | |
2803 maxip = (maxip1, maxip2, ... maxipN)
2804
2805 Longer iterable will be truncated to match length of shorter one.
2806
2807 Examples::
2808
2809 #Check for valid IPv4 address:
2810 INPUT(_type='text', _name='name', requires=IS_IPV4())
2811
2812 #Check for valid IPv4 address belonging to specific range:
2813 INPUT(_type='text', _name='name',
2814 requires=IS_IPV4(minip='100.200.0.0', maxip='100.200.255.255'))
2815
2816 #Check for valid IPv4 address belonging to either 100.110.0.0 -
2817 #100.110.255.255 or 200.50.0.0 - 200.50.0.255 address range:
2818 INPUT(_type='text', _name='name',
2819 requires=IS_IPV4(minip=('100.110.0.0', '200.50.0.0'),
2820 maxip=('100.110.255.255', '200.50.0.255')))
2821
2822 #Check for valid IPv4 address belonging to private address space:
2823 INPUT(_type='text', _name='name', requires=IS_IPV4(is_private=True))
2824
2825 #Check for valid IPv4 address that is not a localhost address:
2826 INPUT(_type='text', _name='name', requires=IS_IPV4(is_localhost=False))
2827
2828 >>> IS_IPV4()('1.2.3.4')
2829 ('1.2.3.4', None)
2830 >>> IS_IPV4()('255.255.255.255')
2831 ('255.255.255.255', None)
2832 >>> IS_IPV4()('1.2.3.4 ')
2833 ('1.2.3.4 ', 'enter valid IPv4 address')
2834 >>> IS_IPV4()('1.2.3.4.5')
2835 ('1.2.3.4.5', 'enter valid IPv4 address')
2836 >>> IS_IPV4()('123.123')
2837 ('123.123', 'enter valid IPv4 address')
2838 >>> IS_IPV4()('1111.2.3.4')
2839 ('1111.2.3.4', 'enter valid IPv4 address')
2840 >>> IS_IPV4()('0111.2.3.4')
2841 ('0111.2.3.4', 'enter valid IPv4 address')
2842 >>> IS_IPV4()('256.2.3.4')
2843 ('256.2.3.4', 'enter valid IPv4 address')
2844 >>> IS_IPV4()('300.2.3.4')
2845 ('300.2.3.4', 'enter valid IPv4 address')
2846 >>> IS_IPV4(minip='1.2.3.4', maxip='1.2.3.4')('1.2.3.4')
2847 ('1.2.3.4', None)
2848 >>> IS_IPV4(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
2849 ('1.2.3.4', 'bad ip')
2850 >>> IS_IPV4(maxip='1.2.3.4', invert=True)('127.0.0.1')
2851 ('127.0.0.1', None)
2852 >>> IS_IPV4(maxip='1.2.3.4', invert=True)('1.2.3.4')
2853 ('1.2.3.4', 'enter valid IPv4 address')
2854 >>> IS_IPV4(is_localhost=True)('127.0.0.1')
2855 ('127.0.0.1', None)
2856 >>> IS_IPV4(is_localhost=True)('1.2.3.4')
2857 ('1.2.3.4', 'enter valid IPv4 address')
2858 >>> IS_IPV4(is_localhost=False)('127.0.0.1')
2859 ('127.0.0.1', 'enter valid IPv4 address')
2860 >>> IS_IPV4(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
2861 ('127.0.0.1', 'enter valid IPv4 address')
2862 """
2863
2864 regex = re.compile(
2865 '^(([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])$')
2866 numbers = (16777216, 65536, 256, 1)
2867 localhost = 2130706433
2868 private = ((2886729728L, 2886795263L), (3232235520L, 3232301055L))
2869 automatic = (2851995648L, 2852061183L)
2870
2871 - def __init__(
2872 self,
2873 minip='0.0.0.0',
2874 maxip='255.255.255.255',
2875 invert=False,
2876 is_localhost=None,
2877 is_private=None,
2878 is_automatic=None,
2879 error_message='enter valid IPv4 address'):
2880 for n, value in enumerate((minip, maxip)):
2881 temp = []
2882 if isinstance(value, str):
2883 temp.append(value.split('.'))
2884 elif isinstance(value, (list, tuple)):
2885 if len(value) == len(filter(lambda item: isinstance(item, int), value)) == 4:
2886 temp.append(value)
2887 else:
2888 for item in value:
2889 if isinstance(item, str):
2890 temp.append(item.split('.'))
2891 elif isinstance(item, (list, tuple)):
2892 temp.append(item)
2893 numbers = []
2894 for item in temp:
2895 number = 0
2896 for i, j in zip(self.numbers, item):
2897 number += i * int(j)
2898 numbers.append(number)
2899 if n == 0:
2900 self.minip = numbers
2901 else:
2902 self.maxip = numbers
2903 self.invert = invert
2904 self.is_localhost = is_localhost
2905 self.is_private = is_private
2906 self.is_automatic = is_automatic
2907 self.error_message = error_message
2908
2910 if self.regex.match(value):
2911 number = 0
2912 for i, j in zip(self.numbers, value.split('.')):
2913 number += i * int(j)
2914 ok = False
2915 for bottom, top in zip(self.minip, self.maxip):
2916 if self.invert != (bottom <= number <= top):
2917 ok = True
2918 if not (self.is_localhost == None or self.is_localhost == \
2919 (number == self.localhost)):
2920 ok = False
2921 if not (self.is_private == None or self.is_private == \
2922 (sum([number[0] <= number <= number[1] for number in self.private]) > 0)):
2923 ok = False
2924 if not (self.is_automatic == None or self.is_automatic == \
2925 (self.automatic[0] <= number <= self.automatic[1])):
2926 ok = False
2927 if ok:
2928 return (value, None)
2929 return (value, translate(self.error_message))
2930
2931 if __name__ == '__main__':
2932 import doctest
2933 doctest.testmod()
2934