2 # -*- coding: iso-8859-15 -*-
5 """This file contains "validators" that convert between string and python (database) representation
6 of properties used in the "Rodelbahnbox" and "Gasthausbox".
7 The "to_python" method has to get a unicode argument.
11 import xml.dom.minidom as minidom
12 from xml.parsers.expat import ExpatError
15 import formencode.national
18 class OrderedSchema(formencode.Schema):
19 def _convert_to_python(self, value, state):
20 pre_validators = self.pre_validators
21 chained_validators = self.chained_validators
22 for validator in pre_validators:
23 value = validator.to_python(value, state)
24 self.pre_validators = []
25 self.chained_validators = []
27 result = formencode.Schema._convert_to_python(self, value, state)
28 ordered_result = collections.OrderedDict()
29 for key in value.iterkeys():
30 ordered_result[key] = result[key]
31 for validator in chained_validators:
32 ordered_result = validator.to_python(ordered_result, state)
34 self.pre_validators = pre_validators
35 self.chained_validators = chained_validators
38 def _convert_from_python(self, value, state):
39 # store original pre- and chained validators
40 pre_validators = self.pre_validators
41 chained_validators = self.chained_validators[:]
42 # apply chained validators
43 chained = chained_validators[:]
45 for validator in chained:
46 value = validator.from_python(value, state)
47 # tempoarly remove pre- and chained validators
48 self.pre_validators = []
49 self.chained_validators = []
50 # apply original _convert_from_python method
52 result = formencode.Schema._convert_from_python(self, value, state)
53 ordered_result = collections.OrderedDict()
54 for key in value.iterkeys():
55 ordered_result[key] = result[key]
56 # apply pre_validators
57 pre = pre_validators[:]
60 ordered_result = validator.from_python(ordered_result, state)
62 # resore original pre- and chained_validators
63 self.pre_validators = pre_validators
64 self.chained_validators = chained_validators
68 class NoneValidator(formencode.FancyValidator):
69 """Takes a validator and makes it possible that empty strings are mapped to None."""
70 def __init__(self, validator, python_none=None):
71 self.validator = validator
72 self.python_none = python_none
74 def to_python(self, value, state=None):
75 self.assert_string(value, state)
76 if value == u'': return self.python_none
77 return self.validator.to_python(value, state)
79 def from_python(self, value, state=None):
80 if value == self.python_none: return u''
81 return self.validator.from_python(value, state)
84 class NeinValidator(formencode.FancyValidator):
85 """Take an arbitrary validator and adds the possibility that the
86 string can be u'Nein'.
87 Example together with an UnsignedNone validator:
88 >>> v = NeinValidator(UnsignedNone())
91 >>> v.to_python(u'34')
93 >>> v.to_python(u'Nein')
96 def __init__(self, validator, python_no=u'Nein'):
97 self.validator = validator
98 self.python_no = python_no
100 def to_python(self, value, state=None):
101 self.assert_string(value, state)
102 if value == u'Nein': return self.python_no
103 return self.validator.to_python(value, state)
105 def from_python(self, value, state=None):
106 if value == self.python_no: return u'Nein'
107 return self.validator.from_python(value, state)
110 class Unicode(formencode.FancyValidator):
111 """Converts an unicode string to an unicode string:
112 u'any string' <=> u'any string'"""
113 def to_python(self, value, state=None):
114 self.assert_string(value, state)
115 return unicode(value)
117 def from_python(self, value, state=None):
118 return unicode(value)
121 class UnicodeNone(NoneValidator):
122 """Converts an unicode string to an unicode string:
124 u'any string' <=> u'any string'"""
126 NoneValidator.__init__(self, Unicode())
129 class Unsigned(formencode.FancyValidator):
130 """Converts an unsigned number to a string and vice versa:
135 def __init__(self, max=None):
136 self.iv = formencode.validators.Int(min=0, max=max)
138 def to_python(self, value, state=None):
139 self.assert_string(value, state)
140 return self.iv.to_python(value, state)
142 def from_python(self, value, state=None):
143 return unicode(value)
146 class UnsignedNone(NoneValidator):
147 """Converts an unsigned number to a string and vice versa:
153 def __init__(self, max=None):
154 NoneValidator.__init__(self, Unsigned(max))
157 class UnsignedNeinNone(NoneValidator):
158 """ Translates a number of Nein to a number.
166 NoneValidator.__init__(self, UnsignedNone())
169 class Loop(formencode.FancyValidator):
170 """Takes a validator and calls from_python(to_python(value))."""
171 def __init__(self, validator):
172 self.validator = validator
174 def to_python(self, value, state=None):
175 self.assert_string(value, state)
176 return self.validator.from_python(self.validator.to_python(value, state))
178 def from_python(self, value, state=None):
179 # we don't call self.validator.to_python(self.validator.from_python(value))
180 # here because our to_python implementation basically leaves the input untouched
181 # and so should from_python do.
182 return self.validator.from_python(self.validator.to_python(value, state))
185 class DictValidator(formencode.FancyValidator):
186 """Translates strings to other values via a python directory.
187 >>> boolValidator = DictValidator({u'': None, u'Ja': True, u'Nein': False})
188 >>> boolValidator.to_python(u'')
190 >>> boolValidator.to_python(u'Ja')
193 def __init__(self, dict):
196 def to_python(self, value, state=None):
197 self.assert_string(value, state)
198 if not self.dict.has_key(value): raise formencode.Invalid("Key not found in dict.", value, state)
199 return self.dict[value]
201 def from_python(self, value, state=None):
202 for k, v in self.dict.iteritems():
205 raise formencode.Invalid('Invalid value', value, state)
208 class GermanBoolNone(DictValidator):
209 """Converts German bool values to the python bool type:
215 DictValidator.__init__(self, {u'': None, u'Ja': True, u'Nein': False})
218 class GermanTristateTuple(DictValidator):
219 """Does the following conversion:
221 u'Ja' <=> (True, False)
222 u'Teilweise' <=> (True, True)
223 u'Nein' <=> (False, True)"""
224 def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), none_python = (None, None)):
225 DictValidator.__init__(self, {u'': none_python, u'Ja': yes_python, u'Nein': no_python, u'Teilweise': partly_python})
228 class GermanTristateFloat(GermanTristateTuple):
229 """Converts the a property with the possible values 0.0, 0.5, 1.0 or None
236 GermanTristateTuple.__init__(self, yes_python=1.0, no_python=0.0, partly_python=0.5, none_python=None)
239 class ValueComment(formencode.FancyValidator):
240 """Converts value with a potentially optional comment to a python tuple. If a comment is present, the
241 closing bracket has to be the rightmost character.
243 u'value' <=> (u'value', None)
244 u'value (comment)' <=> (u'value', u'comment')
245 u'[[link (linkcomment)]]' <=> (u'[[link (linkcomment)]]', None)
246 u'[[link (linkcomment)]] (comment)' <=> (u'[[link (linkcomment)]]', comment)
248 def __init__(self, value_validator=UnicodeNone(), comment_validator=UnicodeNone(), comment_is_optional=True):
249 self.value_validator = value_validator
250 self.comment_validator = comment_validator
251 self.comment_is_optional = comment_is_optional
253 def to_python(self, value, state=None):
254 self.assert_string(value, state)
259 right = value.rfind(')')
260 if right+1 != len(value):
261 if not self.comment_is_optional: raise formencode.Invalid(u'Mandatory comment not present', value, state)
265 left = value.rfind('(')
266 if left < 0: raise formencode.Invalid(u'Invalid format', value, state)
267 v = value[:left].strip()
268 c = value[left+1:right].strip()
269 return self.value_validator.to_python(v, state), self.comment_validator.to_python(c, state)
271 def from_python(self, value, state=None):
272 assert len(value) == 2
273 v = self.value_validator.from_python(value[0], state)
274 c = self.comment_validator.from_python(value[1], state)
276 if len(v) > 0: return u'%s (%s)' % (v, c)
277 else: return u'(%s)' % c
281 class SemicolonList(formencode.FancyValidator):
282 """Applies a given validator to a semicolon separated list of values and returns a python list.
283 For an empty string an empty list is returned."""
284 def __init__(self, validator=Unicode()):
285 self.validator = validator
287 def to_python(self, value, state=None):
288 self.assert_string(value, state)
289 return [self.validator.to_python(s.strip(), state) for s in value.split(';')]
291 def from_python(self, value, state=None):
292 return "; ".join([self.validator.from_python(s, state) for s in value])
295 class ValueCommentList(SemicolonList):
296 """A value-comment list looks like one of the following lines:
298 value (optional comment)
300 value1; value2 (optional comment)
301 value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
302 value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
303 This function returns the value-comment list as list of tuples:
304 [(u'value1', u'comment1'), (u'value2', None)]
305 If no comment is present, None is specified.
306 For an empty string, [] is returned."""
307 def __init__(self, value_validator=Unicode(), comments_are_optional=True):
308 SemicolonList.__init__(self, ValueComment(value_validator, comment_is_optional=comments_are_optional))
311 class GenericDateTime(formencode.FancyValidator):
312 """Converts a generic date/time information to a datetime class with a user defined format.
313 '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'."""
315 def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords):
316 formencode.FancyValidator.__init__(self, **keywords)
317 self.date_time_format = date_time_format
319 def to_python(self, value, state=None):
320 self.assert_string(value, state)
321 try: return datetime.datetime.strptime(value, self.date_time_format)
322 except ValueError, e: raise formencode.Invalid(str(e), value, state)
324 def from_python(self, value, state=None):
325 return value.strftime(self.date_time_format)
328 class DateTimeNoSec(GenericDateTime):
329 def __init__(self, **keywords):
330 GenericDateTime.__init__(self, '%Y-%m-%d %H:%M', **keywords)
333 class DateNone(NoneValidator):
334 """Converts date information to date classes with the format '%Y-%m-%d' or None."""
336 NoneValidator.__init__(self, GenericDateTime('%Y-%m-%d'))
339 class Geo(formencode.FancyValidator):
340 """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
341 def to_python(self, value, state=None):
342 self.assert_string(value, state)
343 r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', value)
344 if r is None: raise formencode.Invalid(u"Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, state)
345 return (float(r.groups()[0]), float(r.groups()[1]))
347 def from_python(self, value, state=None):
348 latitude, longitude = value
349 return u'%.6f N %.6f E' % (latitude, longitude)
352 class GeoNone(NoneValidator):
353 """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
355 NoneValidator.__init__(self, Geo(), (None, None))
358 class MultiGeo(formencode.FancyValidator):
359 "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets."
361 # Valid for input_format
362 FORMAT_GUESS = 0 # guesses the input format; default for input_format
363 FORMAT_NONE = -1 # indicates missing formats
365 # Valid for input_format and output_format
366 FORMAT_GEOCACHING = 1 # e.g. "N 47° 13.692 E 011° 25.535"
367 FORMAT_WINTERRODELN = 2 # e.g. "47.222134 N 11.467211 E"
368 FORMAT_GMAPPLUGIN = 3 # e.g. "47.232922, 11.452239"
369 FORMAT_GPX = 4 # e.g. "<trkpt lat="47.181289" lon="11.408827"><ele>1090.57</ele></trkpt>"
371 input_format = FORMAT_GUESS
372 output_format = FORMAT_WINTERRODELN
373 last_input_format = FORMAT_NONE
375 def __init__(self, input_format = FORMAT_GUESS, output_format = FORMAT_WINTERRODELN, **keywords):
376 self.input_format = input_format
377 self.output_format = output_format
378 formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords)
380 def to_python(self, value, state=None):
381 self.assert_string(value, state)
382 input_format = self.input_format
383 if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]:
384 raise formencode.Invalid(u"input_format %d is not recognized" % input_format, value, state) # Shouldn't it be an other type of runtime error?
385 lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0]
389 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GEOCACHING:
390 r = re.match(u'N ?(\d+)° ?(\d+\.\d+) +E ?(\d+)° ?(\d+\.\d+)', line)
393 result.append((float(g[0]) + float(g[1])/60, float(g[2]) + float(g[3])/60, None))
394 last_input_format = self.FORMAT_WINTERRODELN
397 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_WINTERRODELN:
398 r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', line)
400 result.append((float(r.groups()[0]), float(r.groups()[1]), None))
401 last_input_format = self.FORMAT_WINTERRODELN
404 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GMAPPLUGIN:
405 r = re.match(u'(\d+\.\d+), ?(\d+\.\d+)', line)
407 result.append((float(r.groups()[0]), float(r.groups()[1]), None))
408 last_input_format = self.FORMAT_GMAPPLUGIN
411 if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GPX:
413 xml = minidom.parseString(line)
414 coord = xml.documentElement
415 lat = float(coord.getAttribute('lat'))
416 lon = float(coord.getAttribute('lon'))
417 try: ele = float(coord.childNodes[0].childNodes[0].nodeValue)
418 except (IndexError, ValueError): ele = None
419 result.append((lat, lon, ele))
420 last_input_format = self.FORMAT_GPX
422 except (ExpatError, IndexError, ValueError): pass
424 raise formencode.Invalid(u"Coordinates '%s' have no known format" % line, value, state)
428 def from_python(self, value, state=None):
429 output_format = self.output_format
431 for latitude, longitude, height in value:
432 if output_format == self.FORMAT_GEOCACHING:
434 result.append(u'N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60))
436 elif output_format == self.FORMAT_WINTERRODELN:
437 result.append(u'%.6f N %.6f E' % (latitude, longitude))
439 elif output_format == self.FORMAT_GMAPPLUGIN:
440 result.append(u'%.6f, %.6f' % (latitude, longitude))
442 elif output_format == self.FORMAT_GPX:
443 if not height is None: result.append(u'<trkpt lat="%.6f" lon="%.6f"><ele>%.2f</ele></trkpt>' % (latitude, longitude, height))
444 else: result.append(u'<trkpt lat="%.6f" lon="%.6f"/>' % (latitude, longitude))
447 raise formencode.Invalid(u"output_format %d is not recognized" % output_format, value, state) # Shouldn't it be an other type of runtime error?
449 return "\n".join(result)
453 class AustrianPhoneNumber(formencode.FancyValidator):
455 Validates and converts phone numbers to +##/###/####### or +##/###/#######-### (having an extension)
456 @param default_cc country code for prepending if none is provided, defaults to 43 (Austria)
458 >>> v = AustrianPhoneNumber()
459 >>> v.to_python(u'0512/12345678')
461 >>> v.to_python(u'+43/512/12345678')
463 >>> v.to_python(u'0512/1234567-89') # 89 is the extension
464 u'+43/512/1234567-89'
465 >>> v.to_python(u'+43/512/1234567-89')
466 u'+43/512/1234567-89'
467 >>> v.to_python(u'0512 / 12345678') # Exception
468 >>> v.to_python(u'0512-12345678') # Exception
470 # Inspired by formencode.national.InternationalPhoneNumber
472 default_cc = 43 # Default country code
473 messages = {'phoneFormat': "'%%(value)s' is an invalid format. Please enter a number in the form +43/###/####### or 0###/########."}
475 def to_python(self, value, state=None):
476 self.assert_string(value, state)
477 m = re.match(u'^(?:\+(\d+)/)?([\d/]+)(?:-(\d+))?$', value)
479 # u'+43/512/1234567-89' => (u'43', u'512/1234567', u'89')
480 # u'+43/512/1234/567-89' => (u'43', u'512/1234/567', u'89')
481 # u'+43/512/1234/567' => (u'43', u'512/1234/567', None)
482 # u'0512/1234567' => (None, u'0512/1234567', None)
483 if m is None: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, state)
484 (country, phone, extension) = m.groups()
487 if phone.find(u'//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, state)
491 if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, state)
493 country = unicode(self.default_cc)
495 if extension is None: return '+%s/%s' % (country, phone)
496 return '+%s/%s-%s' % (country, phone, extension)
500 class AustrianPhoneNumberNone(NoneValidator):
502 NoneValidator.__init__(self, AustrianPhoneNumber())
506 class AustrianPhoneNumberCommentLoop(NoneValidator):
508 NoneValidator.__init__(self, Loop(ValueComment(AustrianPhoneNumber())))
511 class GermanDifficulty(DictValidator):
512 """Converts the difficulty represented in a number from 1 to 3 (or None)
513 to a German representation:
519 DictValidator.__init__(self, {u'': None, u'leicht': 1, u'mittel': 2, u'schwer': 3})
522 class GermanAvalanches(DictValidator):
523 """Converts the avalanches property represented as number from 1 to 4 (or None)
524 to a German representation:
528 u'gelegentlich' <=> 3
531 DictValidator.__init__(self, {u'': None, u'kaum': 1, u'selten': 2, u'gelegentlich': 3, u'häufig': 4})
534 class GermanPublicTransport(DictValidator):
535 """Converts the public_transport property represented as number from 1 to 6 (or None)
536 to a German representation:
545 DictValidator.__init__(self, {u'': None, u'Sehr gut': 1, u'Gut': 2, u'Mittelmäßig': 3, u'Schlecht': 4, u'Nein': 5, u'Ja': 6})
548 class GermanTristateFloatComment(ValueComment):
549 """Converts the a property with the possible values 0.0, 0.5, 1.0 or None and an optional comment
550 in parenthesis to a German text:
552 u'Ja' <=> (1.0, None)
553 u'Teilweise' <=> (0.5, None)
554 u'Nein' <=> (0.0, None)
555 u'Ja (aber schmal)' <=> (1.0, u'aber schmal')
556 u'Teilweise (oben)' <=> (0.5, u'oben')
557 u'Nein (aber breit)' <=> (0.0, u'aber breit')
560 ValueComment.__init__(self, GermanTristateFloat())
563 class UnsignedCommentNone(NoneValidator):
564 """Converts the a property with unsigned values an optional comment
565 in parenthesis to a text:
567 u'2 (Mo, Di)' <=> (2, u'Mo, Di')
571 def __init__(self, max=None):
572 NoneValidator.__init__(self, ValueComment(Unsigned(max=max)), (None, None))
575 class GermanCachet(formencode.FancyValidator):
576 """Converts a "Gütesiegel":
579 u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' <=> u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'"""
580 def to_python(self, value, state=None):
581 self.assert_string(value, state)
582 if value == u'': return None
583 elif value == u'Nein': return value
584 elif value.startswith(u'Tiroler Naturrodelbahn-Gütesiegel '):
586 Unsigned().to_python(p[2], state) # check if year can be parsed
587 if not p[3] in ['leicht', 'mittel', 'schwer']: raise formencode.Invalid("Unbekannter Schwierigkeitsgrad", value, state)
589 else: raise formencode.Invalid(u"Unbekanntes Gütesiegel", value, state)
591 def from_python(self, value, state=None):
592 if value is None: return u''
594 return self.to_python(value, state)
597 class Url(formencode.FancyValidator):
598 """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed."""
599 # formencode 1.2.5 to formencode 1.3.0a1 sometimes raise ValueError instead of Invalid exceptions
600 # https://github.com/formencode/formencode/pull/61
601 urlv = formencode.validators.URL()
603 def to_python(self, value, state=None):
604 self.assert_string(value, state)
606 v = v.replace(u'ä', u'a')
607 v = v.replace(u'ö', u'o')
608 v = v.replace(u'ü', u'u')
609 v = v.replace(u'ß', u'ss')
610 v = self.urlv.to_python(v, state)
613 def from_python(self, value, state=None):
617 class UrlNeinNone(NoneValidator):
618 """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.
619 The special value u"Nein" is allowed."""
621 NoneValidator.__init__(self, NeinValidator(Url()))
624 class ValueCommentListNeinLoopNone(NoneValidator):
625 """Translates a semicolon separated list of values with optional comments in paranthesis or u'Nein' to itself.
626 An empty string is translated to None:
629 u'T-Mobile (gut); A1' <=> u'T-Mobile (gut); A1'"""
631 NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList())))
634 class PhoneNumber(formencode.FancyValidator):
635 """Telefonnumber in international format, e.g. u'+43-699-1234567'"""
636 def __init__(self, default_cc=43):
637 self.validator = formencode.national.InternationalPhoneNumber(default_cc=lambda: default_cc)
639 def to_python(self, value, state=None):
640 return unicode(self.validator.to_python(value, state))
642 def from_python(self, value, state=None):
643 return self.validator.from_python(value, state)
646 class PhoneCommentListNeinLoopNone(NoneValidator):
647 """List with semicolon-separated phone numbers in international format with optional comment or 'Nein' as string:
650 u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' <=> u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
652 def __init__(self, comments_are_optional):
653 NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(PhoneNumber(default_cc=43), comments_are_optional=comments_are_optional))))
656 class MaskedEmail(formencode.FancyValidator):
657 """A masked email address as defined here is an email address that has the `@` character replacted by the text `(at)`.
658 So instead of `abd.def@example.com` it would be `abc.def(at)example.com`.
659 This validator takes either a normal or a masked email address in it's to_python method and returns the normal email address as well
660 as a bool indicating whether the email address was masked.
662 u'abc.def@example.com' <=> (u'abc.def@example.com', False)
663 u'abc.def(at)example.com' <=> (u'abc.def@example.com', True)
666 def __init__(self, *args, **kw):
667 if not kw.has_key('strip'): kw['strip'] = True
668 if not kw.has_key('not_empty'): kw['not_empty'] = False
669 if not kw.has_key('if_empty'): kw['if_empty'] = (None, None)
671 formencode.FancyValidator.__init__(self, *args, **kw)
673 def _to_python(self, value, state=None):
674 email = value.replace(self.at, '@')
675 masked = value != email
676 val_email = formencode.validators.Email()
677 return val_email.to_python(email, state), masked
679 def _from_python(self, value, state=None):
680 email, masked = value
681 if email is None: return u''
682 val_email = formencode.validators.Email()
683 email = val_email.from_python(email, state)
684 if masked: email = email.replace('@', self.at)
688 class EmailCommentListNeinLoopNone(NoneValidator):
689 """Converts a semicolon-separated list of email addresses with optional comments to itself.
690 The special value of u'Nein' indicates that there are no email addresses.
691 The empty string translates to None:
694 u'first@example.com' <=> u'first@example.com'
695 u'first@example.com (Nur Winter); second@example.com' <=> u'first@example.com (Nur Winter); second@example.com'
697 If the parameter allow_masked_email is true, the following gives no error:
698 u'abc.def(at)example.com (comment)' <=> u'abc.def(at)example.com (comment)'
700 def __init__(self, allow_masked_email=False):
701 NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(MaskedEmail() if allow_masked_email else formencode.validators.Email()))))
704 class WikiPage(formencode.FancyValidator):
705 """Validates wiki page name like u'[[Birgitzer Alm]]'.
706 The page is not checked for existance.
707 An empty string is an error.
708 u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]'
710 def to_python(self, value, state=None):
711 self.assert_string(value, state)
712 if not value.startswith('[[') or not value.endswith(']]'):
713 raise formencode.Invalid('No valid wiki page name', value, state)
716 def from_python(self, value, state=None):
720 class WikiPageList(SemicolonList):
721 """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]'.
722 u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> [u'[[Birgitzer Alm]]', u'[[Kemater Alm]]']
723 u'[[Birgitzer Alm]]' <=> [u'[[Birgitzer Alm]]']
727 SemicolonList.__init__(self, WikiPage())
730 class WikiPageListLoopNone(NoneValidator):
731 """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]' as string.
732 u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> u'[[Birgitzer Alm]]; [[Kemater Alm]]'
733 u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]'
737 NoneValidator.__init__(self, Loop(WikiPageList()))
740 class TupleSecondValidator(formencode.FancyValidator):
741 """Does not really validate anything but puts the string through
742 a validator in the second part of a tuple.
743 Examples with an Unsigned() validator and the True argument:
745 u'2' <=> (True, 2)"""
746 def __init__(self, first=True, validator=UnicodeNone()):
748 self.validator = validator
750 def to_python(self, value, state=None):
751 self.assert_string(value, state)
752 return self.first, self.validator.to_python(value, state)
754 def from_python(self, value, state=None):
755 assert value[0] == self.first
756 return self.validator.from_python(value[1], state)
759 class BoolUnicodeTupleValidator(NoneValidator):
760 """Translates an unparsed string or u'Nein' to a tuple:
762 u'Nein' <=> (False, None)
763 u'any text' <=> (True, u'any text')
765 def __init__(self, validator=UnicodeNone()):
766 NoneValidator.__init__(self, NeinValidator(TupleSecondValidator(True, validator), (False, None)), (None, None))
769 class GermanLift(BoolUnicodeTupleValidator):
770 """Checks a lift_details property. It is a value comment property with the following
777 Alternatively, the value u'Nein' is allowed.
778 An empty string maps to (None, None).
782 u'Nein' <=> (False, None)
783 u'Sessellift <=> (True, u'Sessellift')
784 u'Gondel (nur bis zur Hälfte)' <=> (True, u'Gondel (nur bis zur Hälfte)')
785 u'Sessellift; Taxi' <=> (True, u'Sessellift; Taxi')
786 u'Sessellift (Wochenende); Taxi (6 Euro)' <=> (True, u'Sessellift (Wochenende); Taxi (6 Euro)')
789 BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList(DictValidator({u'Sessellift': u'Sessellift', u'Gondel': u'Gondel', u'Linienbus': u'Linienbus', u'Taxi': u'Taxi', u'Sonstige': u'Sonstige'}))))
792 class SledRental(BoolUnicodeTupleValidator):
793 """The value can be an empty string, u'Nein' or a comma-separated list of unicode strings with optional comments.
795 u'Nein' <=> (False, None)
796 u'Talstation (nur mit Ticket); Schneealm' <=> (True, u'Talstation (nur mit Ticket); Schneealm')"""
798 BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList()))
801 class RodelbahnboxDictValidator(OrderedSchema):
802 """Takes the fields of the Rodelbahnbox as dict of strings and returns them as dict of appropriet types."""
804 self.add_field(u'Position', GeoNone()) # '47.583333 N 15.75 E'
805 self.add_field(u'Position oben', GeoNone()) # '47.583333 N 15.75 E'
806 self.add_field(u'Höhe oben', UnsignedNone()) # '2000'
807 self.add_field(u'Position unten', GeoNone()) # '47.583333 N 15.75 E'
808 self.add_field(u'Höhe unten', UnsignedNone()) # '1200'
809 self.add_field(u'Länge', UnsignedNone()) # 3500
810 self.add_field(u'Schwierigkeit', GermanDifficulty()) # 'mittel'
811 self.add_field(u'Lawinen', GermanAvalanches()) # 'kaum'
812 self.add_field(u'Betreiber', UnicodeNone()) # 'Max Mustermann'
813 self.add_field(u'Öffentliche Anreise', GermanPublicTransport()) # 'Mittelmäßig'
814 self.add_field(u'Aufstieg möglich', GermanBoolNone()) # 'Ja'
815 self.add_field(u'Aufstieg getrennt', GermanTristateFloatComment()) # 'Ja'
816 self.add_field(u'Gehzeit', UnsignedNone()) # 90
817 self.add_field(u'Aufstiegshilfe', GermanLift()) # 'Gondel (unterer Teil)'
818 self.add_field(u'Beleuchtungsanlage', GermanTristateFloatComment())
819 self.add_field(u'Beleuchtungstage', UnsignedCommentNone(7)) # '3 (Montag, Mittwoch, Freitag)'
820 self.add_field(u'Rodelverleih', SledRental()) # 'Talstation Serlesbahnan'
821 self.add_field(u'Gütesiegel', GermanCachet()) # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
822 self.add_field(u'Webauskunft', UrlNeinNone()) # 'http://www.nösslachhütte.at/page9.php'
823 self.add_field(u'Telefonauskunft', PhoneCommentListNeinLoopNone(comments_are_optional=False)) # '+43-664-5487520 (Mitterer Alm)'
824 self.add_field(u'Bild', UnicodeNone())
825 self.add_field(u'In Übersichtskarte', GermanBoolNone())
826 self.add_field(u'Forumid', UnsignedNeinNone())