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