1
2
3 """ Custom Validators
4
5 @requires: U{B{I{gluon}} <http://web2py.com>}
6
7 @copyright: (c) 2010-2019 Sahana Software Foundation
8 @license: MIT
9
10 Permission is hereby granted, free of charge, to any person
11 obtaining a copy of this software and associated documentation
12 files (the "Software"), to deal in the Software without
13 restriction, including without limitation the rights to use,
14 copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the
16 Software is furnished to do so, subject to the following
17 conditions:
18
19 The above copyright notice and this permission notice shall be
20 included in all copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29 OTHER DEALINGS IN THE SOFTWARE.
30
31 """
32
33 __all__ = ("single_phone_number_pattern",
34 "multi_phone_number_pattern",
35 "s3_single_phone_requires",
36 "s3_phone_requires",
37 "IS_ACL",
38 "IS_COMBO_BOX",
39 "IS_DYNAMIC_FIELDNAME",
40 "IS_DYNAMIC_FIELDTYPE",
41 "IS_FLOAT_AMOUNT",
42 "IS_HTML_COLOUR",
43 "IS_INT_AMOUNT",
44 "IS_IN_SET_LAZY",
45 "IS_ISO639_2_LANGUAGE_CODE",
46 "IS_JSONS3",
47 "IS_LAT",
48 "IS_LON",
49 "IS_LAT_LON",
50 "IS_LOCATION",
51 "IS_ONE_OF",
52 "IS_ONE_OF_EMPTY",
53 "IS_ONE_OF_EMPTY_SELECT",
54 "IS_NOT_ONE_OF",
55 "IS_PERSON_GENDER",
56 "IS_PHONE_NUMBER",
57 "IS_PHONE_NUMBER_MULTI",
58 "IS_PROCESSED_IMAGE",
59 "IS_UTC_DATETIME",
60 "IS_UTC_DATE",
61 "IS_UTC_OFFSET",
62 "QUANTITY_INV_ITEM",
63 )
64
65 import datetime
66 import json
67 import re
68
69 from gluon import current, IS_FLOAT_IN_RANGE, IS_INT_IN_RANGE, IS_IN_SET, \
70 IS_MATCH, IS_NOT_IN_DB
71 from gluon.storage import Storage
72 from gluon.validators import Validator
73
74 from s3datetime import S3DateTime
75 from s3utils import s3_orderby_fields, s3_str, s3_unicode
76
77 DEFAULT = lambda: None
78 JSONERRORS = (NameError, TypeError, ValueError, AttributeError, KeyError)
79 SEPARATORS = (",", ":")
82 if text is None:
83 return None
84 elif isinstance(text, (str, unicode)):
85 if hasattr(current, "T"):
86 return str(current.T(text))
87 return str(text)
88
91
92
93
94
95
96
97
98
99
100
101
102 phone_number_pattern = r"\+?\s*[\s\-\.\(\)\d]+(?:(?: x| ext)\s?\d{1,5})?"
103 single_phone_number_pattern = "%s$" % phone_number_pattern
104 multi_phone_number_pattern = r"%s(\s*(,|/|;)\s*%s)*$" % (phone_number_pattern,
105 phone_number_pattern)
106
107 s3_single_phone_requires = IS_MATCH(single_phone_number_pattern)
108 s3_phone_requires = IS_MATCH(multi_phone_number_pattern,
109 error_message="Invalid phone number!")
113 """
114 Similar to web2py's IS_JSON validator, but extended to handle
115 single quotes in dict keys (=invalid JSON) from CSV imports.
116
117 Example:
118
119 INPUT(_type='text', _name='name', requires=IS_JSONS3())
120
121 >>> IS_JSONS3()('{"a": 100}')
122 ({u'a': 100}, None)
123
124 >>> IS_JSONS3()('spam1234')
125 ('spam1234', 'invalid json')
126 """
127
128 - def __init__(self,
129 native_json=False,
130 error_message="Invalid JSON"):
131 """
132 Constructor
133
134 @param native_json: return the JSON string rather than
135 a Python object (e.g. when the field
136 is "string" type rather than "json")
137 @param error_message: the error message
138 """
139
140 self.native_json = native_json
141 self.error_message = error_message
142
143
145 """
146 Validator, validates a string and converts it into db format
147 """
148
149 error = lambda v, e: (v, "%s: %s" % (current.T(self.error_message), e))
150
151 if current.response.s3.bulk:
152
153
154
155 import ast
156 try:
157 value_ = json.dumps(ast.literal_eval(value),
158 separators = SEPARATORS,
159 )
160 except JSONERRORS + (SyntaxError,), e:
161 return error(value, e)
162 if self.native_json:
163 return (value_, None)
164 else:
165 return (json.loads(value_), None)
166 else:
167
168 try:
169 if self.native_json:
170 json.loads(value)
171 return (value, None)
172 else:
173 return (json.loads(value), None)
174 except JSONERRORS, e:
175 return error(value, e)
176
177
188
191 """
192 example:
193
194 INPUT(_type="text", _name="name", requires=IS_LAT())
195
196 Latitude has to be in decimal degrees between -90 & 90
197 - we attempt to convert DMS format into decimal degrees
198 """
199
200 - def __init__(self,
201 error_message = "Latitude/Northing should be between -90 & 90!"
202 ):
203
204 self.minimum = -90
205 self.maximum = 90
206 self.error_message = error_message
207
208 self.mark_required = True
209
210
212 try:
213 value = float(value)
214 except ValueError:
215
216 pass
217 else:
218 if self.minimum <= value <= self.maximum:
219 return (value, None)
220 else:
221 return (value, self.error_message)
222
223 pattern = re.compile(r"^[0-9]{,3}[\D\W][0-9]{,3}[\D\W][0-9]+$")
224 if not pattern.match(value):
225 return (value, self.error_message)
226 else:
227 val = []
228 val.append(value)
229 sep = []
230 count = 0
231 for i in val[0]:
232 try:
233 int(i)
234 except ValueError:
235 sep.append(count)
236 count += 1
237 sec = ""
238 posn = sep[1]
239 while posn != (count-1):
240
241 sec = sec + val[0][posn+1]
242 posn += 1
243 posn2 = sep[0]
244 mins = ""
245 while posn2 != (sep[1]-1):
246
247 mins = mins + val[0][posn2+1]
248 posn2 += 1
249 deg = ""
250 posn3 = 0
251 while posn3 != (sep[0]):
252
253 deg = deg + val[0][posn3]
254 posn3 += 1
255 e = int(sec) / 60
256 f = int(mins) + e
257 g = int(f) / 60
258 value = int(deg) + g
259 return (value, None)
260
263 """
264 example:
265
266 INPUT(_type="text", _name="name", requires=IS_LON())
267
268 Longitude has to be in decimal degrees between -180 & 180
269 - we attempt to convert DMS format into decimal degrees
270 """
271
272 - def __init__(self,
273 error_message = "Longitude/Easting should be between -180 & 180!"
274 ):
275
276 super(IS_LON, self).__init__(error_message=error_message)
277
278 self.minimum = -180
279 self.maximum = 180
280
283 """
284 Designed for use within the S3LocationLatLonWidget.
285 For Create forms, this will create a new location from the additional fields
286 For Update forms, this will check that we have a valid location_id FK and update any changes
287
288 @ToDo: Audit
289 """
290
294
295 self.field = field
296
297 self.mark_required = True
298
299
301
302 if current.response.s3.bulk:
303
304 return (value, None)
305
306 selector = str(self.field).replace(".", "_")
307 post_vars = current.request.post_vars
308 lat = post_vars.get("%s_lat" % selector, None)
309 if lat == "":
310 lat = None
311 lon = post_vars.get("%s_lon" % selector, None)
312 if lon == "":
313 lon = None
314
315 if lat is None or lon is None:
316
317 return (value, current.T("Latitude and Longitude are required"))
318
319
320 lat, error = IS_LAT()(lat)
321 if error:
322 return (value, error)
323
324
325 lon, error = IS_LON()(lon)
326 if error:
327 return (value, error)
328
329 if value:
330
331 db = current.db
332 db(db.gis_location.id == value).update(lat=lat, lon=lon)
333 else:
334
335 value = current.db.gis_location.insert(lat=lat, lon=lon)
336
337
338 return (value, None)
339
342 """
343 Used by s3data.py to wrap IS_INT_AMOUNT & IS_FLOAT_AMOUNT
344 """
345
346
347 @staticmethod
349 if number is None:
350 return ""
351
352 if isinstance(number, int):
353 return IS_INT_AMOUNT.represent(number)
354 elif isinstance(number, float):
355 return IS_FLOAT_AMOUNT.represent(number, precision)
356 else:
357 return number
358
361 """
362 Validation, widget and representation of
363 integer-values with thousands-separators
364 """
365
366 - def __init__(self,
367 minimum=None,
368 maximum=None,
369 error_message=None,
370 ):
371
372 IS_INT_IN_RANGE.__init__(self,
373 minimum=minimum,
374 maximum=maximum,
375 error_message=error_message,
376 )
377
378
380
381 thousands_sep = current.deployment_settings.get_L10n_thousands_separator()
382 if thousands_sep:
383 value = s3_str(value).replace(thousands_sep, "")
384
385 return IS_INT_IN_RANGE.__call__(self, value)
386
387
388 @staticmethod
390 """
391 Change the format of the number depending on the language
392 Based on https://code.djangoproject.com/browser/django/trunk/django/utils/numberformat.py
393 """
394
395 if number is None:
396 return ""
397 try:
398 intnumber = int(number)
399 except ValueError:
400 intnumber = number
401
402 settings = current.deployment_settings
403 THOUSAND_SEPARATOR = settings.get_L10n_thousands_separator()
404 NUMBER_GROUPING = settings.get_L10n_thousands_grouping()
405
406
407 if float(number) < 0:
408 sign = "-"
409 else:
410 sign = ""
411
412 str_number = str(intnumber)
413
414 if str_number[0] == "-":
415 str_number = str_number[1:]
416
417
418 int_part_gd = ""
419 for cnt, digit in enumerate(str_number[::-1]):
420 if cnt and not cnt % NUMBER_GROUPING:
421 int_part_gd += THOUSAND_SEPARATOR
422 int_part_gd += digit
423 int_part = int_part_gd[::-1]
424
425 return sign + int_part
426
427
428 @staticmethod
437
440 """
441 Validation, widget and representation of
442 float-values with thousands-separators
443 """
444
445 - def __init__(self,
446 minimum=None,
447 maximum=None,
448 error_message=None,
449 dot=None,
450 ):
451
452 if dot is None:
453 dot = current.deployment_settings.get_L10n_decimal_separator()
454
455 IS_FLOAT_IN_RANGE.__init__(self,
456 minimum=minimum,
457 maximum=maximum,
458 error_message=error_message,
459 dot=dot,
460 )
461
462
464
465 thousands_sep = current.deployment_settings.get_L10n_thousands_separator()
466 if thousands_sep and isinstance(value, basestring):
467 value = s3_unicode(value).replace(thousands_sep, "") \
468 .encode("utf-8")
469
470 return IS_FLOAT_IN_RANGE.__call__(self, value)
471
472
473 @staticmethod
474 - def represent(number, precision=None, fixed=False):
475 """
476 Change the format of the number depending on the language
477 Based on https://code.djangoproject.com/browser/django/trunk/django/utils/numberformat.py
478
479 @param number: the number
480 @param precision: the number of decimal places to show
481 @param fixed: show decimal places even if the decimal part is 0
482 """
483
484 if number is None:
485 return ""
486
487 DECIMAL_SEPARATOR = current.deployment_settings.get_L10n_decimal_separator()
488
489 if precision is not None:
490 str_number = format(number, ".0%df" % precision)
491 else:
492
493 str_number = format(number, "f").rstrip("0") \
494 .rstrip(DECIMAL_SEPARATOR)
495
496 if "." in str_number:
497 int_part, dec_part = str_number.split(".")
498 else:
499 int_part, dec_part = str_number, ""
500
501 if dec_part and int(dec_part) == 0 and not fixed:
502
503 dec_part = ""
504
505 if dec_part:
506 dec_part = DECIMAL_SEPARATOR + dec_part
507
508 int_part = IS_INT_AMOUNT.represent(int(int_part))
509
510 return int_part + dec_part
511
512
513 @staticmethod
522
525 """
526 example::
527
528 INPUT(_type="text", _name="name", requires=IS_HTML_COLOUR())
529 """
530
531 - def __init__(self,
532 error_message="must be a 6 digit hex code! (format: rrggbb)"
533 ):
534 IS_MATCH.__init__(self, "^[0-9a-fA-F]{6}$", error_message)
535
536
537 regex1 = re.compile(r"[\w_]+\.[\w_]+")
538 regex2 = re.compile(r"%\((?P<name>[^\)]+)\)s")
541 """
542 Filtered version of IS_IN_DB():
543
544 validates a given value as key of another table, filtered by the
545 'filterby' field for one of the 'filter_opts' options
546 (=a selective IS_IN_DB())
547
548 NB Filtering isn't active in GQL.
549
550 For the dropdown representation:
551
552 'label' can be a string template for the record, or a set of field
553 names of the fields to be used as option labels, or a function or
554 lambda to create an option label from the respective record (which
555 has to return a string, of course). The function will take the
556 record as an argument.
557
558 No 'options' method as designed to be called next to an
559 Autocomplete field so don't download a large dropdown
560 unnecessarily.
561 """
562
563 - def __init__(self,
564 dbset,
565 field,
566 label=None,
567 filterby=None,
568 filter_opts=None,
569 not_filterby=None,
570 not_filter_opts=None,
571 realms=None,
572 updateable=False,
573 instance_types=None,
574 error_message="invalid value!",
575 orderby=None,
576 groupby=None,
577 left=None,
578 multiple=False,
579 zero="",
580 sort=True,
581 _and=None,
582 ):
583 """
584 Validator for foreign keys.
585
586 @param dbset: a Set of records like db(query), or db itself
587 @param field: the field in the referenced table
588 @param label: lookup method for the label corresponding a value,
589 alternatively a string template to be filled with
590 values from the record
591 @param filterby: a field in the referenced table to filter by
592 @param filter_opts: values for the filterby field which indicate
593 records to include
594 @param not_filterby: a field in the referenced table to filter by
595 @param not_filter_opts: values for not_filterby field which indicate
596 records to exclude
597 @param realms: only include records belonging to the listed realms
598 (if None, all readable records will be included)
599 @param updateable: only include records in the referenced table which
600 can be updated by the user (if False, all readable
601 records will be included)
602 @param instance_types: if the referenced table is a super-entity, then
603 only include these instance types (this parameter
604 is required for super entity lookups!)
605 @param error_message: the error message to return for failed validation
606 @param orderby: orderby for the options
607 @param groupby: groupby for the options
608 @param left: additional left joins required for the options lookup
609 (super-entity instance left joins will be included
610 automatically)
611 @param multiple: allow multiple values (for list:reference types)
612 @param zero: add this as label for the None-option (allow selection of "None")
613 @param sort: sort options alphabetically by their label
614 @param _and: internal use
615 """
616
617 if hasattr(dbset, "define_table"):
618 self.dbset = dbset()
619 else:
620 self.dbset = dbset
621 (ktable, kfield) = str(field).split(".")
622
623 if not label:
624 label = "%%(%s)s" % kfield
625
626 if isinstance(label, str):
627 if regex1.match(str(label)):
628 label = "%%(%s)s" % str(label).split(".")[-1]
629 ks = regex2.findall(label)
630 if not kfield in ks:
631 ks += [kfield]
632 fields = ["%s.%s" % (ktable, k) for k in ks]
633 elif hasattr(label, "bulk"):
634
635 ks = [kfield]
636 if label.custom_lookup:
637
638
639 fields = [kfield]
640 if orderby is None:
641 orderby = field
642 else:
643
644
645 label._setup()
646 fields = list(label.fields)
647 if kfield not in fields:
648 fields.insert(0, kfield)
649
650
651
652
653
654
655 else:
656 ks = [kfield]
657 try:
658 table = current.s3db[ktable]
659 fields =[str(f) for f in table if f.name not in ("wkt", "the_geom")]
660 except RuntimeError:
661 fields = "all"
662
663 self.fields = fields
664 self.label = label
665 self.ktable = ktable
666 if not kfield or not len(kfield):
667 self.kfield = "id"
668 else:
669 self.kfield = kfield
670 self.ks = ks
671 self.error_message = error_message
672 self.theset = None
673 self.orderby = orderby
674 self.groupby = groupby
675 self.left = left
676 self.multiple = multiple
677 self.zero = zero
678 self.sort = sort
679 self._and = _and
680
681 self.filterby = filterby
682 self.filter_opts = filter_opts
683 self.not_filterby = not_filterby
684 self.not_filter_opts = not_filter_opts
685
686 self.realms = realms
687 self.updateable = updateable
688 self.instance_types = instance_types
689
690
692 if self._and:
693 self._and.record_id = record_id
694
695
696 - def set_filter(self,
697 filterby = None,
698 filter_opts = None,
699 not_filterby = None,
700 not_filter_opts = None):
701 """
702 This can be called from prep to apply a filter based on
703 data in the record or the primary resource id.
704 """
705
706 if filterby:
707 self.filterby = filterby
708 if filter_opts:
709 self.filter_opts = filter_opts
710 if not_filterby:
711 self.not_filterby = not_filterby
712 if not_filter_opts:
713 self.not_filter_opts = not_filter_opts
714
715
717 """
718 Look up selectable options from the database
719 """
720
721 dbset = self.dbset
722 db = dbset._db
723
724 ktablename = self.ktable
725 if ktablename not in db:
726 table = current.s3db.table(ktablename, db_only=True)
727 else:
728 table = db[ktablename]
729
730 if table:
731 if self.fields == "all":
732 fields = [table[f] for f in table.fields if f not in ("wkt", "the_geom")]
733 else:
734 fieldnames = [f.split(".")[1] if "." in f else f for f in self.fields]
735 fields = [table[k] for k in fieldnames if k in table.fields]
736
737 if db._dbname not in ("gql", "gae"):
738
739 orderby = self.orderby or reduce(lambda a, b: a|b, fields)
740 groupby = self.groupby
741
742 left = self.left
743
744 dd = {"orderby": orderby, "groupby": groupby}
745 query, qleft = self.query(table, fields=fields, dd=dd)
746 if qleft is not None:
747 if left is not None:
748 if not isinstance(qleft, list):
749 qleft = [qleft]
750 ljoins = [str(join) for join in left]
751 for join in qleft:
752 ljoin = str(join)
753 if ljoin not in ljoins:
754 left.append(join)
755 ljoins.append(ljoin)
756 else:
757 left = qleft
758 if left is not None:
759 dd["left"] = left
760
761
762
763 fieldnames = set(str(f) for f in fields)
764 for f in s3_orderby_fields(table, dd.get("orderby")):
765 fieldname = str(f)
766 if fieldname not in fieldnames:
767 fields.append(f)
768 fieldnames.add(fieldname)
769
770 records = dbset(query).select(distinct=True, *fields, **dd)
771
772 else:
773
774 orderby = self.orderby or \
775 reduce(lambda a, b: a|b, (f for f in fields if f.type != "id"))
776 records = dbset.select(table.ALL,
777
778
779 orderby = orderby,
780 )
781
782 self.theset = [str(r[self.kfield]) for r in records]
783
784 label = self.label
785 try:
786
787 if hasattr(label, "bulk"):
788
789 d = label.bulk(None,
790 rows = records,
791 list_type = False,
792 show_link = False,
793 )
794 labels = [d.get(r[self.kfield], d[None]) for r in records]
795 else:
796
797 labels = [label(r) for r in records]
798 except TypeError:
799 if isinstance(label, str):
800 labels = [label % dict(r) for r in records]
801 elif isinstance(label, (list, tuple)):
802 labels = [" ".join([r[l] for l in label if l in r])
803 for r in records
804 ]
805 elif "name" in table:
806 labels = [r.name for r in records]
807 else:
808 labels = [r[self.kfield] for r in records]
809 self.labels = labels
810
811 if labels and self.sort:
812
813 items = zip(self.theset, self.labels)
814 items.sort(key=lambda item: s3_unicode(item[1]).lower())
815 self.theset, self.labels = zip(*items)
816
817 else:
818 self.theset = None
819 self.labels = None
820
821
822 - def query(self, table, fields=None, dd=None):
823 """
824 Construct the query to lookup the options (separated from
825 build_set so the query can be extracted and used in other
826 lookups, e.g. filter options).
827
828 @param table: the lookup table
829 @param fields: fields (updatable list)
830 @param dd: additional query options (updatable dict)
831 """
832
833
834 method = "update" if self.updateable else "read"
835 query, left = self.accessible_query(method, table,
836 instance_types=self.instance_types)
837
838
839 if "deleted" in table:
840 query &= (table["deleted"] != True)
841
842
843 if self.realms:
844 auth = current.auth
845 if auth.is_logged_in() and \
846 auth.get_system_roles().ADMIN in auth.user.realms:
847
848 pass
849 else:
850 query &= auth.permission.realm_query(table, self.realms)
851
852 all_fields = [str(f) for f in fields] if fields is not None else []
853
854 filterby = self.filterby
855 if filterby and filterby in table:
856
857 filter_opts = self.filter_opts
858
859 if filter_opts:
860 if None in filter_opts:
861
862 _query = (table[filterby] == None)
863 filter_opts = [f for f in filter_opts if f is not None]
864 if filter_opts:
865 _query = _query | (table[filterby].belongs(filter_opts))
866 query &= _query
867 else:
868 query &= (table[filterby].belongs(filter_opts))
869
870 if not self.orderby and \
871 fields is not None and dd is not None:
872 filterby_field = table[filterby]
873 if dd is not None:
874 dd.update(orderby=filterby_field)
875 if str(filterby_field) not in all_fields:
876 fields.append(filterby_field)
877 all_fields.append(str(filterby_field))
878
879 not_filterby = self.not_filterby
880 if not_filterby and not_filterby in table:
881
882 not_filter_opts = self.not_filter_opts
883
884 if not_filter_opts:
885 if None in not_filter_opts:
886
887 _query = (table[not_filterby] == None)
888 not_filter_opts = [f for f in not_filter_opts if f is not None]
889 if not_filter_opts:
890 _query = _query | (table[not_filterby].belongs(not_filter_opts))
891 query &= (~_query)
892 else:
893 query &= (~(table[not_filterby].belongs(not_filter_opts)))
894
895 if not self.orderby and \
896 fields is not None and dd is not None:
897 filterby_field = table[not_filterby]
898 if dd is not None:
899 dd.update(orderby=filterby_field)
900 if str(filterby_field) not in all_fields:
901 fields.append(filterby_field)
902 all_fields.append(str(filterby_field))
903
904 return query, left
905
906
907 @classmethod
909 """
910 Returns an accessible query (and left joins, if necessary) for
911 records in table the user is permitted to access with method
912
913 @param method: the method (e.g. "read" or "update")
914 @param table: the table
915 @param instance_types: list of instance tablenames, if table is
916 a super-entity (required in this case!)
917
918 @return: tuple (query, left) where query is the query and left joins
919 is the list of left joins required for the query
920
921 @note: for higher security policies and super-entities with many
922 instance types this can give a very complex query. Try to
923 always limit the instance types to what is really needed
924 """
925
926 DEFAULT = (table._id > 0)
927
928 left = None
929
930 if "instance_type" in table:
931
932 if not instance_types:
933 return DEFAULT, left
934 query = None
935 auth = current.auth
936 s3db = current.s3db
937 for instance_type in instance_types:
938 itable = s3db.table(instance_type)
939 if itable is None:
940 continue
941
942 join = itable.on(itable[table._id.name] == table._id)
943 if left is None:
944 left = [join]
945 else:
946 left.append(join)
947
948 q = (itable._id != None) & \
949 auth.s3_accessible_query(method, itable)
950 if "deleted" in itable:
951 q &= itable.deleted != True
952 if query is None:
953 query = q
954 else:
955 query |= q
956
957 if query is None:
958 query = DEFAULT
959 else:
960 query = current.auth.s3_accessible_query(method, table)
961
962 return query, left
963
964
965
966
967
968
970
971
972 error_message = self.error_message
973 if isinstance(error_message, basestring):
974 error_message = current.T(error_message)
975
976 try:
977 dbset = self.dbset
978 table = dbset._db[self.ktable]
979 deleted_q = ("deleted" in table) and (table["deleted"] == False) or False
980 filter_opts_q = False
981 filterby = self.filterby
982 if filterby and filterby in table:
983 filter_opts = self.filter_opts
984 if filter_opts:
985 if None in filter_opts:
986
987 filter_opts_q = (table[filterby] == None)
988 filter_opts = [f for f in filter_opts if f is not None]
989 if filter_opts:
990 filter_opts_q |= (table[filterby].belongs(filter_opts))
991 else:
992 filter_opts_q = (table[filterby].belongs(filter_opts))
993
994 if self.multiple:
995 if isinstance(value, list):
996 values = [str(v) for v in value]
997 elif isinstance(value, basestring) and \
998 value[0] == "|" and value[-1] == "|":
999 values = value[1:-1].split("|")
1000 elif value:
1001 values = [value]
1002 else:
1003 values = []
1004
1005 if self.theset:
1006 if not [x for x in values if not x in self.theset]:
1007 return (values, None)
1008 else:
1009 return (value, error_message)
1010 else:
1011 field = table[self.kfield]
1012 query = None
1013 for v in values:
1014 q = (field == v)
1015 query = query is not None and query | q or q
1016 if filter_opts_q != False:
1017 query = query is not None and \
1018 (filter_opts_q & (query)) or filter_opts_q
1019 if deleted_q != False:
1020 query = query is not None and \
1021 (deleted_q & (query)) or deleted_q
1022 if dbset(query).count() < 1:
1023 return (value, error_message)
1024 return (values, None)
1025 elif self.theset:
1026 if str(value) in self.theset:
1027 if self._and:
1028 return self._and(value)
1029 else:
1030 return (value, None)
1031 else:
1032 values = [value]
1033 query = None
1034 for v in values:
1035 q = (table[self.kfield] == v)
1036 query = query is not None and query | q or q
1037 if filter_opts_q != False:
1038 query = query is not None and \
1039 (filter_opts_q & (query)) or filter_opts_q
1040 if deleted_q != False:
1041 query = query is not None and \
1042 (deleted_q & (query)) or deleted_q
1043 if dbset(query).count():
1044 if self._and:
1045 return self._and(value)
1046 else:
1047 return (value, None)
1048 except:
1049 pass
1050
1051 return (value, error_message)
1052
1056 """
1057 Extends IS_ONE_OF_EMPTY by restoring the 'options' method.
1058 """
1059
1071
1074
1075 """
1076 Extends IS_ONE_OF_EMPTY by displaying an empty SELECT (instead of INPUT)
1077 """
1078
1081
1084 """
1085 Filtered version of IS_NOT_IN_DB()
1086 - understands the 'deleted' field.
1087 - makes the field unique (amongst non-deleted field)
1088
1089 Example:
1090 - INPUT(_type="text", _name="name", requires=IS_NOT_ONE_OF(db, db.table))
1091 """
1092
1094
1095 value = str(value)
1096 if not value.strip():
1097
1098 return (value, translate(self.error_message))
1099
1100 if value in self.allowed_override:
1101
1102 return (value, None)
1103
1104
1105 (tablename, fieldname) = str(self.field).split(".")
1106 dbset = self.dbset
1107 table = dbset.db[tablename]
1108 field = table[fieldname]
1109
1110
1111 archived = "deleted" in table
1112
1113
1114 record_id = self.record_id
1115 keys = record_id.keys() if isinstance(record_id, dict) else None
1116
1117
1118
1119
1120
1121 query = (field == value)
1122 if not field.unique and archived:
1123 query = (table["deleted"] == False) & query
1124
1125
1126 fields = []
1127 if keys:
1128 fields = [table[k] for k in keys]
1129 else:
1130 fields = [table._id]
1131 if archived:
1132 fields.append(table.deleted)
1133
1134
1135 row = dbset(query).select(limitby=(0, 1), *fields).first()
1136 if row:
1137 if keys:
1138
1139 for f in keys:
1140 if str(getattr(row, f)) != str(record_id[f]):
1141 return (value, translate(self.error_message))
1142
1143 elif str(row[table._id.name]) != str(record_id):
1144
1145 if archived and row.deleted and field.type in ("string", "text"):
1146
1147
1148
1149 import random
1150 tagged = "%s.[%s]" % (value,
1151 "".join(random.choice("abcdefghijklmnopqrstuvwxyz")
1152 for _ in range(8))
1153 )
1154 try:
1155 row.update_record(**{fieldname: tagged})
1156 except:
1157
1158 return (value, translate(self.error_message))
1159 else:
1160 return (value, translate(self.error_message))
1161
1162 return (value, None)
1163
1166 """
1167 Allow all locations, or locations by level.
1168 """
1169
1170 - def __init__(self,
1171 level = None,
1172 error_message = None
1173 ):
1174
1175 self.level = level
1176 self.error_message = error_message
1177
1178 self.ktable = "gis_location"
1179 self.kfield = "id"
1180
1181 self.mark_required = True
1182
1183
1185
1186 level = self.level
1187 if level == "L0":
1188
1189 try:
1190 location_id = int(value)
1191 except ValueError:
1192 ok = False
1193 else:
1194 ok = current.gis.get_country(location_id)
1195 else:
1196 db = current.db
1197 table = db.gis_location
1198 query = (table.id == value) & (table.deleted == False)
1199 if level:
1200 if isinstance(level, (tuple, list)):
1201 if None in level:
1202
1203 level = [l for l in level if l is not None]
1204 query &= ((table.level.belongs(level)) | \
1205 (table.level == None))
1206 else:
1207 query &= (table.level.belongs(level))
1208 else:
1209 query &= (table.level == level)
1210 ok = db(query).select(table.id, limitby=(0, 1)).first()
1211 if ok:
1212 return (value, None)
1213 else:
1214 return (value, self.error_message or current.T("Invalid Location!"))
1215
1218 """
1219 Uses an S3ImageCropWidget to allow the user to crop/scale images and
1220 processes the results sent by the browser.
1221
1222 @param file_cb: callback that returns the file for this field
1223
1224 @param error_message: the error message to be returned
1225
1226 @param image_bounds: the boundaries for the processed image
1227
1228 @param upload_path: upload path for the image
1229 """
1230
1231 - def __init__(self,
1232 field_name,
1233 file_cb,
1234 error_message="No image was specified!",
1235 image_bounds=(300, 300),
1236 upload_path=None,
1237 ):
1238
1239 self.field_name = field_name
1240 self.file_cb = file_cb
1241 self.error_message = error_message
1242 self.image_bounds = image_bounds
1243 self.upload_path = upload_path
1244
1246
1247 if current.response.s3.bulk:
1248
1249 return (value, None)
1250
1251 r = current.request
1252 post_vars = r.post_vars
1253
1254 if r.env.request_method == "GET":
1255 return (value, None)
1256
1257
1258
1259
1260 uploaded_image = post_vars.get(self.field_name)
1261 if uploaded_image not in ("", None):
1262 return (uploaded_image, None)
1263
1264 cropped_image = post_vars.get("imagecrop-data")
1265 uploaded_image = self.file_cb()
1266
1267 if not (cropped_image or uploaded_image):
1268 return value, current.T(self.error_message)
1269
1270
1271
1272 if cropped_image:
1273 import base64
1274 import uuid
1275 try:
1276 from cStringIO import StringIO
1277 except ImportError:
1278 from StringIO import StringIO
1279
1280 metadata, cropped_image = cropped_image.split(",")
1281
1282 filename = metadata.split(";", 1)[0]
1283
1284 f = Storage()
1285 f.filename = uuid.uuid4().hex + filename
1286
1287 f.file = StringIO(base64.decodestring(cropped_image))
1288
1289 return (f, None)
1290
1291
1292 points = post_vars.get("imagecrop-points")
1293 if points and uploaded_image:
1294 import os
1295 points = map(float, points.split(","))
1296
1297 if not self.upload_path:
1298 path = os.path.join(r.folder, "uploads", "images", uploaded_image)
1299 else:
1300 path = os.path.join(self.upload_path, uploaded_image)
1301
1302 current.s3task.async("crop_image",
1303 args=[path] + points + [self.image_bounds[0]])
1304
1305 return (None, None)
1306
1309 """
1310 Validates a given string value as UTC offset in the format +/-HHMM
1311
1312 @param error_message: the error message to be returned
1313
1314 @note:
1315 all leading parts of the string (before the trailing offset specification)
1316 will be ignored and replaced by 'UTC ' in the return value, if the string
1317 passes through.
1318 """
1319
1320 - def __init__(self, error_message="invalid UTC offset!"):
1321
1322 self.error_message = error_message
1323
1324
1326
1327 if value and isinstance(value, str):
1328
1329 offset = S3DateTime.get_offset_value(value)
1330 if offset is not None:
1331 hours, seconds = divmod(abs(offset), 3600)
1332 minutes = int(seconds / 60)
1333 sign = "-" if offset < 0 else "+"
1334 return ("%s%02d%02d" % (sign, hours, minutes), None)
1335
1336 return (value, self.error_message)
1337
1340 """
1341 Validates a given date/time and returns it as timezone-naive
1342 datetime object in UTC. Accepted input types are strings (in
1343 local format), datetime.datetime and datetime.date.
1344
1345 Example:
1346 - INPUT(_type="text", _name="name", requires=IS_UTC_DATETIME())
1347
1348 @note: a date/time string must be in local format, and can have
1349 an optional trailing UTC offset specified as +/-HHMM
1350 (+ for eastern, - for western timezones)
1351 @note: dates stretch 8 hours West and 16 hours East of the current
1352 time zone, i.e. the most Eastern timezones are on the next
1353 day.
1354 """
1355
1356 - def __init__(self,
1357 format=None,
1358 error_message=None,
1359 offset_error=None,
1360 utc_offset=None,
1361 calendar=None,
1362 minimum=None,
1363 maximum=None):
1364 """
1365 Constructor
1366
1367 @param format: strptime/strftime format template string, for
1368 directives refer to your strptime implementation
1369 @param error_message: error message for invalid date/times
1370 @param offset_error: error message for invalid UTC offset
1371 @param utc_offset: offset to UTC in hours, defaults to the
1372 current session's UTC offset
1373 @param calendar: calendar to use for string evaluation, defaults
1374 to current.calendar
1375 @param minimum: the minimum acceptable date/time
1376 @param maximum: the maximum acceptable date/time
1377 """
1378
1379 if format is None:
1380 self.format = str(current.deployment_settings.get_L10n_datetime_format())
1381 else:
1382 self.format = str(format)
1383
1384 if isinstance(calendar, basestring):
1385
1386 from s3datetime import S3Calendar
1387 calendar = S3Calendar(calendar)
1388 elif calendar == None:
1389 calendar = current.calendar
1390 self.calendar = calendar
1391
1392 self.utc_offset = utc_offset
1393
1394 self.minimum = minimum
1395 self.maximum = maximum
1396
1397
1398 T = current.T
1399 if error_message is None:
1400 if minimum is None and maximum is None:
1401 error_message = T("Date/Time is required!")
1402 elif minimum is None:
1403 error_message = T("Date/Time must be %(max)s or earlier!")
1404 elif maximum is None:
1405 error_message = T("Date/Time must be %(min)s or later!")
1406 else:
1407 error_message = T("Date/Time must be between %(min)s and %(max)s!")
1408 if offset_error is None:
1409 offset_error = T("Invalid UTC offset!")
1410
1411
1412 mindt = self.formatter(minimum) if minimum else ""
1413 maxdt = self.formatter(maximum) if maximum else ""
1414
1415
1416 self.error_message = error_message % {"min": mindt, "max": maxdt}
1417 self.offset_error = offset_error
1418
1419
1420 - def delta(self, utc_offset=None):
1421 """
1422 Compute the delta in seconds for the current UTC offset
1423
1424 @param utc_offset: the offset (override defaults)
1425 @return: the offset in seconds
1426 """
1427
1428 if utc_offset is None:
1429
1430 utc_offset = self.utc_offset
1431
1432 if utc_offset is None:
1433
1434 utc_offset = current.session.s3.utc_offset
1435
1436
1437 return S3DateTime.get_offset_value(utc_offset)
1438
1439
1441 """
1442 Validate a value, and convert it into a timezone-naive
1443 datetime.datetime object as necessary
1444
1445 @param value: the value to validate
1446 @return: tuple (value, error)
1447 """
1448
1449 if isinstance(value, basestring):
1450
1451 val = value.strip()
1452
1453
1454 if len(val) > 5 and val[-5] in ("+", "-") and val[-4:].isdigit():
1455 dtstr, utc_offset = val[0:-5].strip(), val[-5:]
1456 else:
1457 dtstr, utc_offset = val, None
1458
1459
1460 dt = self.calendar.parse_datetime(dtstr,
1461 dtfmt=self.format,
1462 local=True,
1463 )
1464 if dt is None:
1465
1466 dt_ = self.calendar.parse_date(dtstr)
1467 if dt_ is None:
1468 return(value, self.error_message)
1469 dt = datetime.datetime.combine(dt_, datetime.datetime.min.time())
1470 elif isinstance(value, datetime.datetime):
1471 dt = value
1472 utc_offset = None
1473 elif isinstance(value, datetime.date):
1474
1475 dt = datetime.datetime.combine(value, datetime.time(8, 0, 0))
1476 utc_offset = None
1477 else:
1478
1479 return value, self.error_message
1480
1481
1482 if dt.tzinfo:
1483 offset = dt.tzinfo.utcoffset(dt)
1484 dt = dt.replace(tzinfo=None)
1485 else:
1486 offset = self.delta(utc_offset=utc_offset)
1487
1488 if not -86340 < offset < 86340:
1489 return (val, self.offset_error)
1490 offset = datetime.timedelta(seconds=offset)
1491 dt_utc = dt - offset
1492
1493
1494 if self.minimum and dt_utc < self.minimum or \
1495 self.maximum and dt_utc > self.maximum:
1496 return (dt_utc, self.error_message)
1497
1498 return (dt_utc, None)
1499
1500
1519
1522 """
1523 Validates a given date and returns the corresponding datetime.date
1524 object in UTC. Accepted input types are strings (in local format),
1525 datetime.datetime and datetime.date.
1526
1527 Example:
1528 - INPUT(_type="text", _name="name", requires=IS_UTC_DATE())
1529
1530 @note: dates stretch 8 hours West and 16 hours East of the current
1531 time zone, i.e. the most Eastern timezones are on the next
1532 day.
1533 """
1534
1535 - def __init__(self,
1536 format=None,
1537 error_message=None,
1538 offset_error=None,
1539 calendar=None,
1540 utc_offset=None,
1541 minimum=None,
1542 maximum=None):
1543 """
1544 Constructor
1545
1546 @param format: strptime/strftime format template string, for
1547 directives refer to your strptime implementation
1548 @param error_message: error message for invalid date/times
1549 @param offset_error: error message for invalid UTC offset
1550 @param calendar: calendar to use for string evaluation, defaults
1551 to current.calendar
1552 @param utc_offset: offset to UTC in seconds, defaults to the
1553 current session's UTC offset
1554 @param minimum: the minimum acceptable date (datetime.date)
1555 @param maximum: the maximum acceptable date (datetime.date)
1556 """
1557
1558 if format is None:
1559 self.format = str(current.deployment_settings.get_L10n_date_format())
1560 else:
1561 self.format = str(format)
1562
1563 if isinstance(calendar, basestring):
1564
1565 from s3datetime import S3Calendar
1566 calendar = S3Calendar(calendar)
1567 elif calendar == None:
1568 calendar = current.calendar
1569 self.calendar = calendar
1570
1571 self.utc_offset = utc_offset
1572
1573 self.minimum = minimum
1574 self.maximum = maximum
1575
1576
1577 T = current.T
1578 if error_message is None:
1579 if minimum is None and maximum is None:
1580 error_message = T("Date is required!")
1581 elif minimum is None:
1582 error_message = T("Date must be %(max)s or earlier!")
1583 elif maximum is None:
1584 error_message = T("Date must be %(min)s or later!")
1585 else:
1586 error_message = T("Date must be between %(min)s and %(max)s!")
1587 if offset_error is None:
1588 offset_error = T("Invalid UTC offset!")
1589
1590
1591 mindt = self.formatter(minimum) if minimum else ""
1592 maxdt = self.formatter(maximum) if maximum else ""
1593
1594
1595 self.error_message = error_message % {"min": mindt, "max": maxdt}
1596 self.offset_error = offset_error
1597
1598
1600 """
1601 Validate a value, and convert it into a datetime.date object
1602 as necessary
1603
1604 @param value: the value to validate
1605 @return: tuple (value, error)
1606 """
1607
1608 is_datetime = False
1609
1610 if isinstance(value, basestring):
1611
1612 dt = self.calendar.parse_date(value.strip(),
1613 dtfmt=self.format,
1614 local=True,
1615 )
1616 if dt is None:
1617 return(value, self.error_message)
1618 elif isinstance(value, datetime.datetime):
1619 dt = value
1620
1621 is_datetime = True
1622 elif isinstance(value, datetime.date):
1623
1624 dt = value
1625
1626 else:
1627
1628 return (value, self.error_message)
1629
1630
1631 if is_datetime and dt.tzinfo:
1632 offset = dt.tzinfo.utcoffset(dt)
1633 dt = dt.replace(tzinfo=None)
1634 else:
1635 offset = self.delta()
1636
1637 if not -86340 < offset < 86340:
1638 return (value, self.offset_error)
1639 offset = datetime.timedelta(seconds=offset)
1640
1641 if not is_datetime:
1642
1643 dt = datetime.datetime.combine(dt, datetime.time(8, 0, 0))
1644 dt_utc = (dt - offset).date()
1645
1646
1647 if self.minimum and dt_utc < self.minimum or \
1648 self.maximum and dt_utc > self.maximum:
1649 return (value, self.error_message)
1650
1651 return (dt_utc, None)
1652
1653
1680
1681
1682 -class IS_ACL(IS_IN_SET):
1683
1684 """
1685 Validator for ACLs
1686
1687 @attention: Incomplete! Does not validate yet, but just convert.
1688 """
1689
1691 """
1692 Validation
1693
1694 @param value: the value to validate
1695 """
1696
1697 if not isinstance(value, (list, tuple)):
1698 value = [value]
1699
1700 acl = 0x0000
1701 for v in value:
1702 try:
1703 flag = int(v)
1704 except (ValueError, TypeError):
1705 flag = 0x0000
1706 else:
1707 acl |= flag
1708
1709 return (acl, None)
1710
1713 """
1714 Designed for use with an Autocomplete.
1715 - catches any new entries & creates the appropriate record
1716 @ToDo: Audit
1717 """
1718
1719 - def __init__(self,
1720 tablename,
1721 requires,
1722 error_message = None,
1723 ):
1724 self.tablename = tablename
1725 self.requires = requires
1726 self.error_message = error_message
1727
1728
1730
1731 if not value:
1732
1733 return self.requires(value)
1734 elif isinstance(value, int):
1735
1736
1737
1738
1739 return self.requires(value)
1740 else:
1741
1742 tablename = self.tablename
1743 db = current.db
1744 table = db[tablename]
1745
1746
1747 query = (table.name == value)
1748 r = db(query).select(table.id,
1749 limitby=(0, 1)).first()
1750 if r:
1751
1752 value = r.id
1753 return (value, None)
1754 if not current.auth.s3_has_permission("create", table):
1755 return (None, current.auth.messages.access_denied)
1756 value = table.insert(name=value)
1757
1758 onaccept = current.s3db.get_config(tablename, "onaccept")
1759 if onaccept:
1760 onaccept(form=Storage(vars=Storage(id=value)))
1761 return (value, None)
1762
1765 """
1766 For Inventory module
1767 """
1768
1769 - def __init__(self,
1770 db,
1771 inv_item_id,
1772 item_pack_id
1773 ):
1774
1775 self.inv_item_id = inv_item_id
1776 self.item_pack_id = item_pack_id
1777 current.db = db
1778
1779
1781
1782 db = current.db
1783 args = current.request.args
1784 track_quantity = 0
1785 if args[1] == "track_item" and len(args) > 2:
1786
1787 track_item_id = args[2]
1788
1789 track_record = current.s3db.inv_track_item[track_item_id]
1790 track_quantity = track_record.quantity
1791 if track_quantity >= float(value):
1792
1793 return (value, None)
1794 error = "Invalid Quantity"
1795 query = (db.inv_inv_item.id == self.inv_item_id) & \
1796 (db.inv_inv_item.item_pack_id == db.supply_item_pack.id)
1797 inv_item_record = db(query).select(db.inv_inv_item.quantity,
1798 db.supply_item_pack.quantity,
1799 db.supply_item_pack.name,
1800 limitby = (0, 1)).first()
1801 if inv_item_record and value:
1802 query = (db.supply_item_pack.id == self.item_pack_id)
1803 send_record = db(query).select(db.supply_item_pack.quantity,
1804 limitby=(0, 1)).first()
1805 send_quantity = (float(value) - track_quantity) * send_record.quantity
1806 inv_quantity = inv_item_record.inv_inv_item.quantity * \
1807 inv_item_record.supply_item_pack.quantity
1808 if send_quantity > inv_quantity:
1809 return (value,
1810 "Only %s %s (%s) in the Warehouse Stock." %
1811 (inv_quantity,
1812 inv_item_record.supply_item_pack.name,
1813 inv_item_record.supply_item_pack.quantity)
1814 )
1815 else:
1816 return (value, None)
1817 else:
1818 return (value, error)
1819
1822 """
1823 Like IS_IN_SET but with options obtained from a supplied function.
1824
1825 Options are instantiated when the validator or its options() method is
1826 called, so don't need to be generated until it's used. Useful if the
1827 field is not needed on every request, and does significant processing
1828 to construct its options, or generates a large collection. If the
1829 options are just from a database query, one can use IS_ONE_OF instead.
1830
1831 Raises an exception if an options collection is passed rather than a
1832 callable as this is a programming error, e.g. accidentally *calling*
1833 the options function in the constructor instead of passing the
1834 function. That would not get lazy options instantiation.
1835
1836 The options collection (theset) and labels collection parameters to
1837 IS_IN_SET are replaced by:
1838
1839 @param theset_fn: Function of no arguments that returns a collection
1840 of options and (optionally) labels. Both options and labels can be
1841 supplied via a dict or OrderedDict (options are keys, values are
1842 labels), list (or tuple) of two-element lists (or tuples) (element 0 in
1843 each pair is an option, element 1 is it's label). Otherwise, labels
1844 are obtained either by calling the supplied represent function on each
1845 item produced by theset_fn, or (if no represent is supplied), the items
1846 themselves are used as labels.
1847
1848 @param represent: Function of one argument that returns the label for
1849 a given option.
1850
1851 If there is a function call that returns the collection, just put
1852 "lambda:" in front of the call. E.g.:
1853
1854 Field("nationality",
1855 requires = IS_EMPTY_OR(IS_IN_SET_LAZY(
1856 lambda: gis.get_countries(key_type="code"))),
1857 label = T("Nationality"),
1858 represent = lambda code: gis.get_country(code, key_type="code") or UNKNOWN_OPT)
1859
1860 Keyword parameters are same as for IS_IN_SET, except for labels, which
1861 is not replaced by a function that parallels theset_fn, since ordering
1862 is problematic if theset_fn returns a dict.
1863 """
1864
1865 - def __init__(
1866 self,
1867 theset_fn,
1868 represent=None,
1869 error_message="value not allowed",
1870 multiple=False,
1871 zero="",
1872 sort=False,
1873 ):
1874 self.multiple = multiple
1875 if not callable(theset_fn):
1876 raise TypeError("Argument must be a callable.")
1877 self.theset_fn = theset_fn
1878 self.theset = None
1879 self.labels = None
1880 self.represent = represent
1881 self.error_message = error_message
1882 self.zero = zero
1883 self.sort = sort
1884
1885
1905
1906
1908 if not self.theset:
1909 self._make_theset()
1910 if not self.labels:
1911 items = [(k, k) for (i, k) in enumerate(self.theset)]
1912 else:
1913 items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)]
1914 if self.sort:
1915 items.sort(options_sorter)
1916 if zero and not self.zero is None and not self.multiple:
1917 items.insert(0, ("", self.zero))
1918 return items
1919
1920
1922 if not self.theset:
1923 self._make_theset()
1924 if self.multiple:
1925
1926 if isinstance(value, (str,unicode)):
1927 values = [value]
1928 elif isinstance(value, (tuple, list)):
1929 values = value
1930 elif not value:
1931 values = []
1932 else:
1933 values = [value]
1934 failures = [x for x in values if not x in self.theset]
1935 if failures and self.theset:
1936 if self.multiple and (value == None or value == ""):
1937 return ([], None)
1938 return (value, self.error_message)
1939 if self.multiple:
1940 if isinstance(self.multiple,(tuple,list)) and \
1941 not self.multiple[0]<=len(values)<self.multiple[1]:
1942 return (values, self.error_message)
1943 return (values, None)
1944 return (value, None)
1945
1971
1974 """
1975 Special validator for pr_person.gender and derivates,
1976 accepts the "O" option even if it's not in the set.
1977 """
1978
1980
1981 if value == 4:
1982
1983 return value, None
1984 else:
1985 return super(IS_PERSON_GENDER, self).__call__(value)
1986
1989 """
1990 Validator for single phone numbers with option to
1991 enforce E.123 international notation (with leading +
1992 and no punctuation or spaces).
1993 """
1994
1995 - def __init__(self,
1996 international = False,
1997 error_message = None):
1998 """
1999 Constructor
2000
2001 @param international: enforce E.123 international notation,
2002 no effect if turned off globally in
2003 deployment settings
2004 @param error_message: alternative error message
2005 """
2006
2007 self.international = international
2008 self.error_message = error_message
2009
2011 """
2012 Validation of a value
2013
2014 @param value: the value
2015 @return: tuple (value, error), where error is None if value
2016 is valid. With international=True, the value returned
2017 is converted into E.123 international notation.
2018 """
2019
2020 if isinstance(value, basestring):
2021 value = value.strip()
2022 if value and value[0] == unichr(8206):
2023
2024 value = value[1:]
2025 number = s3_str(value)
2026 number, error = s3_single_phone_requires(number)
2027 else:
2028 error = True
2029
2030 if not error:
2031 if self.international and \
2032 current.deployment_settings \
2033 .get_msg_require_international_phone_numbers():
2034
2035
2036 error_message = self.error_message
2037 if not error_message:
2038 error_message = current.T("Enter phone number in international format like +46783754957")
2039
2040
2041 number = "".join(re.findall(r"[\d+]+", number))
2042 match = re.match(r"(\+)([1-9]\d+)$", number)
2043
2044
2045 if match:
2046 number = "+%s" % match.groups()[1]
2047 return (number, None)
2048 else:
2049 return (number, None)
2050
2051 error_message = self.error_message
2052 if not error_message:
2053 error_message = current.T("Enter a valid phone number")
2054
2055 return (value, error_message)
2056
2059 """
2060 Validator for multiple phone numbers.
2061 """
2062
2063 - def __init__(self,
2064 error_message = None):
2065 """
2066 Constructor
2067
2068 @param error_message: alternative error message
2069 """
2070
2071 self.error_message = error_message
2072
2074 """
2075 Validation of a value
2076
2077 @param value: the value
2078 @return: tuple (value, error), where error is None if value
2079 is valid.
2080 """
2081
2082 value = value.strip()
2083 if value[0] == unichr(8206):
2084
2085 value = value[1:]
2086 number = s3_str(value)
2087 number, error = s3_phone_requires(number)
2088 if not error:
2089 return (number, None)
2090
2091 error_message = self.error_message
2092 if not error_message:
2093 error_message = current.T("Enter a valid phone number")
2094
2095 return (value, error_message)
2096
2099 """ Validator for field names in dynamic tables """
2100
2101 PATTERN = re.compile("^[a-z]+[a-z0-9_]*$")
2102
2103 - def __init__(self,
2104 error_message = "Invalid field name",
2105 ):
2106 """
2107 Constructor
2108
2109 @param error_message: the error message for invalid values
2110 """
2111
2112 self.error_message = error_message
2113
2114
2135
2138 """ Validator for field types in dynamic tables """
2139
2140 SUPPORTED_TYPES = ("boolean",
2141 "date",
2142 "datetime",
2143 "double",
2144 "integer",
2145 "reference",
2146 "string",
2147 "text",
2148 "upload",
2149 )
2150
2151 - def __init__(self,
2152 error_message = "Unsupported field type",
2153 ):
2154 """
2155 Constructor
2156
2157 @param error_message: the error message for invalid values
2158 """
2159
2160 self.error_message = error_message
2161
2162
2164 """
2165 Validation of a value
2166
2167 @param value: the value
2168 @return: tuple (value, error)
2169 """
2170
2171 if value:
2172
2173 field_type = str(value).lower().strip()
2174
2175 items = field_type.split(" ")
2176 base_type = items[0]
2177
2178 if base_type == "reference":
2179
2180
2181 if len(items) > 1:
2182 ktablename = items[1].split(".")[0]
2183 ktable = current.s3db.table(ktablename, db_only=True)
2184 if ktable:
2185 return (field_type, None)
2186
2187 elif base_type in self.SUPPORTED_TYPES:
2188 return (field_type, None)
2189
2190 return (value, self.error_message)
2191
2194 """
2195 Validate ISO639-2 Alpha-2/Alpha-3 language codes
2196 """
2197
2198 - def __init__(self,
2199 error_message = "Invalid language code",
2200 multiple = False,
2201 select = DEFAULT,
2202 sort = False,
2203 translate = False,
2204 zero = "",
2205 ):
2206 """
2207 Constructor
2208
2209 @param error_message: alternative error message
2210 @param multiple: allow selection of multiple options
2211 @param select: dict of options for the selector,
2212 defaults to settings.L10n.languages,
2213 set explicitly to None to allow all languages
2214 @param sort: sort options in selector
2215 @param translate: translate the language options into
2216 the current UI language
2217 @param zero: use this label for the empty-option (default="")
2218 """
2219
2220 super(IS_ISO639_2_LANGUAGE_CODE, self).__init__(
2221 self.language_codes(),
2222 error_message = error_message,
2223 multiple = multiple,
2224 zero = zero,
2225 sort = sort,
2226 )
2227
2228 if select is DEFAULT:
2229 self._select = current.deployment_settings.get_L10n_languages()
2230 else:
2231 self._select = select
2232 self.translate = translate
2233
2234
2236 """
2237 Get the options for the selector. This could be only a subset
2238 of all valid options (self._select), therefore overriding
2239 superclass function here.
2240 """
2241
2242 language_codes = self.language_codes()
2243
2244 if self._select:
2245 language_codes_dict = dict(language_codes)
2246 if self.translate:
2247 T = current.T
2248 items = ((k, T(v)) for k, v in self._select.items()
2249 if k in language_codes_dict)
2250 else:
2251 items = ((k, v) for k, v in self._select.items()
2252 if k in language_codes_dict)
2253 else:
2254 if self.translate:
2255 T = current.T
2256 items = ((k, T(v)) for k, v in language_codes)
2257 else:
2258 items = language_codes
2259
2260 if self.sort:
2261 items = sorted(items, key=lambda s: s3_unicode(s[1]).lower())
2262 else:
2263 items = list(items)
2264
2265 if zero and not self.zero is None and not self.multiple:
2266 items.insert(0, ("", self.zero))
2267
2268 return items
2269
2270
2272 """
2273 Represent a language code by language name, uses the
2274 representation from deployment_settings if available
2275 (to allow overrides).
2276
2277 @param code: the language code
2278 """
2279
2280 if not code:
2281 return current.messages["NONE"]
2282
2283 l10n_languages = current.deployment_settings.get_L10n_languages()
2284 name = l10n_languages.get(code)
2285 if not name:
2286 name = dict(self.language_codes()).get(code.split("-")[0])
2287 if name is None:
2288 return current.messages.UNKNOWN_OPT
2289
2290 if self.translate:
2291 name = current.T(name)
2292
2293 return name
2294
2295
2296 @classmethod
2298 """
2299 Represent a language code by the name of the language in that
2300 language. e.g. for Use in a Language dropdown
2301
2302 @param code: the language code
2303 """
2304
2305 if not code:
2306 return current.messages["NONE"]
2307
2308 l10n_languages = current.deployment_settings.get_L10n_languages()
2309 name = l10n_languages.get(code)
2310 if not name:
2311 name = dict(cls.language_codes()).get(code.split("-")[0])
2312 if name is None:
2313 return current.messages.UNKNOWN_OPT
2314
2315 T = current.T
2316 name = s3_str(T(name, language=code))
2317
2318 return name
2319
2320
2321 @staticmethod
2323 """
2324 Returns a list of tuples of ISO639-1 alpha-2 language
2325 codes, can also be used to look up the language name
2326
2327 Just the subset which are useful for Translations
2328 - 2 letter code preferred, 3-letter code where none exists,
2329 no 'families' or Old
2330 """
2331
2332 lang = [
2333 ("aa", "Afar"),
2334
2335 ("ab", "Abkhazian"),
2336 ("ace", "Achinese"),
2337 ("ach", "Acoli"),
2338 ("ada", "Adangme"),
2339 ("ady", "Adyghe; Adygei"),
2340
2341 ("afh", "Afrihili"),
2342
2343 ("af", "Afrikaans"),
2344 ("ain", "Ainu"),
2345
2346 ("ak", "Akan"),
2347 ("akk", "Akkadian"),
2348
2349 ("sq", "Albanian"),
2350 ("ale", "Aleut"),
2351
2352 ("alt", "Southern Altai"),
2353
2354 ("am", "Amharic"),
2355
2356 ("anp", "Angika"),
2357
2358
2359 ("ar", "Arabic"),
2360
2361
2362 ("an", "Aragonese"),
2363
2364 ("hy", "Armenian"),
2365 ("arn", "Mapudungun; Mapuche"),
2366 ("arp", "Arapaho"),
2367
2368 ("arw", "Arawak"),
2369
2370 ("as", "Assamese"),
2371 ("ast", "Asturian; Bable; Leonese; Asturleonese"),
2372
2373
2374
2375 ("av", "Avaric"),
2376
2377 ("ae", "Avestan"),
2378 ("awa", "Awadhi"),
2379
2380 ("ay", "Aymara"),
2381
2382 ("az", "Azerbaijani"),
2383
2384
2385
2386 ("ba", "Bashkir"),
2387 ("bal", "Baluchi"),
2388
2389 ("bm", "Bambara"),
2390 ("ban", "Balinese"),
2391
2392 ("eu", "Basque"),
2393 ("bas", "Basa"),
2394
2395 ("bej", "Beja; Bedawiyet"),
2396
2397 ("be", "Belarusian"),
2398 ("bem", "Bemba"),
2399
2400 ("bn", "Bengali"),
2401
2402 ("bho", "Bhojpuri"),
2403
2404
2405 ("bik", "Bikol"),
2406 ("bin", "Bini; Edo"),
2407
2408 ("bi", "Bislama"),
2409 ("bla", "Siksika"),
2410
2411
2412 ("bs", "Bosnian"),
2413 ("bra", "Braj"),
2414
2415 ("br", "Breton"),
2416
2417 ("bua", "Buriat"),
2418 ("bug", "Buginese"),
2419
2420 ("bg", "Bulgarian"),
2421
2422 ("my", "Burmese"),
2423 ("byn", "Blin; Bilin"),
2424 ("cad", "Caddo"),
2425
2426 ("car", "Galibi Carib"),
2427
2428 ("ca", "Catalan; Valencian"),
2429
2430 ("ceb", "Cebuano"),
2431
2432
2433 ("ch", "Chamorro"),
2434 ("chb", "Chibcha"),
2435
2436 ("ce", "Chechen"),
2437 ("chg", "Chagatai"),
2438
2439 ("zh", "Chinese"),
2440 ("chk", "Chuukese"),
2441 ("chm", "Mari"),
2442 ("chn", "Chinook jargon"),
2443 ("cho", "Choctaw"),
2444 ("chp", "Chipewyan; Dene Suline"),
2445 ("chr", "Cherokee"),
2446
2447 ("cu", "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"),
2448
2449 ("cv", "Chuvash"),
2450 ("chy", "Cheyenne"),
2451
2452 ("cop", "Coptic"),
2453
2454 ("kw", "Cornish"),
2455
2456 ("co", "Corsican"),
2457
2458
2459
2460
2461 ("cr", "Cree"),
2462 ("crh", "Crimean Tatar; Crimean Turkish"),
2463
2464 ("csb", "Kashubian"),
2465 ("cus", "Cushitic languages"),
2466
2467 ("cs", "Czech"),
2468 ("dak", "Dakota"),
2469
2470 ("da", "Danish"),
2471 ("dar", "Dargwa"),
2472
2473 ("del", "Delaware"),
2474 ("den", "Slave (Athapascan)"),
2475 ("dgr", "Dogrib"),
2476 ("din", "Dinka"),
2477
2478 ("dv", "Divehi; Dhivehi; Maldivian"),
2479 ("doi", "Dogri"),
2480
2481 ("dsb", "Lower Sorbian"),
2482 ("dua", "Duala"),
2483
2484
2485 ("nl", "Dutch; Flemish"),
2486 ("dyu", "Dyula"),
2487
2488 ("dz", "Dzongkha"),
2489 ("efi", "Efik"),
2490
2491 ("eka", "Ekajuk"),
2492 ("elx", "Elamite"),
2493
2494 ("en", "English"),
2495
2496
2497 ("eo", "Esperanto"),
2498
2499 ("et", "Estonian"),
2500
2501 ("ee", "Ewe"),
2502 ("ewo", "Ewondo"),
2503 ("fan", "Fang"),
2504
2505 ("fo", "Faroese"),
2506 ("fat", "Fanti"),
2507
2508 ("fj", "Fijian"),
2509 ("fil", "Filipino; Pilipino"),
2510
2511 ("fi", "Finnish"),
2512
2513 ("fon", "Fon"),
2514
2515 ("fr", "French"),
2516
2517
2518 ("frr", "Northern Frisian"),
2519 ("frs", "Eastern Frisian"),
2520
2521 ("fy", "Western Frisian"),
2522
2523 ("ff", "Fulah"),
2524 ("fur", "Friulian"),
2525 ("gaa", "Ga"),
2526 ("gay", "Gayo"),
2527 ("gba", "Gbaya"),
2528
2529
2530 ("ka", "Georgian"),
2531
2532 ("de", "German"),
2533 ("gez", "Geez"),
2534 ("gil", "Gilbertese"),
2535
2536 ("gd", "Gaelic; Scottish Gaelic"),
2537
2538 ("ga", "Irish"),
2539
2540 ("gl", "Galician"),
2541
2542 ("gv", "Manx"),
2543
2544
2545 ("gon", "Gondi"),
2546 ("gor", "Gorontalo"),
2547 ("got", "Gothic"),
2548 ("grb", "Grebo"),
2549
2550
2551 ("el", "Greek"),
2552
2553 ("gn", "Guarani"),
2554 ("gsw", "Swiss German; Alemannic; Alsatian"),
2555
2556 ("gu", "Gujarati"),
2557 ("gwi", "Gwich'in"),
2558 ("hai", "Haida"),
2559
2560 ("ht", "Haitian; Haitian Creole"),
2561
2562 ("ha", "Hausa"),
2563 ("haw", "Hawaiian"),
2564
2565 ("he", "Hebrew"),
2566
2567 ("hz", "Herero"),
2568 ("hil", "Hiligaynon"),
2569
2570
2571 ("hi", "Hindi"),
2572 ("hit", "Hittite"),
2573 ("hmn", "Hmong; Mong"),
2574
2575 ("ho", "Hiri Motu"),
2576
2577 ("hr", "Croatian"),
2578 ("hsb", "Upper Sorbian"),
2579
2580 ("hu", "Hungarian"),
2581 ("hup", "Hupa"),
2582 ("iba", "Iban"),
2583
2584 ("ig", "Igbo"),
2585
2586 ("is", "Icelandic"),
2587
2588 ("io", "Ido"),
2589
2590 ("ii", "Sichuan Yi; Nuosu"),
2591
2592
2593 ("iu", "Inuktitut"),
2594
2595 ("ie", "Interlingue; Occidental"),
2596 ("ilo", "Iloko"),
2597
2598 ("ia", "Interlingua (International Auxiliary Language Association)"),
2599
2600
2601 ("id", "Indonesian"),
2602
2603 ("inh", "Ingush"),
2604
2605 ("ik", "Inupiaq"),
2606
2607
2608
2609 ("it", "Italian"),
2610
2611 ("jv", "Javanese"),
2612 ("jbo", "Lojban"),
2613
2614 ("ja", "Japanese"),
2615
2616
2617 ("kaa", "Kara-Kalpak"),
2618 ("kab", "Kabyle"),
2619 ("kac", "Kachin; Jingpho"),
2620
2621 ("kl", "Kalaallisut; Greenlandic"),
2622 ("kam", "Kamba"),
2623
2624 ("kn", "Kannada"),
2625
2626
2627 ("ks", "Kashmiri"),
2628
2629 ("kr", "Kanuri"),
2630 ("kaw", "Kawi"),
2631
2632 ("kk", "Kazakh"),
2633 ("kbd", "Kabardian"),
2634 ("kha", "Khasi"),
2635
2636
2637 ("km", "Central Khmer"),
2638 ("kho", "Khotanese; Sakan"),
2639
2640 ("ki", "Kikuyu; Gikuyu"),
2641
2642 ("rw", "Kinyarwanda"),
2643
2644 ("ky", "Kirghiz; Kyrgyz"),
2645 ("kmb", "Kimbundu"),
2646 ("kok", "Konkani"),
2647
2648 ("kv", "Komi"),
2649
2650 ("kg", "Kongo"),
2651
2652 ("ko", "Korean"),
2653 ("kos", "Kosraean"),
2654 ("kpe", "Kpelle"),
2655 ("krc", "Karachay-Balkar"),
2656 ("krl", "Karelian"),
2657
2658 ("kru", "Kurukh"),
2659
2660 ("kj", "Kuanyama; Kwanyama"),
2661 ("kum", "Kumyk"),
2662
2663 ("ku", "Kurdish"),
2664 ("kut", "Kutenai"),
2665 ("lad", "Ladino"),
2666 ("lah", "Lahnda"),
2667 ("lam", "Lamba"),
2668
2669 ("lo", "Lao"),
2670
2671 ("la", "Latin"),
2672
2673 ("lv", "Latvian"),
2674 ("lez", "Lezghian"),
2675
2676 ("li", "Limburgan; Limburger; Limburgish"),
2677
2678 ("ln", "Lingala"),
2679
2680 ("lt", "Lithuanian"),
2681 ("lol", "Mongo"),
2682 ("loz", "Lozi"),
2683
2684 ("lb", "Luxembourgish; Letzeburgesch"),
2685 ("lua", "Luba-Lulua"),
2686
2687 ("lu", "Luba-Katanga"),
2688
2689 ("lg", "Ganda"),
2690 ("lui", "Luiseno"),
2691 ("lun", "Lunda"),
2692 ("luo", "Luo (Kenya and Tanzania)"),
2693 ("lus", "Lushai"),
2694
2695 ("mk", "Macedonian"),
2696 ("mad", "Madurese"),
2697 ("mag", "Magahi"),
2698
2699 ("mh", "Marshallese"),
2700 ("mai", "Maithili"),
2701 ("mak", "Makasar"),
2702
2703 ("ml", "Malayalam"),
2704 ("man", "Mandingo"),
2705
2706 ("mi", "Maori"),
2707
2708
2709 ("mr", "Marathi"),
2710 ("mas", "Masai"),
2711
2712 ("ms", "Malay"),
2713 ("mdf", "Moksha"),
2714 ("mdr", "Mandar"),
2715 ("men", "Mende"),
2716
2717 ("mic", "Mi'kmaq; Micmac"),
2718 ("min", "Minangkabau"),
2719
2720
2721
2722 ("mg", "Malagasy"),
2723 ("mlt", "Maltese"),
2724 ("mt", "Maltese"),
2725 ("mnc", "Manchu"),
2726 ("mni", "Manipuri"),
2727
2728 ("moh", "Mohawk"),
2729
2730 ("mn", "Mongolian"),
2731 ("mos", "Mossi"),
2732
2733
2734 ("mus", "Creek"),
2735 ("mwl", "Mirandese"),
2736 ("mwr", "Marwari"),
2737
2738 ("myv", "Erzya"),
2739
2740
2741 ("nap", "Neapolitan"),
2742
2743 ("na", "Nauru"),
2744
2745 ("nv", "Navajo; Navaho"),
2746
2747 ("nr", "Ndebele, South; South Ndebele"),
2748
2749 ("nd", "Ndebele, North; North Ndebele"),
2750
2751 ("ng", "Ndonga"),
2752 ("nds", "Low German; Low Saxon; German, Low; Saxon, Low"),
2753
2754 ("ne", "Nepali"),
2755 ("new", "Nepal Bhasa; Newari"),
2756 ("nia", "Nias"),
2757
2758 ("niu", "Niuean"),
2759
2760 ("nn", "Norwegian Nynorsk; Nynorsk, Norwegian"),
2761
2762 ("nb", "Bokmål, Norwegian; Norwegian Bokmål"),
2763 ("nog", "Nogai"),
2764
2765
2766 ("no", "Norwegian"),
2767 ("nqo", "N'Ko"),
2768 ("nso", "Pedi; Sepedi; Northern Sotho"),
2769
2770
2771
2772 ("ny", "Chichewa; Chewa; Nyanja"),
2773 ("nym", "Nyamwezi"),
2774 ("nyn", "Nyankole"),
2775 ("nyo", "Nyoro"),
2776 ("nzi", "Nzima"),
2777
2778 ("oc", "Occitan (post 1500); Provençal"),
2779
2780 ("oj", "Ojibwa"),
2781
2782 ("or", "Oriya"),
2783
2784 ("om", "Oromo"),
2785 ("osa", "Osage"),
2786
2787 ("os", "Ossetian; Ossetic"),
2788
2789
2790
2791 ("pag", "Pangasinan"),
2792 ("pal", "Pahlavi"),
2793 ("pam", "Pampanga; Kapampangan"),
2794
2795 ("pa", "Panjabi; Punjabi"),
2796 ("pap", "Papiamento"),
2797 ("pau", "Palauan"),
2798
2799
2800 ("fa", "Persian"),
2801
2802 ("phn", "Phoenician"),
2803
2804 ("pi", "Pali"),
2805
2806 ("pl", "Polish"),
2807 ("pon", "Pohnpeian"),
2808
2809 ("pt", "Portuguese"),
2810
2811
2812 ("prs", "Dari"),
2813
2814 ("ps", "Pushto; Pashto"),
2815
2816
2817 ("qu", "Quechua"),
2818 ("raj", "Rajasthani"),
2819 ("rap", "Rapanui"),
2820 ("rar", "Rarotongan; Cook Islands Maori"),
2821
2822
2823 ("rm", "Romansh"),
2824 ("rom", "Romany"),
2825
2826 ("ro", "Romanian; Moldavian; Moldovan"),
2827
2828 ("rn", "Rundi"),
2829 ("rup", "Aromanian; Arumanian; Macedo-Romanian"),
2830
2831 ("ru", "Russian"),
2832 ("sad", "Sandawe"),
2833
2834 ("sg", "Sango"),
2835 ("sah", "Yakut"),
2836
2837
2838 ("sam", "Samaritan Aramaic"),
2839
2840 ("sa", "Sanskrit"),
2841 ("sas", "Sasak"),
2842 ("sat", "Santali"),
2843 ("scn", "Sicilian"),
2844 ("sco", "Scots"),
2845 ("sel", "Selkup"),
2846
2847
2848 ("sgn", "Sign Languages"),
2849 ("shn", "Shan"),
2850 ("sid", "Sidamo"),
2851
2852 ("si", "Sinhala; Sinhalese"),
2853
2854
2855
2856
2857 ("sk", "Slovak"),
2858
2859 ("sl", "Slovenian"),
2860 ("sma", "Southern Sami"),
2861
2862 ("se", "Northern Sami"),
2863
2864 ("smj", "Lule Sami"),
2865 ("smn", "Inari Sami"),
2866
2867 ("sm", "Samoan"),
2868 ("sms", "Skolt Sami"),
2869
2870 ("sn", "Shona"),
2871
2872 ("sd", "Sindhi"),
2873 ("snk", "Soninke"),
2874 ("sog", "Sogdian"),
2875
2876 ("so", "Somali"),
2877
2878
2879 ("st", "Sotho, Southern"),
2880
2881 ("es", "Spanish; Castilian"),
2882
2883 ("sc", "Sardinian"),
2884 ("srn", "Sranan Tongo"),
2885
2886 ("sr", "Serbian"),
2887 ("srr", "Serer"),
2888
2889
2890 ("ss", "Swati"),
2891 ("suk", "Sukuma"),
2892
2893 ("su", "Sundanese"),
2894 ("sus", "Susu"),
2895 ("sux", "Sumerian"),
2896
2897 ("sw", "Swahili"),
2898
2899 ("sv", "Swedish"),
2900
2901 ("syr", "Syriac"),
2902
2903 ("ty", "Tahitian"),
2904
2905
2906 ("ta", "Tamil"),
2907
2908 ("tt", "Tatar"),
2909
2910 ("te", "Telugu"),
2911 ("tem", "Timne"),
2912 ("ter", "Tereno"),
2913 ("tet", "Tetum"),
2914
2915 ("tg", "Tajik"),
2916
2917 ("tl", "Tagalog"),
2918
2919 ("th", "Thai"),
2920
2921 ("bo", "Tibetan"),
2922 ("tig", "Tigre"),
2923
2924 ("ti", "Tigrinya"),
2925 ("tiv", "Tiv"),
2926 ("tkl", "Tokelau"),
2927
2928 ("tli", "Tlingit"),
2929 ("tmh", "Tamashek"),
2930 ("tog", "Tonga (Nyasa)"),
2931
2932 ("to", "Tonga (Tonga Islands)"),
2933 ("tpi", "Tok Pisin"),
2934 ("tsi", "Tsimshian"),
2935
2936 ("tn", "Tswana"),
2937
2938 ("ts", "Tsonga"),
2939
2940 ("tk", "Turkmen"),
2941 ("tum", "Tumbuka"),
2942
2943
2944 ("tr", "Turkish"),
2945
2946 ("tvl", "Tuvalu"),
2947
2948 ("tw", "Twi"),
2949 ("tyv", "Tuvinian"),
2950 ("udm", "Udmurt"),
2951 ("uga", "Ugaritic"),
2952
2953 ("ug", "Uighur; Uyghur"),
2954
2955 ("uk", "Ukrainian"),
2956 ("umb", "Umbundu"),
2957
2958
2959 ("ur", "Urdu"),
2960
2961 ("uz", "Uzbek"),
2962 ("vai", "Vai"),
2963
2964 ("ve", "Venda"),
2965
2966 ("vi", "Vietnamese"),
2967
2968 ("vo", "Volapük"),
2969 ("vot", "Votic"),
2970
2971 ("wal", "Walamo"),
2972 ("war", "Waray"),
2973 ("was", "Washo"),
2974
2975 ("cy", "Welsh"),
2976
2977
2978 ("wa", "Walloon"),
2979
2980 ("wo", "Wolof"),
2981 ("xal", "Kalmyk; Oirat"),
2982
2983 ("xh", "Xhosa"),
2984 ("yao", "Yao"),
2985 ("yap", "Yapese"),
2986
2987 ("yi", "Yiddish"),
2988
2989 ("yo", "Yoruba"),
2990
2991 ("zap", "Zapotec"),
2992
2993 ("zen", "Zenaga"),
2994 ("zgh", "Standard Moroccan Tamazight"),
2995
2996 ("za", "Zhuang; Chuang"),
2997
2998
2999 ("zu", "Zulu"),
3000 ("zun", "Zuni"),
3001
3002 ("zza", "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"),
3003 ]
3004
3005 settings = current.deployment_settings
3006
3007 l10n_languages = settings.get_L10n_languages()
3008 lang += l10n_languages.items()
3009
3010 extra_codes = settings.get_L10n_extra_codes()
3011 if extra_codes:
3012 lang += extra_codes
3013
3014 return list(set(lang))
3015
3016
3017