]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/wradmin/model/validators.py
4550da0dcae5376c16b51a968ea454554d141281
[philipp/winterrodeln/wradmin.git] / wradmin / wradmin / model / validators.py
1 # -*- coding: iso-8859-15 -*-
2 """This file contains "validators" that convert between string and python (database) representation
3 of properties used in the "Rodelbahnbox" and "Gasthausbox".
4 The "to_python" method has to get a unicode argument.
5 You can run tests with
6 >>> nosetests --with-pylons=test.ini
7 """
8 import formencode
9 import formencode.national
10 import datetime
11 import re
12 import xml.dom.minidom as minidom
13 from xml.parsers.expat import ExpatError
14
15
16 class NoneValidator(formencode.FancyValidator):
17     """Takes a validator and makes it possible that empty strings are mapped to None."""
18     def __init__(self, validator, python_none=None):
19         self.validator = validator
20         self.python_none = python_none
21     
22     def to_python(self, value):
23         self.assert_string(value, None)
24         if value == u'': return self.python_none
25         return self.validator.to_python(value)
26     
27     def from_python(self, value):
28         if value == self.python_none: return u''
29         return self.validator.from_python(value)
30
31
32 class NeinValidator(formencode.FancyValidator):
33     """Take an arbitrary validator and adds the possibility that the
34     string can be u'Nein'.
35     Example together with an UnsignedNone validator:
36     >>> v = NeinValidator(UnsignedNone())
37     >>> v.to_python(u'')
38     None
39     >>> v.to_python(u'34')
40     34
41     >>> v.to_python(u'Nein')
42     u'Nein'
43     """
44     def __init__(self, validator, python_no=u'Nein'):
45         self.validator = validator
46         self.python_no = python_no
47     
48     def to_python(self, value):
49         self.assert_string(value, None)
50         if value == u'Nein': return self.python_no
51         return self.validator.to_python(value)
52     
53     def from_python(self, value):
54         if value == self.python_no: return u'Nein'
55         return self.validator.from_python(value)
56
57
58 class Unicode(formencode.FancyValidator):
59     """Converts an unicode string to an unicode string:
60     u'any string' <=> u'any string'"""
61     def to_python(self, value):
62         self.assert_string(value, None)
63         return unicode(value)
64
65     def from_python(self, value):
66         return unicode(value)
67
68
69 class UnicodeNone(NoneValidator):
70     """Converts an unicode string to an unicode string:
71     u'' <=> None
72     u'any string' <=> u'any string'"""
73     def __init__(self):
74         NoneValidator.__init__(self, Unicode())
75
76
77 class Unsigned(formencode.FancyValidator):
78     """Converts an unsigned number to a string and vice versa:
79     u'0'  <=>  0
80     u'1'  <=>  1
81     u'45' <=> 45
82     """
83     def __init__(self, max=None):
84         self.iv = formencode.validators.Int(min=0, max=max)
85
86     def to_python(self, value):
87         self.assert_string(value, None)
88         return self.iv.to_python(value)
89     
90     def from_python(self, value):
91         return unicode(value)
92
93
94 class UnsignedNone(NoneValidator):
95     """Converts an unsigned number to a string and vice versa:
96     u''   <=> None
97     u'0'  <=>  0
98     u'1'  <=>  1
99     u'45' <=> 45
100     """
101     def __init__(self, max=None):
102         NoneValidator.__init__(self, Unsigned(max))
103
104
105 class UnsignedNeinNone(NoneValidator):
106     """ Translates a number of Nein to a number.
107     u''     <=> None
108     u'Nein' <=> 0
109     u'1'    <=> 1
110     u'2'    <=> 2
111     ...
112     """
113     def __init__(self):
114         NoneValidator.__init__(self, UnsignedNone())
115
116
117 class Loop(formencode.FancyValidator):
118     """Takes a validator and calls from_python(to_python(value))."""
119     def __init__(self, validator):
120         self.validator = validator
121
122     def to_python(self, value):
123         self.assert_string(value, None)
124         return self.validator.from_python(self.validator.to_python(value))
125     
126     def from_python(self, value):
127         # we don't call self.validator.to_python(self.validator.from_python(value))
128         # here because our to_python implementation basically leaves the input untouches
129         # and so should from_python do.
130         return self.validator.from_python(self.validator.to_python(value))
131
132
133 class DictValidator(formencode.FancyValidator):
134     """Translates strings to other values via a python directory.
135     >>> boolValidator = DictValidator({u'': None, u'Ja': True, u'Nein': False})
136     >>> boolValidator.to_python(u'')
137     None
138     >>> boolValidator.to_python(u'Ja')
139     True
140     """
141     def __init__(self, dict):
142         self.dict = dict
143     
144     def to_python(self, value):
145         self.assert_string(value, None)
146         if not self.dict.has_key(value): raise formencode.Invalid("Key not found in dict.", value, None)
147         return self.dict[value]
148     
149     def from_python(self, value):
150         for k, v in self.dict.iteritems():
151             if type(v) == type(value) and v == value: return k
152         raise formencode.Invalid('Invalid value', value, None)
153
154
155 class GermanBoolNone(DictValidator):
156     """Converts German bool values to the python bool type:
157     u''     <=> None
158     u'Ja'   <=> True
159     u'Nein' <=> False
160     """
161     def __init__(self):
162         DictValidator.__init__(self, {u'': None, u'Ja': True, u'Nein': False})
163
164
165 class GermanTristateTuple(DictValidator):
166     """Does the following conversion:
167     u''          <=> (None, None)
168     u'Ja'        <=> (True, False)
169     u'Teilweise' <=> (True,  True)
170     u'Nein'      <=> (False, True)"""
171     def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), none_python = (None, None)):
172         DictValidator.__init__(self, {u'': none_python, u'Ja': yes_python, u'Nein': no_python, u'Teilweise': partly_python})
173
174
175 class GermanTristateFloat(GermanTristateTuple):
176     """Converts the a property with the possible values 0.0, 0.5, 1.0 or None
177     to a German text:
178     u''          <=> None
179     u'Ja'        <=> 1.0
180     u'Teilweise' <=> 0.5
181     u'Nein'      <=> 0.0"""
182     def __init__(self):
183         GermanTristateTuple.__init__(self, yes_python=1.0, no_python=0.0, partly_python=0.5, none_python=None)
184
185
186 class ValueComment(formencode.FancyValidator):
187     """Converts value with a potentially optional comment to a python tuple:
188     u''                <=> (None, None)
189     u'value'           <=> (u'value', None)
190     u'value (comment)' <=> (u'value', u'comment')"""
191     def __init__(self, value_validator=UnicodeNone(), comment_validator=UnicodeNone(), comment_is_optional=True):
192         self.value_validator = value_validator
193         self.comment_validator = comment_validator
194         self.comment_is_optional = comment_is_optional
195     
196     def to_python(self, value):
197         self.assert_string(value, None)
198         if value == u'':
199             v = value
200             c = value
201         else:
202             left = value.find('(')
203             right = value.rfind(')')
204             if left < 0 and right < 0:
205                 if not self.comment_is_optional: raise formencode.Invalid(u'Mandatory comment not present', value, None)
206                 v = value
207                 c = u''
208             elif left >= 0 and right >= 0 and left < right:
209                 v = value[:left].strip()
210                 c = value[left+1:right].strip()
211             else: raise formencode.Invalid(u'Invalid format', value, None)
212         return self.value_validator.to_python(v), self.comment_validator.to_python(c)
213
214     def from_python(self, value):
215         assert len(value) == 2
216         v = self.value_validator.from_python(value[0])
217         c = self.comment_validator.from_python(value[1])
218         if len(c) > 0:
219             if len(v) > 0: return u'%s (%s)' % (v, c)
220             else: return u'(%s)' % c
221         return v
222
223
224 class SemicolonList(formencode.FancyValidator):
225     """Applies a given validator to a semicolon separated list of values and returns a python list.
226     For an empty string an empty list is returned."""
227     def __init__(self, validator=Unicode()):
228         self.validator = validator
229     
230     def to_python(self, value):
231         self.assert_string(value, None)
232         return [self.validator.to_python(s.strip()) for s in value.split(';')]
233     
234     def from_python(self, value):
235         return "; ".join([self.validator.from_python(s) for s in value])
236         
237     
238 class ValueCommentList(SemicolonList):
239     """A value-comment list looks like one of the following lines:
240         value
241         value (optional comment)
242         value1; value2
243         value1; value2 (optional comment)
244         value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
245         value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
246     This function returns the value-comment list as list of tuples:
247         [(u'value1', u'comment1'), (u'value2', None)]
248     If no comment is present, None is specified.
249     For an empty string, [] is returned."""    
250     def __init__(self, value_validator=Unicode(), comments_are_optional=True):
251         SemicolonList.__init__(self, ValueComment(value_validator, comment_is_optional=comments_are_optional))
252
253
254 class GenericDateTime(formencode.FancyValidator):
255     """Converts a generic date/time information to a datetime class with a user defined format.
256     '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'."""
257     
258     def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords):
259         formencode.FancyValidator.__init__(self, **keywords)
260         self.date_time_format = date_time_format
261     
262     def to_python(self, value, state=None):
263         self.assert_string(value, None)
264         try: return datetime.datetime.strptime(value, self.date_time_format)
265         except ValueError, e: raise formencode.Invalid(str(e), value, None)
266     
267     def from_python(self, value, state=None):
268         return value.strftime(self.date_time_format)
269
270
271 class DateTimeNoSec(GenericDateTime):
272     def __init__(self, **keywords):
273         GenericDateTime.__init__(self, '%Y-%m-%d %H:%M', **keywords)
274
275
276 class DateNone(NoneValidator):
277     """Converts date information to date classes with the format '%Y-%m-%d' or None."""
278     def __init__(self):
279         NoneValidator.__init__(self, GenericDateTime('%Y-%m-%d'))
280
281
282 class Geo(formencode.FancyValidator):
283     """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
284     def to_python(self, value):
285         self.assert_string(value, None)
286         r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', value)
287         if r is None: raise formencode.Invalid(u"Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, None)
288         return (float(r.groups()[0]), float(r.groups()[1]))
289     
290     def from_python(self, value):
291         latitude, longitude = value
292         return u'%.6f N %.6f E' % (latitude, longitude)
293
294
295 class GeoNone(NoneValidator):
296     """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
297     def __init__(self):
298         NoneValidator.__init__(self, Geo(), (None, None))
299
300
301 class MultiGeo(formencode.FancyValidator):
302     "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets."
303     
304     # Valid for input_format
305     FORMAT_GUESS = 0         # guesses the input format; default for input_format
306     FORMAT_NONE = -1          # indicates missing formats
307     
308     # Valid for input_format and output_format
309     FORMAT_GEOCACHING = 1    # e.g. "N 47° 13.692 E 011° 25.535"
310     FORMAT_WINTERRODELN = 2  # e.g. "47.222134 N 11.467211 E"
311     FORMAT_GMAPPLUGIN = 3    # e.g. "47.232922, 11.452239"
312     FORMAT_GPX = 4           # e.g. "<trkpt lat="47.181289" lon="11.408827"><ele>1090.57</ele></trkpt>"
313     
314     input_format = FORMAT_GUESS
315     output_format = FORMAT_WINTERRODELN
316     last_input_format = FORMAT_NONE
317
318     def __init__(self, input_format = FORMAT_GUESS, output_format = FORMAT_WINTERRODELN, **keywords):
319         self.input_format = input_format
320         self.output_format = output_format
321         formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords)
322     
323     def to_python(self, value):
324         self.assert_string(value, None)
325         input_format = self.input_format
326         if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]:
327             raise formencode.Invalid(u"input_format %d is not recognized" % input_format, value, None) # Shouldn't it be an other type of runtime error?
328         lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0]
329         
330         result = []
331         for line in lines:
332             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GEOCACHING:
333                 r = re.match(u'N ?(\d+)° ?(\d+\.\d+) +E ?(\d+)° ?(\d+\.\d+)', line)
334                 if not r is None:
335                     g = r.groups()
336                     result.append((float(g[0]) + float(g[1])/60, float(g[2]) + float(g[3])/60, None))
337                     last_input_format = self.FORMAT_WINTERRODELN
338                     continue
339                     
340             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_WINTERRODELN:
341                 r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', line)
342                 if not r is None:
343                     result.append((float(r.groups()[0]), float(r.groups()[1]), None))
344                     last_input_format = self.FORMAT_WINTERRODELN
345                     continue
346                 
347             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GMAPPLUGIN:
348                 r = re.match(u'(\d+\.\d+), ?(\d+\.\d+)', line)
349                 if not r is None:
350                     result.append((float(r.groups()[0]), float(r.groups()[1]), None))
351                     last_input_format = self.FORMAT_GMAPPLUGIN
352                     continue
353                 
354             if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GPX:
355                 try:
356                     xml = minidom.parseString(line)
357                     coord = xml.documentElement
358                     lat = float(coord.getAttribute('lat'))
359                     lon = float(coord.getAttribute('lon'))
360                     try: ele = float(coord.childNodes[0].childNodes[0].nodeValue)
361                     except (IndexError, ValueError): ele = None
362                     result.append((lat, lon, ele))
363                     last_input_format = self.FORMAT_GPX
364                     continue
365                 except (ExpatError, IndexError, ValueError): pass
366
367             raise formencode.Invalid(u"Coordinates '%s' have no known format" % line, value, None)
368             
369         return result
370     
371     def from_python(self, value):
372         output_format = self.output_format
373         result = []
374         for latitude, longitude, height in value:
375             if output_format == self.FORMAT_GEOCACHING:
376                 degree = latitude
377                 result.append(u'N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60))
378                 
379             elif output_format == self.FORMAT_WINTERRODELN:
380                 result.append(u'%.6f N %.6f E' % (latitude, longitude))
381
382             elif output_format == self.FORMAT_GMAPPLUGIN:
383                 result.append(u'%.6f, %.6f' % (latitude, longitude))
384                 
385             elif output_format == self.FORMAT_GPX:
386                 if not height is None: result.append(u'<trkpt lat="%.6f" lon="%.6f"><ele>%.2f</ele></trkpt>' % (latitude, longitude, height))
387                 else: result.append(u'<trkpt lat="%.6f" lon="%.6f"/>' % (latitude, longitude))
388             
389             else:
390                 raise formencode.Invalid(u"output_format %d is not recognized" % output_format, value, None) # Shouldn't it be an other type of runtime error?
391             
392         return "\n".join(result)
393
394
395 # deprecated
396 class AustrianPhoneNumber(formencode.FancyValidator):
397     """
398     Validates and converts phone numbers to +##/###/####### or +##/###/#######-### (having an extension)
399     @param  default_cc      country code for prepending if none is provided, defaults to 43 (Austria)
400     ::
401         >>> v = AustrianPhoneNumber()
402         >>> v.to_python(u'0512/12345678')
403         u'+43/512/12345678'
404         >>> v.to_python(u'+43/512/12345678')
405         u'+43/512/12345678'
406         >>> v.to_python(u'0512/1234567-89') # 89 is the extension
407         u'+43/512/1234567-89'
408         >>> v.to_python(u'+43/512/1234567-89')
409         u'+43/512/1234567-89'
410         >>> v.to_python(u'0512 / 12345678') # Exception
411         >>> v.to_python(u'0512-12345678') # Exception
412     """
413     # Inspired by formencode.national.InternationalPhoneNumber
414
415     default_cc = 43 # Default country code
416     messages = {'phoneFormat': "'%%(value)s' is an invalid format. Please enter a number in the form +43/###/####### or 0###/########."}
417
418     def to_python(self, value):
419         self.assert_string(value, None)
420         m = re.match(u'^(?:\+(\d+)/)?([\d/]+)(?:-(\d+))?$', value)
421         # This will separate 
422         #     u'+43/512/1234567-89'  => (u'43', u'512/1234567', u'89')
423         #     u'+43/512/1234/567-89' => (u'43', u'512/1234/567', u'89')
424         #     u'+43/512/1234/567'    => (u'43', u'512/1234/567', None)
425         #     u'0512/1234567'        => (None, u'0512/1234567', None)
426         if m is None: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
427         (country, phone, extension) = m.groups()
428         
429         # Phone
430         if phone.find(u'//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
431         
432         # Country
433         if country is None:
434             if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
435             phone = phone[1:]
436             country = unicode(self.default_cc)
437         
438         if extension is None: return '+%s/%s' % (country, phone)
439         return '+%s/%s-%s' % (country, phone, extension)
440
441
442 # Deprecated
443 class AustrianPhoneNumberNone(NoneValidator):
444     def __init__(self):
445         NoneValidator.__init__(self, AustrianPhoneNumber())
446
447
448 # Deprecated
449 class AustrianPhoneNumberCommentLoop(NoneValidator):
450     def __init__(self):
451         NoneValidator.__init__(self, Loop(ValueComment(AustrianPhoneNumber())))
452
453
454 class GermanDifficulty(DictValidator):
455     """Converts the difficulty represented in a number from 1 to 3 (or None)
456     to a German representation:
457     u''       <=> None
458     u'leicht' <=> 1
459     u'mittel' <=> 2
460     u'schwer' <=> 3"""
461     def __init__(self):
462         DictValidator.__init__(self, {u'': None, u'leicht': 1, u'mittel': 2, u'schwer': 3})
463
464
465 class GermanAvalanches(DictValidator):
466     """Converts the avalanches property represented as number from 1 to 4 (or None)
467     to a German representation:
468     u''             <=> None
469     u'kaum'         <=> 1
470     u'selten'       <=> 2
471     u'gelegentlich' <=> 3
472     u'häufig'       <=> 4"""
473     def __init__(self):
474         DictValidator.__init__(self, {u'': None, u'kaum': 1, u'selten': 2, u'gelegentlich': 3, u'häufig': 4})
475
476
477 class GermanPublicTransport(DictValidator):
478     """Converts the public_transport property represented as number from 1 to 6 (or None)
479     to a German representation:
480     u''            <=> None
481     u'Sehr gut'    <=> 1
482     u'Gut'         <=> 2
483     u'Mittelmäßig' <=> 3
484     u'Schlecht'    <=> 4
485     u'Nein'        <=> 5
486     u'Ja'          <=> 6"""
487     def __init__(self):
488         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})
489
490
491 class GermanTristateFloatComment(ValueComment):
492     """Converts the a property with the possible values 0.0, 0.5, 1.0 or None and an optional comment
493     in parenthesis to a German text:
494     u''                  <=> (None, None)
495     u'Ja'                <=> (1.0,  None)
496     u'Teilweise'         <=> (0.5,  None)
497     u'Nein'              <=> (0.0,  None)
498     u'Ja (aber schmal)'  <=> (1.0,  u'aber schmal')
499     u'Teilweise (oben)'  <=> (0.5,  u'oben')
500     u'Nein (aber breit)' <=> (0.0,  u'aber breit')
501     """
502     def __init__(self):
503         ValueComment.__init__(self, GermanTristateFloat())
504
505
506 class UnsignedCommentNone(NoneValidator):
507     """Converts the a property with unsigned values an optional comment
508     in parenthesis to a text:
509     u''           <=> (None, None)
510     u'2 (Mo, Di)' <=> (2,  u'Mo, Di')
511     u'7'          <=> (7,  None)
512     u'0'          <=> (0,  None)
513     """
514     def __init__(self, max=None):
515         NoneValidator.__init__(self, ValueComment(Unsigned(max=max)), (None, None))
516
517
518 class GermanCachet(formencode.FancyValidator):
519     """Converts a "Gütesiegel":
520     u'' <=> None
521     u'Nein' <=> 'Nein'
522     u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' <=> u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'"""
523     def to_python(self, value):
524         self.assert_string(value, None)
525         if value == u'': return None
526         elif value == u'Nein': return value
527         elif value.startswith(u'Tiroler Naturrodelbahn-Gütesiegel '):
528             p = value.split(" ")
529             Unsigned().to_python(p[2]) # check if year can be parsed
530             if not p[3] in ['leicht', 'mittel', 'schwer']: raise formencode.Invalid("Unbekannter Schwierigkeitsgrad", value, None)
531             return value
532         else: raise formencode.Invalid("Unbekanntes Gütesiegel", value, None)
533     
534     def from_python(self, value):
535         if value == None: return u''
536         assert value != u''
537         return self.to_python(self, value)
538
539
540 class Url(formencode.FancyValidator):
541     """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed."""
542     urlv = formencode.validators.URL()    
543     def to_python(self, value):
544         self.assert_string(value, None)
545         v = value
546         v = v.replace(u'ä', u'a')
547         v = v.replace(u'ö', u'o')
548         v = v.replace(u'ü', u'u')
549         v = v.replace(u'ß', u'ss')
550         v = self.urlv.to_python(v)
551         return value
552     
553     def from_python(self, value):
554         return value
555
556
557 class UrlNeinNone(NoneValidator):
558     """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.
559     The special value u"Nein" is allowed."""
560     def __init__(self):
561         NoneValidator.__init__(self, NeinValidator(Url()))
562
563
564 class ValueCommentListNeinLoopNone(NoneValidator):
565     """Translates a semicolon separated list of values with optional comments in paranthesis or u'Nein' to itself.
566     An empty string is translated to None:
567     u''                   <=> None
568     u'Nein'               <=> u'Nein'
569     u'T-Mobile (gut); A1' <=> u'T-Mobile (gut); A1'"""
570     def __init__(self):
571         NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList())))
572
573
574 class PhoneNumber(formencode.FancyValidator):
575     """Telefonnumber in international format, e.g. u'+43-699-1234567'"""
576     def __init__(self, default_cc=43):
577         self.validator = formencode.national.InternationalPhoneNumber(default_cc=lambda: default_cc)
578
579     def to_python(self, value):
580         return unicode(self.validator.to_python(value))
581
582     def from_python(self, value):
583         return self.validator.from_python(value)
584
585
586 class PhoneCommentListNeinLoopNone(NoneValidator):
587     """List with semicolon-separated phone numbers in international format with optional comment or 'Nein' as string:
588     u''                                                       <=> None
589     u'Nein'                                                   <=> u'Nein'
590     u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' <=> u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
591     """
592     def __init__(self, comments_are_optional):
593         NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(PhoneNumber(default_cc=43), comments_are_optional=comments_are_optional))))
594
595
596 class EmailCommentListNeinLoopNone(NoneValidator):
597     """Converts a semicolon-separated list of email addresses with optional comments to itself.
598     The special value of u'Nein' indicates that there are no email addresses.
599     The empty string translates to None:
600     u''                                                   <=> None
601     u'Nein'                                               <=> u'Nein'
602     u'first@example.com'                                  <=> u'first@example.com'
603     u'first@example.com (Nur Winter); second@example.com' <=> u'first@example.com (Nur Winter); second@example.com'
604     """
605     def __init__(self):
606         NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(formencode.validators.Email()))))
607
608
609 class WikiPage(formencode.FancyValidator):
610     """Validates wiki page name like u'[[Birgitzer Alm]]'.
611     The page is not checked for existance.
612     An empty string is an error.
613     u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]'
614     """
615     def to_python(self, value):
616         self.assert_string(value, None)
617         if not value.startswith('[[') or not value.endswith(']]'): 
618             raise formencode.Invalid('No valid wiki page name', value, None)
619         return value
620     
621     def from_python(self, value):
622         return value
623
624
625 class WikiPageList(SemicolonList):
626     """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]'.
627     u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> [u'[[Birgitzer Alm]]', u'[[Kemater Alm]]']
628     u'[[Birgitzer Alm]]'                  <=> [u'[[Birgitzer Alm]]']
629     u''                                   <=> []
630     """
631     def __init__(self):
632         SemicolonList.__init__(self, WikiPage())
633
634
635 class WikiPageListLoopNone(NoneValidator):
636     """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]' as string.
637     u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> u'[[Birgitzer Alm]]; [[Kemater Alm]]'
638     u'[[Birgitzer Alm]]'                  <=> u'[[Birgitzer Alm]]'
639     u''                                   <=> None
640     """
641     def __init__(self):
642         NoneValidator.__init__(self, Loop(WikiPageList()))
643
644
645 class TupleSecondValidator(formencode.FancyValidator):
646     """Does not really validate anything but puts the string through
647     a validator in the second part of a tuple.
648     Examples with an Unsigned() validator and the True argument:
649     u'6' <=> (True, 6)
650     u'2' <=> (True, 2)"""
651     def __init__(self, first=True, validator=UnicodeNone()):
652         self.first = first
653         self.validator = validator
654     
655     def to_python(self, value):
656         self.assert_string(value, None)
657         return self.first, self.validator.to_python(value)
658     
659     def from_python(self, value):
660         assert value[0] == self.first
661         return self.validator.from_python(value[1])
662
663
664 class BoolUnicodeTupleValidator(NoneValidator):
665     """Translates an unparsed string or u'Nein' to a tuple:
666     u''         <=> (None, None)
667     u'Nein'     <=> (False, None)
668     u'any text' <=> (True, u'any text')
669     """
670     def __init__(self, validator=UnicodeNone()):
671         NoneValidator.__init__(self, NeinValidator(TupleSecondValidator(True, validator), (False, None)), (None, None))
672
673
674 class GermanLift(BoolUnicodeTupleValidator):
675     """Checks a lift_details property. It is a value comment property with the following
676     values allowed:
677     u'Sessellift'
678     u'Gondel'
679     u'Linienbus'
680     u'Taxi'
681     u'Sonstige'
682     Alternatively, the value u'Nein' is allowed.
683     An empty string maps to (None, None).
684     
685     Examples:
686     u''                                       <=> (None, None)
687     u'Nein'                                   <=> (False, None)
688     u'Sessellift                              <=> (True, u'Sessellift')
689     u'Gondel (nur bis zur Hälfte)'            <=> (True, u'Gondel (nur bis zur Hälfte)')
690     u'Sessellift; Taxi'                       <=> (True, u'Sessellift; Taxi')
691     u'Sessellift (Wochenende); Taxi (6 Euro)' <=> (True, u'Sessellift (Wochenende); Taxi (6 Euro)')
692     """
693     def __init__(self):
694         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'}))))
695         
696
697 class SledRental(BoolUnicodeTupleValidator):
698     """The value can be an empty string, u'Nein' or a comma-separated list of unicode strings with optional comments.
699     u''                                       <=> (None, None)
700     u'Nein'                                   <=> (False, None)
701     u'Talstation (nur mit Ticket); Schneealm' <=> (True, u'Talstation (nur mit Ticket); Schneealm')"""
702     def __init__(self):
703         BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList()))