Package turbogears :: Module validators

Source Code for Module turbogears.validators

  1  """Convenient validators and converters for data coming in from the web. 
  2   
  3  This module also imports everything from formencode.validators, so all 
  4  common validation routines are available here.""" 
  5   
  6  import time 
  7  import re 
  8  from datetime import datetime 
  9  import warnings 
 10   
 11  import simplejson 
 12   
 13  from formencode import ForEach 
 14  from formencode import validators # to disambiguate the Number validator... 
 15  from formencode import national 
 16   
 17  from formencode.validators import * 
 18  from formencode.compound import * 
 19  from formencode.api import Invalid, NoDefault 
 20  from formencode.schema import Schema 
 21   
 22  from turbojson import jsonify 
 23   
 24  from turbogears import util 
 25  from turbogears.i18n import format 
 26   
 27   
28 -def _(s): return s # dummy
29 30 # FormEncode should call TG's gettext function with domain = "FormEncode" 31 Validator.gettextargs['domain'] = 'FormEncode' 32 33 34 # Validators handling country names and/or languages 35
36 -def StateProvince(*kw, **kwargs):
37 warnings.warn("Please use national.USStateProvince", 38 DeprecationWarning, 2) 39 return national.USStateProvince(*kw, **kwargs)
40
41 -def PhoneNumber(*kw, **kwargs):
42 warnings.warn("Please use national.USPhoneNumber", 43 DeprecationWarning, 2) 44 return national.USPhoneNumber(*kw, **kwargs)
45
46 -def PostalCode(*kw, **kwargs):
47 warnings.warn("Please use national.USPostalCode", 48 DeprecationWarning, 2) 49 return national.USPostalCode(*kw, **kwargs)
50
51 -def IPhoneNumberValidator(*kw, **kwargs):
52 warnings.warn("Please use national.InternationalPhoneNumber", 53 DeprecationWarning, 2) 54 return national.InternationalPhoneNumber(*kw, **kwargs)
55 56
57 -class TgFancyValidator(FancyValidator):
58 gettextargs = {'domain': 'TurboGears'}
59 60
61 -class Money(TgFancyValidator):
62 """Validate a monetary value with currency.""" 63 64 messages = { 65 'badFormat': _('Invalid number format'), 66 'empty': _('Empty values not allowed'), 67 } 68
69 - def _to_python(self, value, state):
70 """Parse a string and return a float or integer.""" 71 try: 72 return format.parse_decimal(value) 73 except ValueError: 74 raise Invalid(self.message('badFormat', state), value, state)
75
76 - def _from_python(self, value, state):
77 """Return a string using the correct grouping.""" 78 return format.format_currency(value)
79 80
81 -class Number(TgFancyValidator):
82 """Validate a decimal number.""" 83 84 decimals = None 85
86 - def _to_python(self, value, state):
87 """Parse a string and return a float or integer.""" 88 if isinstance(value, basestring): 89 try: 90 value = format.parse_decimal(value) 91 except ValueError: 92 pass 93 return validators.Number.to_python(value, state)
94
95 - def _from_python(self, value, state):
96 """Return a string using the correct grouping. 97 98 If the validator was instantiated with a decimals parameter, 99 this is used for the number of decimals. If None ist given, 100 the number of decimals is determined by the value itself. 101 102 """ 103 if self.decimals is None: 104 decimals = util.find_precision(value) 105 else: 106 decimals = self.decimals 107 if decimals > 0: 108 return format.format_decimal(value, decimals) 109 else: 110 return format.format_number(value)
111 112
113 -class DateTimeConverter(TgFancyValidator):
114 """Convert between Python datetime objects and strings.""" 115 116 format="%Y/%m/%d %H:%M" 117 118 messages = { 119 'badFormat': _('Invalid datetime format'), 120 'empty': _('Empty values not allowed'), 121 } 122
123 - def _to_python(self, value, state):
124 """Parse a string and return a datetime object.""" 125 if value and isinstance(value, datetime): 126 return value 127 else: 128 try: 129 format = self.format 130 if callable(format): 131 format = format() 132 tpl = time.strptime(value, format) 133 except ValueError: 134 raise Invalid(self.message('badFormat', state), value, state) 135 # shoudn't use time.mktime() because it can give OverflowError, 136 # depending on the date (e.g. pre 1970) and underlying C library 137 return datetime(year=tpl.tm_year, month=tpl.tm_mon, day=tpl.tm_mday, 138 hour=tpl.tm_hour, minute=tpl.tm_min, second=tpl.tm_sec)
139
140 - def _from_python(self, value, state):
141 """Return a string representation of a datetime object.""" 142 if not value: 143 return None 144 elif isinstance(value, datetime): 145 # Python stdlib can only handle dates with year greater than 1900 146 format = self.format 147 if callable(format): 148 format = format() 149 if format is None: 150 format = "%Y-%m-%d" 151 if value.year <= 1900: 152 return strftime_before1900(value, format) 153 else: 154 return value.strftime(format) 155 else: 156 return value
157 158 159 # Improved FieldStorageUploadConverter heeding not_empty=False 160 # (see TurboGears ticket #1705, FormEncode bug #1905250) 161
162 -class FieldStorageUploadConverter(TgFancyValidator):
163 164 messages = { 165 'notEmpty': _("Filename must not be empty"), 166 } 167
168 - def _to_python(self, value, state=None):
169 try: 170 filename = value.filename 171 except AttributeError: 172 filename = None 173 if not filename and self.not_empty: 174 raise Invalid(self.message('notEmpty', state), value, state) 175 return value
176 177 178 # For translated messages that are not wrapped in a Validator.messages 179 # dictionary, we need to reinstate the Turbogears gettext function under 180 # the name "_", with the "TurboGears" domain, so that the TurboGears.mo 181 # file is selected. 182 import turbogears.i18n 183 _ = lambda s: turbogears.i18n.gettext(s, domain='TurboGears') 184 185
186 -class MultipleSelection(ForEach):
187 """A default validator for SelectionFields with multiple selection.""" 188 189 if_missing = NoDefault 190 if_empty = [] 191
192 - def to_python(self, value, state=None):
193 try: 194 return super(MultipleSelection, self).to_python(value, state) 195 except Invalid: 196 raise Invalid(_("Please select at least a value"), value, state)
197 198
199 -class Schema(Schema):
200 """Modified Schema validator for TurboGears. 201 202 A schema validates a dictionary of values, applying different validators 203 (by key) to the different values. 204 205 This modified Schema allows fields that do not appear in the fields 206 parameter of your schema, but filters them out from the value dictionary. 207 You might want to set filter_extra_fields to True when you're building a 208 dynamic form with unpredictable keys and need these values. 209 210 """ 211 212 filter_extra_fields = True 213 allow_extra_fields = True 214 if_key_missing = None 215
216 - def from_python(self, value, state=None):
217 # The Schema shouldn't do any from_python conversion because 218 # adjust_value already takes care of that for all childs. 219 return value
220 221
222 -class JSONValidator(TgFancyValidator):
223 """A validator for JSON format.""" 224
225 - def _from_python(self, value, state):
226 return jsonify.encode(value)
227
228 - def _to_python(self, value, state):
229 return simplejson.loads(value)
230 231 232 # Auxiliary functions 233
234 -def _findall(text, substr):
235 # Also finds overlaps 236 sites = [] 237 i = 0 238 while 1: 239 j = text.find(substr, i) 240 if j == -1: 241 break 242 sites.append(j) 243 i = j+1 244 return sites
245 246 _illegal_s = re.compile(r"((^|[^%])(%%)*%s)") 247
248 -def strftime_before1900(dt, fmt):
249 """strftime implementation supporting proleptic Gregorian dates before 1900. 250 251 @see: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/306860 252 253 """ 254 if _illegal_s.search(fmt): 255 raise TypeError("This strftime implementation does not handle %s") 256 if dt.year > 1900: 257 return dt.strftime(fmt) 258 259 year = dt.year 260 # For every non-leap year century, advance by 261 # 6 years to get into the 28-year repeat cycle 262 delta = 2000 - year 263 off = 6*(delta // 100 + delta // 400) 264 year += off 265 266 # Move to around the year 2000 267 year = year + ((2000 - year)//28)*28 268 timetuple = dt.timetuple() 269 s1 = time.strftime(fmt, (year,) + timetuple[1:]) 270 sites1 = _findall(s1, str(year)) 271 272 s2 = time.strftime(fmt, (year+28,) + timetuple[1:]) 273 sites2 = _findall(s2, str(year+28)) 274 275 sites = [] 276 for site in sites1: 277 if site in sites2: 278 sites.append(site) 279 280 s = s1 281 syear = "%4d" % (dt.year,) 282 for site in sites: 283 s = s[:site] + syear + s[site+4:] 284 return s
285