]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/commitdiff
Further work on 1.3 format of the tables and the Rodelbahnbox/Gasthausbox.
authorphilipp <philipp@7aebc617-e5e2-0310-91dc-80fb5f6d2477>
Sat, 26 Jun 2010 10:50:10 +0000 (10:50 +0000)
committerphilipp <philipp@7aebc617-e5e2-0310-91dc-80fb5f6d2477>
Sat, 26 Jun 2010 10:50:10 +0000 (10:50 +0000)
git-svn-id: http://www.winterrodeln.org/svn/servermediawiki/trunk/wradmin@568 7aebc617-e5e2-0310-91dc-80fb5f6d2477

wradmin/wradmin/controllers/bericht.py
wradmin/wradmin/controllers/rodelbahn.py
wradmin/wradmin/lib/mediawiki.py
wradmin/wradmin/lib/wrgpx.py
wradmin/wradmin/model/validators.py
wradmin/wradmin/tests/test_lib.py
wradmin/wradmin/tests/test_models.py

index 892509865933b525cf63914dfcb6d52948ed9798..a004d2b064b106f4f4662fe78a60de2939db38ec 100644 (file)
@@ -33,7 +33,7 @@ class ChangeDateInvalidForm(formencode.Schema):
     allow_extra_fields = True
     filter_extra_fields = True
     date_invalid = formencode.validators.OneOf(['no_change', 'tomorrow', 'one_week', 'two_weeks', 'one_week_more', 'end_of_saison', 'now', 'userdefined'], not_empty=True)
-    date_userdefined = wradmin.model.validators.DateTimeNoSecConverter
+    date_userdefined = wradmin.model.validators.DateTimeNoSec
     chained_validators = [RequireDateIfUserDefined()]
 
 
index 237e6e4f592861ec5382bb084fc8af954997b873..6b1b6dc37d2bfc9abb3eb4b0fdb443f87f207749 100644 (file)
@@ -11,7 +11,7 @@ import wradmin.model as model
 import sqlalchemy as sa
 import formencode
 import re
-from wradmin.lib.mediawiki import to_bool, to_unsigned, to_date, to_geo, to_title, to_tristate, to_email, to_url, to_phone, to_phone_info, conv, unicode_e
+from wradmin.lib.mediawiki import wikipage_to_wrsleddingcache1_2
 
 log = logging.getLogger(__name__)
 
@@ -40,57 +40,57 @@ class RodelbahnController(BaseController):
         return render('rodelbahn_view.html')
     
     
-    def _wikipage_to_wrsleddingcache(self, sledding_wiki):
-        "Converts a sledding route wiki page to a sledding route wrsleddingcache database record."
-        # TODO: Use mediawiki.wikipage_to_wrsleddingcache
-        
-        
-        sl = model.WrSleddingCache()
-        sl.page_id = sledding_wiki.page_id
-        sl.page_title = to_title(sledding_wiki.page_title)
-        
-        # Match Rodelbahnbox
-        wikitext = sledding_wiki.old_text
-        regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
-        match = regexp.search(wikitext)
-        if not match:
-            raise Exception(u"No 'Rodelbahnbox' found")
-        box = match.group(1)
-        
-        # Process Rodelbahnbox
-        for property in box.split('|'):
-            property = property.strip()
-            if property == u'Rodelbahnbox': continue
-            key_value = property.split('=')
-            if len(key_value) != 2:
-                raise Exception(u"Property '%s' has unexpected format" % key_value)
-            key = key_value[0].strip()
-            value = key_value[1].strip()
-            if key == u'Rodelbahnnummer': pass
-            elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
-            elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
-            elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
-            elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
-            elif key == u'Aufstieg getrennt': sl.walkup_separate = conv(to_bool, value, u'Aufstieg getrennt')
-            elif key == u'Lift': sl.lift = conv(to_bool, value, u'Lift')
-            elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
-            elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
-            elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
-            elif key == u'Bild': sl.image = value
-            elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
-            elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
-            elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
-            elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
-            elif key == u'Lawinengefahr':
-                if not value in [u'kaum', u'selten', u'gelegentlich', u'häufig']: raise formencode.Invalid(u"No valid value for 'Lawinengefahr': '%s'" % value, value, None)
-            else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
-        sl.under_construction = None
-        
-        # Match Forumlink (e.g. {{Forumlink|68}})
-        match = re.search(u"\{\{Forumlink\|(\d+)\}\}", wikitext)
-        if match: sl.forum_id = match.group(1)
-        
-        return sl
+    def _wikipage_to_wrsleddingcache(self, sledding_wiki):
+        "Converts a sledding route wiki page to a sledding route wrsleddingcache database record."
+        # TODO: Use mediawiki.wikipage_to_wrsleddingcache
+    #     
+    #     
+        sl = model.WrSleddingCache()
+        sl.page_id = sledding_wiki.page_id
+        sl.page_title = to_title(sledding_wiki.page_title)
+    #     
+        # Match Rodelbahnbox
+        wikitext = sledding_wiki.old_text
+        regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
+        match = regexp.search(wikitext)
+        if not match:
+            raise Exception(u"No 'Rodelbahnbox' found")
+        box = match.group(1)
+    #     
+        # Process Rodelbahnbox
+        for property in box.split('|'):
+            property = property.strip()
+            if property == u'Rodelbahnbox': continue
+            key_value = property.split('=')
+            if len(key_value) != 2:
+                raise Exception(u"Property '%s' has unexpected format" % key_value)
+            key = key_value[0].strip()
+            value = key_value[1].strip()
+            if key == u'Rodelbahnnummer': pass
+            elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
+            elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
+            elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
+            elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
+            elif key == u'Aufstieg getrennt': sl.walkup_separate = conv(to_bool, value, u'Aufstieg getrennt')
+            elif key == u'Lift': sl.lift = conv(to_bool, value, u'Lift')
+            elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
+            elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
+            elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
+            elif key == u'Bild': sl.image = value
+            elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
+            elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
+            elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
+            elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
+            elif key == u'Lawinengefahr':
+                if not value in [u'kaum', u'selten', u'gelegentlich', u'häufig']: raise formencode.Invalid(u"No valid value for 'Lawinengefahr': '%s'" % value, value, None)
+            else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
+        sl.under_construction = None
+    #     
+        # Match Forumlink (e.g. {{Forumlink|68}})
+        match = re.search(u"\{\{Forumlink\|(\d+)\}\}", wikitext)
+        if match: sl.forum_id = match.group(1)
+    #     
+        return sl
     
     
     def update(self):
@@ -112,7 +112,7 @@ class RodelbahnController(BaseController):
         error_msg = ''
         for sl in sledding_pages:
             try: 
-                sl = self._wikipage_to_wrsleddingcache(sl)
+                sl = wikipage_to_wrsleddingcache1_2(sl.page_id, sl.page_title, sl.old_text)
                 sl.under_construction = c.execute(select([categorylinks], (categorylinks.c.cl_from==sl.page_id) & (categorylinks.c.cl_to == u'In_Arbeit')).alias('x').count()).fetchone()[0] > 0 # It would be better to do this in the query above
                 model.meta.Session.add(sl)
             except formencode.Invalid, e: error_msg = u"Fehler bei Rodelbahn '%s': " % sl.page_title + unicode_e(e)
index 8ab55f6a2e67abdb9d5b04ccb6ff084f78f304e6..32e43037bd2455ba7289cf07fc58b0ecde2821f2 100644 (file)
@@ -17,236 +17,17 @@ import wradmin.model.validators
 # Converter functions
 # -------------------
 
-def to_bool(value):
-    return model.validators.GermanBool().to_python(value)
-
-
-def to_unsigned(value):
-    "Requires a positive number"
-    return formencode.validators.Int(min=0).to_python(value)
-
-
-def from_unsigned(value):
-    """None -> ''
-    integer -> unicode"""
-    if value is None: return ''
-    return unicode(value)
-
-
-def to_string(value):
-    "Converts an empty string to None."
-    if len(value) == 0: return None
-    return value
-
-
-def to_date(value):
-    "Parses a date in the form 'yyy-mm-dd'"
-    return model.validators.DateConverter().to_python(value)
-
-
-def to_geo(value):
-    "Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."
-    return model.validators.Geo().to_python(value)
-
-
 def to_title(value):
     """Line 2237 of includes/Title.php says: $this->mTextform = str_replace( '_', ' ', $dbkey );
     No not check for None because a missing title is an error"""
     return value.replace(u'_', u' ')
 
 
-def to_tristate(value):
-    """Does the following conversion:
-    None         -> (None, None)
-    u'Ja'        -> (True, False)
-    u'Teilweise' -> (True,  True)
-    u'Nein'      -> (False, True)"""
-    return model.validators.GermanTristate().to_python(value)
-
-
-def to_email(value):
-    return formencode.validators.Email().to_python(value)
-
-
-def to_url(value):
-    # return formencode.validators.URL().to_python(value) # does not accept URLs with accents
-    if value == '': return None
-    if not value.startswith('http'): raise formencode.Invalid('URL does not start with http.', value, None)
-    return value
-
-
-# deprecated
-def to_phone(value):
-    return model.validators.AustrianPhoneNumber(messages={'phoneFormat': u"Telefonnummer %%(value)s muss das Format 0123/456789 oder +43/123/456789 haben"}).to_python(value)
-
-
 # deprecated
 def to_phone_info(value):
     return model.validators.PhoneInfo(messages={'phoneInfo': u"Bitte verwenden Sie ein Format wie '0512/123456 (Schnee Alm)'."}).to_python(value)
 
 
-def to_walkup_separate(value):
-    """'Ja', 'Nein', 'Teilweise' -> 1, 0, 0.5. A comment is allowed."""
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None: return None, None
-    if len(v) > 1: raise formencode.Invalid('Only one field is allowed.', value, None)
-    return formencode.validators.DictConverter({u'Ja': 1.0, u'Teilweise': 0.5, u'Nein': 0.0}).to_python(v[0][0]), v[0][1]
-
-
-def to_lift(value):
-    """Example 'Gondel (nur tagsüber); Taxi'"""
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None: return None, None
-    has_lift = None
-    for vv, comment in v:
-        if vv == u'Nein':
-            if len(vv) != 1: raise formencode.Invalid('"Nein" kann mit keiner anderen Aufstiegshilfe kombiniert werden.', value, None)
-            lift = False
-        elif not vv in [u'Sessellift', u'Gondel', u'Linienbus', u'Taxi', u'Sonstige']:
-            raise formencode.Invalid(u'"%s" ist keine gültige Aufstiegshilfe.' % vv, vv, None)
-        else: has_lift = True
-    return has_lift, value
-
-
-def to_night_light(value):
-    """Examle: 'Teilweise (im unteren Teil)'"""
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None: return None, None
-    if len(v) > 1: raise formencode.Invalid('Only one field is allowed.', value, None)
-    return formencode.validators.DictConverter({u'Ja': 1.0, u'Teilweise': 0.5, u'Nein': 0.0}).to_python(v[0][0]), v[0][1]
-
-
-def to_night_light_days(value):
-    """Example: '3 (Montag, Mittwoch, Freitag)'"""
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None: return None, None
-    if len(v) > 1: raise formencode.Invalid('Only one field is allowed.', value, None)
-    return to_unsigned(v[0][0]), v[0][1]
-
-
-def to_sled_rental(value):
-    """Example: 'Talstation Serlesbahnan; [[Pinnisalm]]'"""
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None: return None, None
-    sled_rental = True
-    if len(v) == 1: 
-        if v[0][0] == 'Nein': return False, None
-    return True, value
-
-
-def to_cachet(value):
-    if value == '': return None
-    elif value == 'Nein': return value
-    elif value.startswith(u'Tiroler Naturrodelbahn-Gütesiegel '):
-        p = value.split(" ")
-        to_unsigned(p[2]) # check if year can be parsed
-        if not p[3] in ['leicht', 'mittel', 'schwer']: raise formencode.Invalid("Unbekannter Schwierigkeitsgrad", value, None)
-        return value
-    else: raise formencode.Invalid("Unbekanntes Gütesiegel", value, None)
-
-
-def to_information_web(value):
-    if value == 'Nein': return value
-    to_url(value) # is the URL valid?
-    return value
-
-
-def to_information_phone(value):
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None: return None
-    c = formencode.national.InternationalPhoneNumber(default_cc=lambda: 43)
-    for t  in v:
-        if t[0] == 'Nein':
-            if len(v) != 1: raise formencode.Invalid("Bei 'Nein' ist nur ein Eintrag erlaubt", value, None)
-        else:
-            if c.to_python(t[0]) != t[0]: raise formencode.Invalid(u"Telefonnummer hat nicht das gewünschte Format.", value, None)
-            if (t[1] is None): raise formencode.Invalid(u"Bei Telefonnummern muss ein Kommentar angegeben werden.", value, None)
-    return value
-
-
-def to_overnight(value):
-    """'Nein' -> False, None
-    <unparsed text> -> True, <unparsed text>"""
-    if value == '': return None, None
-    if value == 'Nein': return False, None
-    return True, value
-
-
-def from_overnight(is_possible, comment):
-    """is_possible can be None, True or False
-    comment has to be a unicode string."""
-    if is_possible is None:
-        assert comment is None
-        return ''
-    if not is_possible:
-        assert comment is None
-        return u'Nein'
-    return comment
-
-
-def to_sled_rental_inn(value):
-    """'Nein' -> False, None
-    <unparsed text> -> True, <unparsed text>"""
-    return to_overnight(value) # same implementation as to_overnight
-
-
-def from_sled_rental_inn(is_possible, comment):
-    return from_overnight(is_possible, comment) # same implementation
-
-
-def to_forum_id(value):
-    if value == u'Nein': return 0
-    return to_unsigned(value)
-
-
-def to_mobile_provider(value):
-    """A1; T-Mobile A (gut)"""
-    model.validators.ValueCommentList().to_python(value) # no exception should be raised.
-    return value
-
-
-def to_email_list(value):
-    """Converts a list of email addresses with optional comments to the primary (first) adress,
-    comment for the primary address and the complete list.
-    >>> email_primary, email_primary_comment, email_list = to_email_list('first@example.com (Nur Winter); second@example.com')
-    >>> print email_primary
-    'first@example.com'
-    >>> print email_primary_comment
-    'Nur Winter'
-    >>> print email_list
-    'first@example.com (Nur Winter); second@example.com'
-    """
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None or len(v) == 0: return None, None, None
-    for e, c in v: to_email(e) # check for valid addresses
-    return v[0][0], v[0][1], v
-
-
-def to_phone_list(value):
-    """Converts a list of internationa telephone number with optional comments to the primary (first) number,
-    comment for the primary number and the complete list.
-    >>> phone_primary, phone_primary_comment, phone_list = to_phone_list('+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456')
-    >>> print phone_primary
-    '+43-699-1234567'
-    >>> print phone_primary_comment
-    'nicht nach 20:00 Uhr'
-    >>> print phone_list
-    '+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
-    """
-    v = model.validators.ValueCommentList().to_python(value)
-    if v is None or len(v) == 0: return None, None, None
-    ip = formencode.national.InternationalPhoneNumber(default_cc=lambda: 43)
-    for p, c in v: ip.to_python(p) # check for valid phone numbers
-    return v[0][0], v[0][1], v
-
-
-def to_wiki_list(value):
-    """Validates a list of wiki pages like "[[Birgitzer Alm]]; [[Kemater Alm]]" """
-    v = value.split(";")
-    for w in v:
-        if not w.startswith('[[') or not w.endswith(']]'): raise formencode.Invalid('No valid wiki page name', value, None)
-
-
 def conv(fnct, value, fieldname):
     "Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"
     try: return fnct(value)
@@ -287,20 +68,20 @@ def wikipage_to_wrsleddingcache1_2(page_id, page_title, page_text):
         key = key_value[0].strip()
         value = key_value[1].strip()
         if key == u'Rodelbahnnummer': pass
-        elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
-        elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
-        elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
-        elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
-        elif key == u'Aufstieg getrennt': sl.walkup_separate = conv(to_bool, value, u'Aufstieg getrennt')
-        elif key == u'Lift': sl.lift = conv(to_bool, value, u'Lift')
-        elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
-        elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
-        elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
-        elif key == u'Bild': sl.image = conv(to_string, value, key)
-        elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
-        elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
-        elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
-        elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
+        elif key == u'Länge': sl.length = conv(model.validators.Unsigned().to_python, value, u'Länge')
+        elif key == u'Gehzeit': sl.walktime = conv(model.validators.Unsigned().to_python, value, u'Gehzeit')
+        elif key == u'Höhe oben': sl.height_top = conv(model.validators.Unsigned().to_python, value, u'Höhe oben')
+        elif key == u'Höhe unten': sl.height_bottom = conv(model.validators.Unsigned().to_python, value, u'Höhe unten')
+        elif key == u'Aufstieg getrennt': sl.walkup_separate = conv(model.validators.GermanBoolNone().to_python, value, u'Aufstieg getrennt')
+        elif key == u'Lift': sl.lift = conv(model.validators.GermanBoolNone().to_python, value, u'Lift')
+        elif key == u'Beleuchtung': sl.night_light = conv(model.validators.GermanBoolNone().to_python, value, u'Beleuchtung')
+        elif key == u'Rodelverleih': sl.sledge_rental = conv(model.validators.GermanBoolNone().to_python, value, u'Rodelverleih')
+        elif key == u'Öffentliche Anreise': sl.public_transport = conv(model.validators.GermanBoolNone().to_python, value, u'Öffentliche Anreise')
+        elif key == u'Bild': sl.image = conv(model.validators.UnicodeNone().to_python, value, key)
+        elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(model.validators.GeoNone().to_python, value, u'Position') # '47.583333 N 15.75 E'
+        elif key == u'Auskunft': sl.information = conv(model.validators.AustrianPhoneNumberCommentLoop().to_python, value, u'Auskunft')
+        elif key == u'In Übersichtskarte': sl.show_in_overview = conv(model.validators.GermanBoolNone().to_python, value, u'In Übersichtskarte')
+        elif key == u'Aufnahmedatum': sl.creation_date = conv(model.validators.DateNone().to_python, value, u'Aufnahmedatum') # '2006-03-15'
         elif key == u'Lawinengefahr':
             # sl.avalanches is not part of the 1.2 sleddingcache table. We store it in the WrSleddingCache1_2 anyway.
             sl.avalanches = conv(model.validators.GermanAvalanches().to_python, value, key)
@@ -314,19 +95,19 @@ def wikipage_to_wrsleddingcache1_2(page_id, page_title, page_text):
     return sl
 
 
-def wikipage_to_wrsleddingcache(wiki_page):
+def wikipage_to_wrsleddingcache(page_id, page_title, page_text):
     """Converts a sled-route wiki page (wradmin.model.page_table) 
     to a sledding route wrsleddingcache database record (wradmin.model.wrsleddingcache_table).
     Raises a RuntimeError if the format is not OK
     sledding_wiki is a column of tabe "page".
     Returns the WrSleddingCache class"""
     sl = model.WrSleddingCache()
-    sl.page_id = wiki_page.page_id
-    sl.page_title = to_title(wiki_page.page_title)
+    sl.page_id = page_id
+    sl.page_title = to_title(page_title)
     errors = [] # List of errors with localized messages
     
     # Match Rodelbahnbox
-    wikitext = wiki_page.old_text
+    wikitext = page_text
     regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
     match = regexp.search(wikitext)
     if not match:
@@ -344,28 +125,28 @@ def wikipage_to_wrsleddingcache(wiki_page):
         value = key_value[1].strip()
         if key in [u'Rodelbahnnummer', u'Lift']:
             errors.append(_("Property '%s' is not supported anymore, see %s.") % (key, 'http://www.winterrodeln.org/wiki/Vorlage:Rodelbahnbox'))
-        elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, key) # '47.583333 N 15.75 E'
-        elif key == u'Position oben': (sl.top_latitude, sl.top_longitude) = conv(to_geo, value, key) # '47.583333 N 15.75 E'
-        elif key == u'Höhe oben': sl.top_elevation = conv(to_unsigned, value, key) # '2000'
-        elif key == u'Position unten': (sl.bottom_latitude, sl.bottom_longitude) = conv(to_geo, value, key) # '47.583333 N 15.75 E'
-        elif key == u'Höhe unten': sl.bottom_elevation = conv(to_unsigned, value, key) # '1200'
-        elif key == u'Länge': sl.length = conv(to_unsigned, value, key) # 3500
+        elif key == u'Position': sl.position_latitude, sl.position_longitude = conv(model.validators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
+        elif key == u'Position oben': sl.top_latitude, sl.top_longitude = conv(model.validators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
+        elif key == u'Höhe oben': sl.top_elevation = conv(model.validators.UnsignedNone().to_python, value, key) # '2000'
+        elif key == u'Position unten': sl.bottom_latitude, sl.bottom_longitude = conv(model.validators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
+        elif key == u'Höhe unten': sl.bottom_elevation = conv(model.validators.UnsignedNone().to_python, value, key) # '1200'
+        elif key == u'Länge': sl.length = conv(model.validators.UnsignedNone().to_python, value, key) # 3500
         elif key == u'Schwierigkeit': sl.difficulty = conv(model.validators.GermanDifficulty().to_python, value, key) # 'mittel'
         elif key == u'Lawinen': sl.avalanches = conv(model.validators.GermanAvalanches().to_python, value, key) # 'kaum'
-        elif key == u'Betreiber': sl.operator = value # 'Max Mustermann'
+        elif key == u'Betreiber': sl.operator = conv(model.validators.UnicodeNone().to_python, value, key) # 'Max Mustermann'
         elif key == u'Öffentliche Anreise': sl.public_transport = conv(model.validators.GermanPublicTransport().to_python, value, key) # 'Mittelmäßig'
-        elif key == u'Gehzeit': sl.walkup_time = conv(to_unsigned, value, key) # 90
-        elif key == u'Aufstieg getrennt': sl.walkup_separate, sl.walkup_separate_comment = conv(to_walkup_separate, value, key) # 'Ja'
-        elif key == u'Aufstiegshilfe': sl.lift, sl.lift_comment = conv(to_lift, value, key) # 'Gondel (unterer Teil)'
-        elif key == u'Beleuchtungsanlage': sl.night_light, sl.night_light_comment = conv(to_night_light, value, key)
-        elif key == u'Beleuchtungstage': sl.night_light_days, sl.night_light_days_comment = conv(to_night_light_days, value, key) # '3 (Montag, Mittwoch, Freitag)'
-        elif key == u'Rodelverleih': sl.sled_rental, sl.sled_rental_comment = conv(to_sled_rental, value, key) # 'Talstation Serlesbahnan'
-        elif key == u'Gütesiegel': sl.cachet = conv(to_cachet, value, key) # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
-        elif key == u'Webauskunft': sl.information_web = conv(to_information_web, value, key) # 'http://www.nösslachhütte.at/page9.php'
-        elif key == u'Telefonauskunft': sl.information_phone = conv(to_information_phone, value, key) # '+43-664-5487520 (Mitterer Alm)'
-        elif key == u'Bild': sl.image = conv(to_string, value, key)
-        elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, key)
-        elif key == u'Forumid': sl.forum_id = conv(to_forum_id, value, key)
+        elif key == u'Gehzeit': sl.walkup_time = conv(model.validators.UnsignedNone().to_python, value, key) # 90
+        elif key == u'Aufstieg getrennt': sl.walkup_separate, sl.walkup_separate_comment = conv(model.validators.GermanTristateFloatComment().to_python, value, key) # 'Ja'
+        elif key == u'Aufstiegshilfe': sl.lift, sl.lift_details = conv(model.validators.GermanLift().to_python, value, key) # 'Gondel (unterer Teil)'
+        elif key == u'Beleuchtungsanlage': sl.night_light, sl.night_light_comment = conv(model.validators.GermanTristateFloatComment().to_python, value, key)
+        elif key == u'Beleuchtungstage': sl.night_light_days, sl.night_light_days_comment = conv(model.validators.UnsignedCommentNone(7).to_python, value, key) # '3 (Montag, Mittwoch, Freitag)'
+        elif key == u'Rodelverleih': sl.sled_rental, sl.sled_rental_comment = conv(model.validators.SledRental().to_python, value, key) # 'Talstation Serlesbahnan'
+        elif key == u'Gütesiegel': sl.cachet = conv(model.validators.GermanCachet().to_python, value, key) # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
+        elif key == u'Webauskunft': sl.information_web = conv(model.validators.UrlNeinNone().to_python, value, key) # 'http://www.nösslachhütte.at/page9.php'
+        elif key == u'Telefonauskunft': sl.information_phone = conv(model.validators.PhoneCommentListNeinLoopNone(comments_are_optional=False).to_python, value, key) # '+43-664-5487520 (Mitterer Alm)'
+        elif key == u'Bild': sl.image = conv(model.validators.UnicodeNone().to_python, value, key)
+        elif key == u'In Übersichtskarte': sl.show_in_overview = conv(model.validators.GermanBoolNone().to_python, value, key)
+        elif key == u'Forumid': sl.forum_id = conv(model.validators.UnsignedNeinNone().to_python, value, key)
         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
     sl.under_construction = None
     return sl
@@ -397,7 +178,8 @@ def wrSleddingCache1_2_to_WrSleddingCache(wrSleddingCache1_2):
     wrSleddingCache.walkup_separate = 1.0 if wrSleddingCache1_2.walkup_separate else 0.0
     wrSleddingCache.walkup_separate_comment = None
     wrSleddingCache.lift = wrSleddingCache1_2.lift
-    if wrSleddingCache1_2.lift: wrSleddingCache.lift_details = "Ja"
+    if wrSleddingCache1_2.lift is None: wrSleddingCache.lift_details = None
+    elif wrSleddingCache1_2.lift: wrSleddingCache.lift_details = "Sonstige"
     else: wrSleddingCache.lift_details = None
     if wrSleddingCache1_2.night_light is None: wrSleddingCache.night_light = None
     else: wrSleddingCache.night_light = 1.0 if wrSleddingCache1_2.night_light else 0.0
@@ -405,7 +187,8 @@ def wrSleddingCache1_2_to_WrSleddingCache(wrSleddingCache1_2):
     wrSleddingCache.night_light_days = None
     wrSleddingCache.night_light_days_comment = None
     wrSleddingCache.sled_rental = wrSleddingCache1_2.sledge_rental
-    wrSleddingCache.sled_rental_comment = None
+    if wrSleddingCache.sled_rental: wrSleddingCache.sled_rental_comment = u'Ja'
+    else: wrSleddingCache.sled_rental_comment = None
     wrSleddingCache.cachet = None
     wrSleddingCache.information_web = None
     if wrSleddingCache1_2.information is None: wrSleddingCache.information_phone = None
@@ -427,90 +210,53 @@ def wrSleddingCache1_2_to_WrSleddingCache(wrSleddingCache1_2):
 
 
 def wrSleddingCache_to_Rodelbahnbox(wrSleddingCache):
-    """Converts the WrSleddingCache class to the {{Rodelbahnbox}} representation.
-wrSleddingCache.cachet
-wrSleddingCache.forum_id
-wrSleddingCache.image
-wrSleddingCache.information_phone
-wrSleddingCache.information_web
-wrSleddingCache.lift
-wrSleddingCache.lift_details
-wrSleddingCache.night_light
-wrSleddingCache.night_light_comment
-wrSleddingCache.night_light_days
-wrSleddingCache.night_light_days_comment
-wrSleddingCache.show_in_overview
-wrSleddingCache.sled_rental
-wrSleddingCache.sled_rental_comment
-wrSleddingCache.under_construction
-    """
+    """Converts the WrSleddingCache class to the {{Rodelbahnbox}} representation."""
     keys = []
     values = []
     keys.append(u'Position')
-    values.append(wradmin.model.validators.Geo().from_python((wrSleddingCache.position_latitude, wrSleddingCache.position_longitude)))
+    values.append(model.validators.GeoNone().from_python((wrSleddingCache.position_latitude, wrSleddingCache.position_longitude)))
     keys.append(u'Position oben')
-    values.append(wradmin.model.validators.Geo().from_python((wrSleddingCache.top_latitude, wrSleddingCache.top_longitude)))
+    values.append(model.validators.GeoNone().from_python((wrSleddingCache.top_latitude, wrSleddingCache.top_longitude)))
     keys.append(u'Höhe oben')
-    values.append(from_unsigned(wrSleddingCache.top_elevation))
+    values.append(model.validators.UnsignedNone().from_python(wrSleddingCache.top_elevation))
     keys.append(u'Position unten')
-    values.append(wradmin.model.validators.Geo().from_python((wrSleddingCache.bottom_latitude, wrSleddingCache.bottom_longitude)))
+    values.append(model.validators.GeoNone().from_python((wrSleddingCache.bottom_latitude, wrSleddingCache.bottom_longitude)))
     keys.append(u'Höhe unten')
-    values.append(from_unsigned(wrSleddingCache.bottom_elevation))
+    values.append(model.validators.UnsignedNone().from_python(wrSleddingCache.bottom_elevation))
     keys.append(u'Länge')
-    values.append(from_unsigned(wrSleddingCache.length))
+    values.append(model.validators.UnsignedNone().from_python(wrSleddingCache.length))
     keys.append(u'Schwierigkeit')
     values.append(model.validators.GermanDifficulty().from_python(wrSleddingCache.difficulty))
     keys.append(u'Lawinen')
     values.append(model.validators.GermanAvalanches().from_python(wrSleddingCache.avalanches))
     keys.append(u'Betreiber')
-    values.append(formencode.validators.String().from_python(wrSleddingCache.operator))
+    values.append(model.validators.UnicodeNone().from_python(wrSleddingCache.operator))
     keys.append(u'Öffentliche Anreise')
     values.append(model.validators.GermanPublicTransport().from_python(wrSleddingCache.public_transport))
     keys.append(u'Gehzeit')
-    values.append(from_unsigned(wrSleddingCache.walkup_time))
+    values.append(model.validators.UnsignedNone().from_python(wrSleddingCache.walkup_time))
     keys.append(u'Aufstieg getrennt')
-    v = model.validators.GermanTristateFloat().from_python(wrSleddingCache.walkup_separate)
-    if not wrSleddingCache.walkup_separate_comment is None: v += ' (%s)' % wrSleddingCache.walkup_separate_comment
-    values.append(v)
-    
-    b = u"""
-| Aufstiegshilfe       = Gondel (unterer Teil)
-| Beleuchtungsanlage   = Ja
-| Beleuchtungstage     = 3 (Montag, Mittwoch, Freitag)
-| Rodelverleih         = Ja (Talstation Serlesbahnan)
-| Gütesiegel           = Tiroler Naturrodelbahn-Gütesiegel 2009 mittel
-| Webauskunft          = http://www.nösslachhütte.at/page9.php
-| Telefonauskunft      = +43-664-5487520 (Mitterer Alm)
-| Bild                 = Rodelbahn_Mitterer_Alm_04.jpg
-| In Übersichtskarte   = Ja
-| Forumid              = 33
-}}
-    """
-    
-    c = u"""
-    keys.append(u'Betreiber')
-    values.append(formencode.validators.String().from_python(wrInnCache.operator))
-    keys.append(u'Sitzplätze')
-    values.append(from_unsigned(wrInnCache.seats))
-    keys.append(u'Übernachtung')
-    values.append(from_overnight(wrInnCache.overnight, wrInnCache.overnight_comment))
-    keys.append(u'Rauchfrei')
-    values.append(wradmin.model.validators.GermanTristate.from_python((wrInnCache.nonsmoker_area, wrInnCache.smoker_area)))
+    values.append(model.validators.GermanTristateFloatComment().from_python((wrSleddingCache.walkup_separate, wrSleddingCache.walkup_separate_comment)))
+    keys.append(u'Aufstiegshilfe')
+    values.append(model.validators.GermanLift().from_python((wrSleddingCache.lift, wrSleddingCache.lift_details)))
+    keys.append(u'Beleuchtungsanlage')
+    values.append(model.validators.GermanTristateFloatComment().from_python((wrSleddingCache.night_light, wrSleddingCache.night_light_comment)))
+    keys.append(u'Beleuchtungstage')
+    values.append(model.validators.UnsignedCommentNone(max=7).from_python((wrSleddingCache.night_light_days, wrSleddingCache.night_light_days_comment)))
     keys.append(u'Rodelverleih')
-    values.append(from_sled_rental_inn(wrInnCache.sled_rental, wrInnCache.sled_rental_comment))
-    keys.append(u'Handyempfang')
-    values.append(formencode.validators.String().from_python(wrInnCache.mobile_provider))
-    keys.append(u'Homepage')
-    values.append(formencode.validators.String().from_python(wrInnCache.homepage))
-    keys.append(u'E-Mail')
-    values.append(formencode.validators.String().from_python(wrInnCache.email_list))
-    keys.append(u'Telefon')
-    values.append(formencode.validators.String().from_python(wrInnCache.phone_list))
+    values.append(model.validators.SledRental().from_python((wrSleddingCache.sled_rental, wrSleddingCache.sled_rental_comment)))
+    keys.append(u'Gütesiegel')
+    values.append(model.validators.GermanCachet().from_python(wrSleddingCache.cachet))
+    keys.append(u'Webauskunft')
+    values.append(model.validators.UrlNeinNone().from_python(wrSleddingCache.information_web))
+    keys.append(u'Telefonauskunft')
+    values.append(model.validators.PhoneCommentListNeinLoopNone(comments_are_optional=False).from_python(wrSleddingCache.information_phone))
     keys.append(u'Bild')
-    values.append(formencode.validators.String().from_python(wrInnCache.image))
-    keys.append(u'Rodelbahnen')
-    values.append(formencode.validators.String().from_python(wrInnCache.sledding_list))
-    """
+    values.append(model.validators.UnicodeNone().from_python(wrSleddingCache.image))
+    keys.append(u'In Übersichtskarte')
+    values.append(model.validators.GermanBoolNone().from_python(wrSleddingCache.show_in_overview))
+    keys.append(u'Forumid')
+    values.append(model.validators.UnsignedNeinNone().from_python(wrSleddingCache.forum_id))
     result = [u'{{Rodelbahnbox']
     for i in xrange(len(keys)): result.append(u'| %-20s = %s' % (keys[i], values[i]))
     result.append('}}\n')
@@ -544,29 +290,29 @@ def wikipage_to_wrinncache1_2(page_id, page_title, page_text):
         key = key_value[0].strip()
         value = key_value[1].strip()
         if key == u'Gasthausnummer': pass
-        elif key == u'E-Mail': inn.email = conv(to_email, value, u'E-Mail')
-        elif key == u'Homepage': inn.homepage = conv(to_url, value, u'Homepage')
-        elif key == u'Höhe': inn.height = conv(to_unsigned, value, u'Höhe')
-        elif key == u'Bild': inn.image = conv(to_string, value, key)
-        elif key == u'Position': inn.position_latitude, inn.position_longitude = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
-        elif key == u'Telefon (Festnetz)': inn.phone = conv(to_phone, value, u'Telefon (Festnetz)')
-        elif key == u'Telefon (Mobil)': inn.mobile_phone = conv(to_phone, value, u'Telefon (Mobil)')
-        elif key == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = conv(to_tristate, value, u'Rauchfrei')
-        elif key == u'Aufnahmedatum': inn.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
+        elif key == u'E-Mail': inn.email = conv(formencode.validators.Email().to_python, value, u'E-Mail')
+        elif key == u'Homepage': inn.homepage = conv(model.validators.UrlNeinNone().to_python, value, u'Homepage')
+        elif key == u'Höhe': inn.height = conv(model.validators.Unsigned().to_python, value, u'Höhe')
+        elif key == u'Bild': inn.image = conv(model.validators.UnicodeNone().to_python, value, key)
+        elif key == u'Position': inn.position_latitude, inn.position_longitude = conv(model.validators.GeoNone().to_python, value, u'Position') # '47.583333 N 15.75 E'
+        elif key == u'Telefon (Festnetz)': inn.phone = conv(model.validators.AustrianPhoneNumberNone().to_python, value, u'Telefon (Festnetz)')
+        elif key == u'Telefon (Mobil)': inn.mobile_phone = conv(model.validators.AustrianPhoneNumberNone().to_python, value, u'Telefon (Mobil)')
+        elif key == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = conv(model.validators.GermanTristateTuple().to_python, value, u'Rauchfrei')
+        elif key == u'Aufnahmedatum': inn.creation_date = conv(model.validators.DateNone().to_python, value, u'Aufnahmedatum') # '2006-03-15'
         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Gasthausbox: '%s' (mit Wert '%s')" % (key, value), value, None)
     inn.under_construction = None
     return inn
 
 
-def wikipage_to_wrinncache(wiki_page):
+def wikipage_to_wrinncache(page_id, page_title, page_text):
     """Converts a inn wiki page (wradmin.model.page_table) to a wrinncache database record
     (wradmin.model.wrinncache_table)."""
     inn = model.WrInnCache()
-    inn.page_id = inn_wiki.page_id
-    inn.page_title = to_title(inn_wiki.page_title)
+    inn.page_id = page_id
+    inn.page_title = to_title(page_title)
     
     # Match Gasthausbox
-    wikitext = inn_wiki.old_text
+    wikitext = page_text
     regexp = re.compile(u"\{\{(Gasthausbox[^\}]*)\}\}", re.DOTALL)
     match = regexp.search(wikitext)
     if not match:
@@ -582,19 +328,19 @@ def wikipage_to_wrinncache(wiki_page):
             raise Exception(u"Property '%s' has unexpected format" % key_value)
         key = key_value[0].strip()
         value = key_value[1].strip()
-        if key == u'Position': (inn.position_latitude, inn.position_longitude) = conv(to_geo, value, key) # '47.583333 N 15.75 E'
-        elif key == u'Höhe': inn.position_elevation = conv(to_unsigned, value, key)
-        elif key == u'Operator': inn.operator = conv(to_string, value, key)
-        elif key == u'Sitzplätze': inn.seats = conv(to_unsigned, value, key)
-        elif key == u'Übernachtung': inn.overnight, inn.overnight_comment = conv(to_overnight, value, key)
-        elif key == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = conv(to_tristate, value, key)
-        elif key == u'Rodelverleih': inn.sled_rental, inn.sled_rental_comment = conv(to_sled_rental_inn, value, key)
-        elif key == u'Handyempfang': inn.mobile_provider = conv(to_mobile_provider, value, key)
-        elif key == u'Homepage': inn.homepage = conv(to_information_web, value, key)
-        elif key == u'E-Mail': inn.email, inn.email_comment, inn.email_list = conv(to_email_list, value, key)
-        elif key == u'Telefon': inn.phone, inn.phone_comment, inn.phone_list = conv(to_phone_list, value, key)
-        elif key == u'Bild': inn.image = conv(to_string, value, key)
-        elif key == u'Rodelbahnen': inn.sledding_list = conv(to_sledding_list, value, key)
+        if key == u'Position': inn.position_latitude, inn.position_longitude = conv(model.validators.GeoNone().to_python, value, key) # '47.583333 N 15.75 E'
+        elif key == u'Höhe': inn.position_elevation = conv(model.validators.UnsignedNone().to_python, value, key)
+        elif key == u'Betreiber': inn.operator = conv(model.validators.UnicodeNone().to_python, value, key)
+        elif key == u'Sitzplätze': inn.seats = conv(model.validators.UnsignedNone().to_python, value, key)
+        elif key == u'Übernachtung': inn.overnight, inn.overnight_comment = conv(model.validators.BoolUnicodeTupleValidator().to_python, value, key)
+        elif key == u'Rauchfrei': inn.nonsmoker_area, inn.smoker_area = conv(model.validators.GermanTristateTuple().to_python, value, key)
+        elif key == u'Rodelverleih': inn.sled_rental, inn.sled_rental_comment = conv(model.validators.BoolUnicodeTupleValidator().to_python, value, key)
+        elif key == u'Handyempfang': inn.mobile_provider = conv(model.validators.ValueCommentListNeinLoopNone().to_python, value, key)
+        elif key == u'Homepage': inn.homepage = conv(model.validators.UrlNeinNone().to_python, value, key)
+        elif key == u'E-Mail': inn.email_list = conv(model.validators.EmailCommentListNeinLoopNone().to_python, value, key)
+        elif key == u'Telefon': inn.phone_list = conv(model.validators.PhoneCommentListNeinLoopNone(comments_are_optional=True).to_python, value, key)
+        elif key == u'Bild': inn.image = conv(model.validators.UnicodeNone().to_python, value, key)
+        elif key == u'Rodelbahnen': inn.sledding_list = conv(model.validators.WikiPageListLoopNone().to_python, value, key)
         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Gasthausbox: '%s' (mit Wert '%s')" % (key, value), value, None)
     inn.under_construction = None
     return inn
@@ -618,17 +364,13 @@ def wrInnCache1_2_to_WrInnCache(wrInnCache1_2):
     wrInnCache.sled_rental_comment = None
     wrInnCache.mobile_provider = None
     wrInnCache.homepage = wrInnCache1_2.homepage
-    wrInnCache.email = wrInnCache1_2.email
-    wrInnCache.email_comment = None
     wrInnCache.email_list = wrInnCache1_2.email
     phone_list = []
     c = formencode.national.InternationalPhoneNumber(default_cc=lambda: 43)
     if not wrInnCache1_2.phone is None: phone_list.append(c.to_python(wrInnCache1_2.phone))
     if not wrInnCache1_2.mobile_phone is None: phone_list.append(c.to_python(wrInnCache1_2.mobile_phone))
-    if len(phone_list) >= 1: wrInnCache.phone = phone_list[0]
-    else: wrInnCache.phone = None
-    wrInnCache.phone_comment = None
-    wrInnCache.phone_list = "; ".join(phone_list)
+    if len(phone_list) >= 1: wrInnCache.phone_list = "; ".join(phone_list)
+    else: phone_list = None
     wrInnCache.image = wrInnCache1_2.image
     wrInnCache.sledding_list = None
     wrInnCache.under_construction = wrInnCache1_2.under_construction
@@ -640,31 +382,31 @@ def wrInnCache_to_Gasthausbox(wrInnCache):
     keys = []
     values = []
     keys.append(u'Position')
-    values.append(wradmin.model.validators.Geo().from_python((wrInnCache.position_latitude, wrInnCache.position_longitude)))
+    values.append(model.validators.GeoNone().from_python((wrInnCache.position_latitude, wrInnCache.position_longitude)))
     keys.append(u'Höhe')
-    values.append(from_unsigned(wrInnCache.position_elevation))
+    values.append(model.validators.UnsignedNone().from_python(wrInnCache.position_elevation))
     keys.append(u'Betreiber')
-    values.append(formencode.validators.String().from_python(wrInnCache.operator))
+    values.append(model.validators.UnicodeNone().from_python(wrInnCache.operator))
     keys.append(u'Sitzplätze')
-    values.append(from_unsigned(wrInnCache.seats))
+    values.append(model.validators.UnsignedNone().from_python(wrInnCache.seats))
     keys.append(u'Übernachtung')
-    values.append(from_overnight(wrInnCache.overnight, wrInnCache.overnight_comment))
+    values.append(model.validators.BoolUnicodeTupleValidator().from_python((wrInnCache.overnight, wrInnCache.overnight_comment)))
     keys.append(u'Rauchfrei')
-    values.append(wradmin.model.validators.GermanTristate.from_python((wrInnCache.nonsmoker_area, wrInnCache.smoker_area)))
+    values.append(model.validators.GermanTristateTuple().from_python((wrInnCache.nonsmoker_area, wrInnCache.smoker_area)))
     keys.append(u'Rodelverleih')
-    values.append(from_sled_rental_inn(wrInnCache.sled_rental, wrInnCache.sled_rental_comment))
+    values.append(model.validators.BoolUnicodeTupleValidator().from_python((wrInnCache.sled_rental, wrInnCache.sled_rental_comment)))
     keys.append(u'Handyempfang')
-    values.append(formencode.validators.String().from_python(wrInnCache.mobile_provider))
+    values.append(model.validators.ValueCommentListNeinLoopNone().from_python(wrInnCache.mobile_provider))
     keys.append(u'Homepage')
-    values.append(formencode.validators.String().from_python(wrInnCache.homepage))
+    values.append(model.validators.UrlNeinNone().from_python(wrInnCache.homepage))
     keys.append(u'E-Mail')
-    values.append(formencode.validators.String().from_python(wrInnCache.email_list))
+    values.append(model.validators.EmailCommentListNeinLoopNone().from_python(wrInnCache.email_list))
     keys.append(u'Telefon')
-    values.append(formencode.validators.String().from_python(wrInnCache.phone_list))
+    values.append(model.validators.PhoneCommentListNeinLoopNone(comments_are_optional=True).from_python(wrInnCache.phone_list))
     keys.append(u'Bild')
-    values.append(formencode.validators.String().from_python(wrInnCache.image))
+    values.append(model.validators.UnicodeNone().from_python(wrInnCache.image))
     keys.append(u'Rodelbahnen')
-    values.append(formencode.validators.String().from_python(wrInnCache.sledding_list))
+    values.append(model.validators.WikiPageListLoopNone().from_python(wrInnCache.sledding_list))
     result = [u'{{Gasthausbox']
     for i in xrange(len(keys)): result.append(u'| %-17s = %s' % (keys[i], values[i]))
     result.append('}}\n')
index d91e7c6d08c7de395cb912acb7d36c636a679ef0..eb5429780f18427e59a75a9ef6dbda03f22b698b 100644 (file)
@@ -5,7 +5,7 @@ A WRGPX file (WR stands for Winterrodeln) is a normal GPX file
 that follows several conventions so that it can be rendered to
 a map automatically."""
 
-import mapnik
+import mapnik2 as mapnik
 import datetime
 import os
 from lxml import etree
index e733948a192f58ed9dd21c65e88b59993b3d52da..31f8f2c1b6b76367eb53b0f4f14a1bac0154bb34 100644 (file)
 # -*- coding: iso-8859-15 -*-
+"""This file contains "validators" that convert between string and python (database) representation
+of properties used in the "Rodelbahnbox" and "Gasthausbox".
+The "to_python" method has to get a unicode argument.
+You can run tests with
+>>> nosetests --with-pylons=test.ini
+"""
 import formencode
+import formencode.national
 import datetime
 import re
 import xml.dom.minidom as minidom
 from xml.parsers.expat import ExpatError
 
 
-class GermanBool(formencode.FancyValidator):
-    "Converts German bool values to the python bool type. 'Ja' and 'Nein' are supported."
+class NoneValidator(formencode.FancyValidator):
+    """Takes a validator and makes it possible that empty strings are mapped to None."""
+    def __init__(self, validator, python_none=None):
+        self.validator = validator
+        self.python_none = python_none
     
-    def __init__(self, yes = [u'Ja'], no = [u'Nein'], **keywords):
-        "The yes and no arguments specify the valid possibilities. The first possibility is the default one."
-        formencode.FancyValidator.__init__(self, **keywords)
-        self.yes = yes
-        self.no = no
+    def to_python(self, value):
+        self.assert_string(value, None)
+        if value == u'': return self.python_none
+        return self.validator.to_python(value)
+    
+    def from_python(self, value):
+        if value == self.python_none: return u''
+        return self.validator.from_python(value)
+
+
+class NeinValidator(formencode.FancyValidator):
+    """Take an arbitrary validator and adds the possibility that the
+    string can be u'Nein'.
+    Example together with an UnsignedNone validator:
+    >>> v = NeinValidator(UnsignedNone())
+    >>> v.to_python(u'')
+    None
+    >>> v.to_python(u'34')
+    34
+    >>> v.to_python(u'Nein')
+    u'Nein'
+    """
+    def __init__(self, validator, python_no=u'Nein'):
+        self.validator = validator
+        self.python_no = python_no
     
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        if value in self.yes: return True
-        if value in self.no: return False
-        all = self.yes[:]
-        all.extend(self.no)
-        raise formencode.Invalid(u"'%s' is not a valid boolean value, use one of %s" % (value, all), value, state)
+    def to_python(self, value):
+        self.assert_string(value, None)
+        if value == u'Nein': return self.python_no
+        return self.validator.to_python(value)
     
     def from_python(self, value):
-        if value is None: return u''
-        if value: return self.yes[0]
-        return self.no[0]
+        if value == self.python_no: return u'Nein'
+        return self.validator.from_python(value)
 
 
-class GenericDateTimeConverter(formencode.FancyValidator):
-    """Converts generic date/time information to datetime classes with a user defined format.
-    '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'."""
-    
-    def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords):
-        formencode.FancyValidator.__init__(self, **keywords)
-        self.date_time_format = date_time_format
-    
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        try: return datetime.datetime.strptime(value, self.date_time_format)
-        except ValueError, e: raise formencode.Invalid(str(e), value, state)
+class Unicode(formencode.FancyValidator):
+    """Converts an unicode string to an unicode string:
+    u'any string' <=> u'any string'"""
+    def to_python(self, value):
+        self.assert_string(value, None)
+        return unicode(value)
+
+    def from_python(self, value):
+        return unicode(value)
+
+
+class UnicodeNone(NoneValidator):
+    """Converts an unicode string to an unicode string:
+    u'' <=> None
+    u'any string' <=> u'any string'"""
+    def __init__(self):
+        NoneValidator.__init__(self, Unicode())
+
+
+class Unsigned(formencode.FancyValidator):
+    """Converts an unsigned number to a string and vice versa:
+    u'0'  <=>  0
+    u'1'  <=>  1
+    u'45' <=> 45
+    """
+    def __init__(self, max=None):
+        self.iv = formencode.validators.Int(min=0, max=max)
+
+    def to_python(self, value):
+        self.assert_string(value, None)
+        return self.iv.to_python(value)
     
-    def _from_python(self, value, state):
-        if value is None: return u''
-        return value.strftime(self.date_time_format)
+    def from_python(self, value):
+        return unicode(value)
 
 
-class DateTimeNoSecConverter(GenericDateTimeConverter):
-    def __init__(self, **keywords):
-        GenericDateTimeConverter.__init__(self, '%Y-%m-%d %H:%M', **keywords)
+class UnsignedNone(NoneValidator):
+    """Converts an unsigned number to a string and vice versa:
+    u''   <=> None
+    u'0'  <=>  0
+    u'1'  <=>  1
+    u'45' <=> 45
+    """
+    def __init__(self, max=None):
+        NoneValidator.__init__(self, Unsigned(max))
+
+
+class UnsignedNeinNone(NoneValidator):
+    """ Translates a number of Nein to a number.
+    u''     <=> None
+    u'Nein' <=> 0
+    u'1'    <=> 1
+    u'2'    <=> 2
+    ...
+    """
+    def __init__(self):
+        NoneValidator.__init__(self, UnsignedNone())
+
 
+class Loop(formencode.FancyValidator):
+    """Takes a validator and calls from_python(to_python(value))."""
+    def __init__(self, validator):
+        self.validator = validator
 
-class DateConverter(GenericDateTimeConverter):
-    "Converts date information to date classes with the format '%Y-%m-%d'."
+    def to_python(self, value):
+        self.assert_string(value, None)
+        return self.validator.from_python(self.validator.to_python(value))
     
-    def __init__(self, **keywords):
-        GenericDateTimeConverter.__init__(self, '%Y-%m-%d', **keywords)
+    def from_python(self, value):
+        # we don't call self.validator.to_python(self.validator.from_python(value))
+        # here because our to_python implementation basically leaves the input untouches
+        # and so should from_python do.
+        return self.validator.from_python(self.validator.to_python(value))
+
+
+class DictValidator(formencode.FancyValidator):
+    """Translates strings to other values via a python directory.
+    >>> boolValidator = DictValidator({u'': None, u'Ja': True, u'Nein': False})
+    >>> boolValidator.to_python(u'')
+    None
+    >>> boolValidator.to_python(u'Ja')
+    True
+    """
+    def __init__(self, dict):
+        self.dict = dict
+    
+    def to_python(self, value):
+        self.assert_string(value, None)
+        if not self.dict.has_key(value): raise formencode.Invalid("Key not found in dict.", value, None)
+        return self.dict[value]
     
-    def _to_python(self, value, state):
-        return GenericDateTimeConverter._to_python(self, value, state).date()
+    def from_python(self, value):
+        for k, v in self.dict.iteritems():
+            if type(v) == type(value) and v == value: return k
+        raise formencode.Invalid('Invalid value', value, None)
 
 
-class GermanTristate(formencode.FancyValidator):
+class GermanBoolNone(DictValidator):
+    """Converts German bool values to the python bool type:
+    u''     <=> None
+    u'Ja'   <=> True
+    u'Nein' <=> False
+    """
+    def __init__(self):
+        DictValidator.__init__(self, {u'': None, u'Ja': True, u'Nein': False})
+
+
+class GermanTristateTuple(DictValidator):
     """Does the following conversion:
-    None         -> (None, None)
-    u'Ja'        -> (True, False)
-    u'Teilweise' -> (True,  True)
-    u'Nein'      -> (False, True)"""
+    u''          <=> (None, None)
+    u'Ja'        <=> (True, False)
+    u'Teilweise' <=> (True,  True)
+    u'Nein'      <=> (False, True)"""
+    def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), none_python = (None, None)):
+        DictValidator.__init__(self, {u'': none_python, u'Ja': yes_python, u'Nein': no_python, u'Teilweise': partly_python})
+
+
+class GermanTristateFloat(GermanTristateTuple):
+    """Converts the a property with the possible values 0.0, 0.5, 1.0 or None
+    to a German text:
+    u''          <=> None
+    u'Ja'        <=> 1.0
+    u'Teilweise' <=> 0.5
+    u'Nein'      <=> 0.0"""
+    def __init__(self):
+        GermanTristateTuple.__init__(self, yes_python=1.0, no_python=0.0, partly_python=0.5, none_python=None)
+
+
+class ValueComment(formencode.FancyValidator):
+    """Converts value with a potentially optional comment to a python tuple:
+    u''                <=> (None, None)
+    u'value'           <=> (u'value', None)
+    u'value (comment)' <=> (u'value', u'comment')"""
+    def __init__(self, value_validator=UnicodeNone(), comment_validator=UnicodeNone(), comment_is_optional=True):
+        self.value_validator = value_validator
+        self.comment_validator = comment_validator
+        self.comment_is_optional = comment_is_optional
+    
+    def to_python(self, value):
+        self.assert_string(value, None)
+        if value == u'':
+            v = value
+            c = value
+        else:
+            left = value.find('(')
+            right = value.rfind(')')
+            if left < 0 and right < 0:
+                if not self.comment_is_optional: raise formencode.Invalid(u'Mandatory comment not present', value, None)
+                v = value
+                c = u''
+            elif left >= 0 and right >= 0 and left < right:
+                v = value[:left].strip()
+                c = value[left+1:right].strip()
+            else: raise formencode.Invalid(u'Invalid format', value, None)
+        return self.value_validator.to_python(v), self.comment_validator.to_python(c)
+
+    def from_python(self, value):
+        assert len(value) == 2
+        v = self.value_validator.from_python(value[0])
+        c = self.comment_validator.from_python(value[1])
+        if len(c) > 0:
+            if len(v) > 0: return u'%s (%s)' % (v, c)
+            else: return u'(%s)' % c
+        return v
+
+
+class SemicolonList(formencode.FancyValidator):
+    """Applies a given validator to a semicolon separated list of values and returns a python list.
+    For an empty string an empty list is returned."""
+    def __init__(self, validator=Unicode()):
+        self.validator = validator
     
-    def __init__(self, yes_python = (True, False), no_python = (False, True), partly_python = (True, True), yes_text = [u'Ja'], no_text = [u'Nein'], partly_text = [u'Teilweise'], **keywords):
-        formencode.FancyValidator.__init__(self, if_empty = (None, None), **keywords)
-        self.yes_python = yes_python
-        self.no_python = no_python
-        self.partly_python = partly_python
-        self.yes_text = yes_text
-        self.no_text = no_text
-        self.partly_text = partly_text
+    def to_python(self, value):
+        self.assert_string(value, None)
+        return [self.validator.to_python(s.strip()) for s in value.split(';')]
     
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        if value in self.yes_text: return self.yes_python
-        if value in self.no_text: return self.no_python
-        if value in self.partly_text: return self.partly_python
-        all = self.yes_text[:]
-        all.extend(self.no_text)
-        all.extend(self.partly_text)
-        raise formencode.Invalid(u"'%s' is not a valid value, use one of %s" % (value, all), value, state)
+    def from_python(self, value):
+        return "; ".join([self.validator.from_python(s) for s in value])
+        
     
-    def _from_python(self, value, state):
-        if value == (None, None): return ''
-        if value == self.yes_python: return self.yes_text[0]
-        if value == self.no_python: return self.no_text[0]
-        if value == self.partly_python: return self.partly_text[0]
-        raise formencode.Invalid(u"Invalid representation of a tristate value: '%s'" % (value,), value, state)
+class ValueCommentList(SemicolonList):
+    """A value-comment list looks like one of the following lines:
+        value
+        value (optional comment)
+        value1; value2
+        value1; value2 (optional comment)
+        value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
+        value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
+    This function returns the value-comment list as list of tuples:
+        [(u'value1', u'comment1'), (u'value2', None)]
+    If no comment is present, None is specified.
+    For an empty string, [] is returned."""    
+    def __init__(self, value_validator=Unicode(), comments_are_optional=True):
+        SemicolonList.__init__(self, ValueComment(value_validator, comment_is_optional=comments_are_optional))
 
 
-class Geo(formencode.FancyValidator):
-    "Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."
+class GenericDateTime(formencode.FancyValidator):
+    """Converts a generic date/time information to a datetime class with a user defined format.
+    '2009-03-22 20:36:15' would be specified as '%Y-%m-%d %H:%M:%S'."""
     
-    def __init__(self, **keywords):
-        formencode.FancyValidator.__init__(self, if_empty = (None, None), **keywords)
+    def __init__(self, date_time_format = '%Y-%m-%d %H:%M:%S', **keywords):
+        formencode.FancyValidator.__init__(self, **keywords)
+        self.date_time_format = date_time_format
+    
+    def to_python(self, value):
+        self.assert_string(value, None)
+        try: return datetime.datetime.strptime(value, self.date_time_format)
+        except ValueError, e: raise formencode.Invalid(str(e), value, None)
     
-    def _to_python(self, value, state):
+    def from_python(self, value):
+        return value.strftime(self.date_time_format)
+
+
+class DateTimeNoSec(GenericDateTime):
+    def __init__(self, **keywords):
+        GenericDateTime.__init__(self, '%Y-%m-%d %H:%M', **keywords)
+
+
+class DateNone(NoneValidator):
+    """Converts date information to date classes with the format '%Y-%m-%d' or None."""
+    def __init__(self):
+        NoneValidator.__init__(self, GenericDateTime('%Y-%m-%d'))
+
+
+class Geo(formencode.FancyValidator):
+    """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
+    def to_python(self, value):
+        self.assert_string(value, None)
         r = re.match(u'(\d+\.\d+) N (\d+\.\d+) E', value)
-        if r is None: raise formencode.Invalid(u"Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, state)
+        if r is None: raise formencode.Invalid(u"Coordinates '%s' have not a format like '47.076207 N 11.453553 E'" % value, value, None)
         return (float(r.groups()[0]), float(r.groups()[1]))
     
-    def _from_python(self, value, state):
-        if value == (None, None): return ''
+    def from_python(self, value):
         latitude, longitude = value
         return u'%.6f N %.6f E' % (latitude, longitude)
 
 
+class GeoNone(NoneValidator):
+    """Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."""
+    def __init__(self):
+        NoneValidator.__init__(self, Geo(), (None, None))
+
+
 class MultiGeo(formencode.FancyValidator):
     "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets."
     
@@ -135,10 +320,11 @@ class MultiGeo(formencode.FancyValidator):
         self.output_format = output_format
         formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords)
     
-    def _to_python(self, value, state):
+    def to_python(self, value):
+        self.assert_string(value, None)
         input_format = self.input_format
         if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]:
-            raise formencode.Invalid(u"input_format %d is not recognized" % input_format, value, state) # Shouldn't it be an other type of runtime error?
+            raise formencode.Invalid(u"input_format %d is not recognized" % input_format, value, None) # Shouldn't it be an other type of runtime error?
         lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0]
         
         result = []
@@ -178,11 +364,11 @@ class MultiGeo(formencode.FancyValidator):
                     continue
                 except (ExpatError, IndexError, ValueError): pass
 
-            raise formencode.Invalid(u"Coordinates '%s' have no known format" % line, value, state)
+            raise formencode.Invalid(u"Coordinates '%s' have no known format" % line, value, None)
             
         return result
     
-    def _from_python(self, value, state):
+    def from_python(self, value):
         output_format = self.output_format
         result = []
         for latitude, longitude, height in value:
@@ -201,7 +387,7 @@ class MultiGeo(formencode.FancyValidator):
                 else: result.append(u'<trkpt lat="%.6f" lon="%.6f"/>' % (latitude, longitude))
             
             else:
-                raise formencode.Invalid(u"output_format %d is not recognized" % output_format, value, state) # Shouldn't it be an other type of runtime error?
+                raise formencode.Invalid(u"output_format %d is not recognized" % output_format, value, None) # Shouldn't it be an other type of runtime error?
             
         return "\n".join(result)
 
@@ -229,23 +415,23 @@ class AustrianPhoneNumber(formencode.FancyValidator):
     default_cc = 43 # Default country code
     messages = {'phoneFormat': "'%%(value)s' is an invalid format. Please enter a number in the form +43/###/####### or 0###/########."}
 
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
+    def to_python(self, value):
+        self.assert_string(value, None)
         m = re.match(u'^(?:\+(\d+)/)?([\d/]+)(?:-(\d+))?$', value)
         # This will separate 
         #     u'+43/512/1234567-89'  => (u'43', u'512/1234567', u'89')
         #     u'+43/512/1234/567-89' => (u'43', u'512/1234/567', u'89')
         #     u'+43/512/1234/567'    => (u'43', u'512/1234/567', None)
         #     u'0512/1234567'        => (None, u'0512/1234567', None)
-        if m is None: raise formencode.Invalid(self.message('phoneFormat', state) % {'value': value}, value, state)
+        if m is None: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
         (country, phone, extension) = m.groups()
         
         # Phone
-        if phone.find(u'//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', state) % {'value': value}, value, state)
+        if phone.find(u'//') > -1 or phone.count('/') == 0: raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
         
         # Country
         if country is None:
-            if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', state) % {'value': value}, value, state)
+            if phone[0] != '0': raise formencode.Invalid(self.message('phoneFormat', None) % {'value': value}, value, None)
             phone = phone[1:]
             country = unicode(self.default_cc)
         
@@ -254,118 +440,29 @@ class AustrianPhoneNumber(formencode.FancyValidator):
 
 
 # Deprecated
-class PhoneInfo(formencode.FancyValidator):
-    "Validates a info of the form '0644/1234567 (Schnee Alm)'"
-    messages = {'infoFormat': "'%%(value)s' is no valid format, please use a form like '0644/1234567 (Schnee Alm)'"}
-    
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        m = re.match('^([-\d/\+]{5,}) \((.+)\)', value)
-        if m is None: raise formencode.Invalid(self.message('infoFormat', state) % {'value': value}, value, state)
-        (phone, info) = m.groups()
-        
-        # check phone
-        phone = AustrianPhoneNumber().to_python(phone)
-        
-        return "%s (%s)" % (phone, info)
-
-
-class ValueComment(formencode.FancyValidator):
-    """Converts value with a potentially optional comment to a python tuple:
-        u''                <=> (None, None)
-        u'value'           <=> (u'value', None)
-        u'value (comment)' <=> (u'value', u'comment')"""
-    
-    def __init__(self, comment_is_optional=True):
-        self.comment_is_optional = comment_is_optional
-    
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        if value == u'': return None
-        left = value.find('(')
-        right = value.rfind(')')
-        if left < 0 and right < 0:
-            if not comment_is_optional: raise formencode.Invalid(u'Mandatory comment not present', value, state)
-            return value, None
-        elif left >= 0 and right >= 0 and left < right:
-            return value[:left].strip(), value[left+1:right].strip()
-        else: raise formencode.Invalid(u'Invalid format', value, state)
-
-    def from_python(self, value):
-        assert len(value) == 2
-        if value[0] is None:
-            assert value[1] == None
-            return u''
-        if value[1] is None:
-            return value[0]
-        return u'%s (%s)' % (value[0], value[1])
+class AustrianPhoneNumberNone(NoneValidator):
+    def __init__(self):
+        NoneValidator.__init__(self, AustrianPhoneNumber())
 
 
-class ValueCommentList(formencode.FancyValidator):
-    """A value-comment list looks like one of the following lines:
-        value
-        value (optional comment)
-        value1; value2
-        value1; value2 (optional comment)
-        value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
-        value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
-    This function returns the value-comment list as list of tuples:
-        [(u'value1', u'comment1'), (u'value2', None)]
-    If no comment is present, None is specified.
-    For an empty string, None is returned."""
-    messages = {'infoFormat': "'%%(value)s' is no valid format, please use a form like 'value1 (optional comment1); value2 (optional comment2)'"}
-    
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        value_options = [s.strip() for s in value.split(';')]
-        result = []
-        for value_option in value_options:
-            left = value_option.find('(')
-            right = value_option.rfind(')')
-            if left < 0 and right < 0:
-                result.append((value_option, None))
-            elif left >= 0 and right >= 0 and left < right:
-                result.append((value_option[:left].strip(), value_option[left+1:right].strip()))
-            else: raise formencode.Invalid(self.message('infoFormat', state) % {'value': value}, value, state)
-        return result
-
-    def _from_python(self, value, state):
-        result = []
-        for v, c in value:
-            if c is None: result.append(v)
-            else: result.append('%s (%s)' % (v, c))
-        return "; ".join(result)
+# Deprecated
+class AustrianPhoneNumberCommentLoop(NoneValidator):
+    def __init__(self):
+        NoneValidator.__init__(self, Loop(ValueComment(AustrianPhoneNumber())))
 
 
-class GermanDifficulty(formencode.FancyValidator):
+class GermanDifficulty(DictValidator):
     """Converts the difficulty represented in a number from 1 to 3 (or None)
     to a German representation:
     u''       <=> None
     u'leicht' <=> 1
     u'mittel' <=> 2
     u'schwer' <=> 3"""
-    dc = formencode.validators.DictConverter({u'leicht': 1, u'mittel': 2, u'schwer': 3})
-    
-    def _to_python(self, value, state):
-        """Value has to be one of u'', u'leicht', u'mittel' or u'schwer':
-        u''       => None
-        u'leicht' => 1
-        u'mittel' => 2
-        u'schwer' => 3"""
-        self.assert_string(value, state)
-        return self.dc.to_python(value)
-    
-    def from_python(self, value):
-        """value has to be one of None, 1, 2, 3:
-        None => u''
-        1    => u'leicht'
-        2    => u'mittel'
-        3    => u'schwer'"""
-        if value is None: return u''
-        return self.dc.from_python(value)
+    def __init__(self):
+        DictValidator.__init__(self, {u'': None, u'leicht': 1, u'mittel': 2, u'schwer': 3})
 
 
-class GermanAvalanches(formencode.FancyValidator):
+class GermanAvalanches(DictValidator):
     """Converts the avalanches property represented as number from 1 to 4 (or None)
     to a German representation:
     u''             <=> None
@@ -373,30 +470,11 @@ class GermanAvalanches(formencode.FancyValidator):
     u'selten'       <=> 2
     u'gelegentlich' <=> 3
     u'häufig'       <=> 4"""
-    dc = formencode.validators.DictConverter({u'kaum': 1, u'selten': 2, u'gelegentlich': 3, u'häufig': 4})
-    
-    def _to_python(self, value, state):
-        """value has to be one of u'', u'kaum', u'selten', u'gelegentlich', u'häufig':
-        u''             => None
-        u'kaum'         => 1
-        u'selten'       => 2
-        u'gelegentlich' => 3
-        u'häufig'       => 4"""
-        self.assert_string(value, state)
-        return self.dc.to_python(value)
-    
-    def from_python(self, value):
-        """value has to be one of None, 1, 2, 3, 4:
-        None => u''
-        1    => u'kaum'
-        2    => u'selten'
-        3    => u'gelegentlich'
-        4    => u'häufig'"""
-        if value is None: return u''
-        return self.dc.from_python(value)
+    def __init__(self):
+        DictValidator.__init__(self, {u'': None, u'kaum': 1, u'selten': 2, u'gelegentlich': 3, u'häufig': 4})
 
 
-class GermanPublicTransport(formencode.FancyValidator):
+class GermanPublicTransport(DictValidator):
     """Converts the public_transport property represented as number from 1 to 6 (or None)
     to a German representation:
     u''            <=> None
@@ -406,84 +484,208 @@ class GermanPublicTransport(formencode.FancyValidator):
     u'Schlecht'    <=> 4
     u'Nein'        <=> 5
     u'Ja'          <=> 6"""
-    dc = formencode.validators.DictConverter({u'Sehr gut': 1, u'Gut': 2, u'Mittelmäßig': 3, u'Schlecht': 4, u'Nein': 5, u'Ja': 6})
-
-    def _to_python(self, value, state):
-        """value has to be one of u'', u'Sehr gut', u'Gut', u'Mittelmäßig', u'Schlecht', u'Nein', u'Ja':
-        u''            => None
-        u'Sehr gut'    => 1
-        u'Gut'         => 2
-        u'Mittelmäßig' => 3
-        u'Schlecht'    => 4
-        u'Nein'        => 5
-        u'Ja'          => 6"""
-        self.assert_string(value, state)
-        return self.dc.to_python(value)
-    
-    def from_python(self, value):
-        """value has to be one of None, 1, 2, 3, 4, 5, 6:
-        None => u''            
-        1    => u'Sehr gut'
-        2    => u'Gut'
-        3    => u'Mittelmäßig'
-        4    => u'Schlecht'
-        5    => u'Nein'
-        6    => u'Ja'"""
-        if value is None: return u''
-        return self.dc.from_python(value)
-
-
-class GermanTristateFloat(formencode.FancyValidator):
-    """Converts the a property with the possible values 0.0, 0.5, 1.0 or None
-    to a German text:
-    u''          <=> None
-    u'Ja'        <=> 1.0
-    u'Teilweise' <=> 0.5
-    u'Nein'      <=> 0.0"""
-    dc = formencode.validators.DictConverter({u'Ja': 1.0, u'Teilweise': 0.5, u'Nein': 0.0})
-
-    def _to_python(self, value, state):
-        """value has to be one of u'', u'Ja', u'Teilweise', u'Nein':
-        u''          => None
-        u'Ja'        => 1.0
-        u'Teilweise' => 0.5
-        u'Nein'      => 0.0"""
-        self.assert_string(value, state)
-        return self.dc.to_python(value)
-    
-    def from_python(self, value):
-        """value has to be one of None, 0.0, 0.5, 1.0:
-        None => u''
-        0.0    => u'Nein'
-        0.5    => u'Teilweise'
-        1.0    => u'Ja'"""
-        if value is None: return u''
-        return self.dc.from_python(value)
+    def __init__(self):
+        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})
 
 
-class GermanTristateFloatComment(formencode.FancyValidator):
+class GermanTristateFloatComment(ValueComment):
     """Converts the a property with the possible values 0.0, 0.5, 1.0 or None and an optional comment
     in parenthesis to a German text:
-    u''                  <=> (None, None
-    u'Ja'                <=> (1.0,  None
-    u'Teilweise'         <=> (0.5,  None
-    u'Nein'              <=> (0.0,  None
+    u''                  <=> (None, None)
+    u'Ja'                <=> (1.0,  None)
+    u'Teilweise'         <=> (0.5,  None)
+    u'Nein'              <=> (0.0,  None)
     u'Ja (aber schmal)'  <=> (1.0,  u'aber schmal')
     u'Teilweise (oben)'  <=> (0.5,  u'oben')
     u'Nein (aber breit)' <=> (0.0,  u'aber breit')
     """
-    tf = GermanTristateFloat()
+    def __init__(self):
+        ValueComment.__init__(self, GermanTristateFloat())
+
+
+class UnsignedCommentNone(NoneValidator):
+    """Converts the a property with unsigned values an optional comment
+    in parenthesis to a text:
+    u''           <=> (None, None)
+    u'2 (Mo, Di)' <=> (2,  u'Mo, Di')
+    u'7'          <=> (7,  None)
+    u'0'          <=> (0,  None)
+    """
+    def __init__(self, max=None):
+        NoneValidator.__init__(self, ValueComment(Unsigned(max=max)), (None, None))
+
+
+class GermanCachet(formencode.FancyValidator):
+    """Converts a "Gütesiegel":
+    u'' <=> None
+    u'Nein' <=> 'Nein'
+    u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' <=> u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'"""
+    def to_python(self, value):
+        self.assert_string(value, None)
+        if value == u'': return None
+        elif value == u'Nein': return value
+        elif value.startswith(u'Tiroler Naturrodelbahn-Gütesiegel '):
+            p = value.split(" ")
+            Unsigned().to_python(p[2]) # check if year can be parsed
+            if not p[3] in ['leicht', 'mittel', 'schwer']: raise formencode.Invalid("Unbekannter Schwierigkeitsgrad", value, None)
+            return value
+        else: raise formencode.Invalid("Unbekanntes Gütesiegel", value, None)
+    
+    def from_python(self, value):
+        if value == None: return u''
+        assert value != u''
+        return self.to_python(self, value)
+
+
+class Url(formencode.FancyValidator):
+    """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed."""
+    urlv = formencode.validators.URL()    
+    def to_python(self, value):
+        self.assert_string(value, None)
+        v = value
+        v = v.replace(u'ä', u'a')
+        v = v.replace(u'ö', u'o')
+        v = v.replace(u'ü', u'u')
+        v = v.replace(u'ß', u'ss')
+        v = self.urlv.to_python(v)
+        return value
+    
+    def from_python(self, value):
+        return value
+
+
+class UrlNeinNone(NoneValidator):
+    """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.
+    The special value u"Nein" is allowed."""
+    def __init__(self):
+        NoneValidator.__init__(self, NeinValidator(Url()))
+
+
+class ValueCommentListNeinLoopNone(NoneValidator):
+    """Translates a semicolon separated list of values with optional comments in paranthesis or u'Nein' to itself.
+    An empty string is translated to None:
+    u''                   <=> None
+    u'Nein'               <=> u'Nein'
+    u'T-Mobile (gut); A1' <=> u'T-Mobile (gut); A1'"""
+    def __init__(self):
+        NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList())))
+
+
+class PhoneCommentListNeinLoopNone(NoneValidator):
+    """List with semicolon-separated phone numbers in international format with optional comment or 'Nein' as string:
+    u''                                                       <=> None
+    u'Nein'                                                   <=> u'Nein'
+    u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' <=> u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
+    """
+    def __init__(self, comments_are_optional):
+        NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(formencode.national.InternationalPhoneNumber(default_cc=lambda: 43), comments_are_optional=comments_are_optional))))
+
+
+class EmailCommentListNeinLoopNone(NoneValidator):
+    """Converts a semicolon-separated list of email addresses with optional comments to itself.
+    The special value of u'Nein' indicates that there are no email addresses.
+    The empty string translates to None:
+    u''                                                   <=> None
+    u'Nein'                                               <=> u'Nein'
+    u'first@example.com'                                  <=> u'first@example.com'
+    u'first@example.com (Nur Winter); second@example.com' <=> u'first@example.com (Nur Winter); second@example.com'
+    """
+    def __init__(self):
+        NoneValidator.__init__(self, NeinValidator(Loop(ValueCommentList(formencode.validators.Email()))))
+
 
-    def _to_python(self, value, state):
-        self.assert_string(value, state)
-        return self.dc.to_python(value)
+class WikiPage(formencode.FancyValidator):
+    """Validates wiki page name like u'[[Birgitzer Alm]]'.
+    The page is not checked for existance.
+    An empty string is an error.
+    u'[[Birgitzer Alm]]' <=> u'[[Birgitzer Alm]]'
+    """
+    def to_python(self, value):
+        self.assert_string(value, None)
+        if not value.startswith('[[') or not value.endswith(']]'): 
+            raise formencode.Invalid('No valid wiki page name', value, None)
+        return value
     
     def from_python(self, value):
-        """value has to be one of None, 0.0, 0.5, 1.0:
-        None => u''
-        0.0    => u'Nein'
-        0.5    => u'Teilweise'
-        1.0    => u'Ja'"""
-        if value is None: return u''
-        return self.dc.from_python(value)
+        return value
+
+
+class WikiPageList(SemicolonList):
+    """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]'.
+    u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> [u'[[Birgitzer Alm]]', u'[[Kemater Alm]]']
+    u'[[Birgitzer Alm]]'                  <=> [u'[[Birgitzer Alm]]']
+    u''                                   <=> []
+    """
+    def __init__(self):
+        SemicolonList.__init__(self, WikiPage())
+
+
+class WikiPageListLoopNone(NoneValidator):
+    """Validates a list of wiki pages like u'[[Birgitzer Alm]]; [[Kemater Alm]]' as string.
+    u'[[Birgitzer Alm]]; [[Kemater Alm]]' <=> u'[[Birgitzer Alm]]; [[Kemater Alm]]'
+    u'[[Birgitzer Alm]]'                  <=> u'[[Birgitzer Alm]]'
+    u''                                   <=> None
+    """
+    def __init__(self):
+        NoneValidator.__init__(self, Loop(WikiPageList()))
+
+
+class TupleSecondValidator(formencode.FancyValidator):
+    """Does not really validate anything but puts the string through
+    a validator in the second part of a tuple.
+    Examples with an Unsigned() validator and the True argument:
+    u'6' <=> (True, 6)
+    u'2' <=> (True, 2)"""
+    def __init__(self, first=True, validator=UnicodeNone()):
+        self.first = first
+        self.validator = validator
+    
+    def to_python(self, value):
+        self.assert_string(value, None)
+        return self.first, self.validator.to_python(value)
+    
+    def from_python(self, value):
+        assert value[0] == self.first
+        return self.validator.from_python(value[1])
+
+
+class BoolUnicodeTupleValidator(NoneValidator):
+    """Translates an unparsed string or u'Nein' to a tuple:
+    u''         <=> (None, None)
+    u'Nein'     <=> (False, None)
+    u'any text' <=> (True, u'any text')
+    """
+    def __init__(self, validator=UnicodeNone()):
+        NoneValidator.__init__(self, NeinValidator(TupleSecondValidator(True, validator), (False, None)), (None, None))
+
+
+class GermanLift(BoolUnicodeTupleValidator):
+    """Checks a lift_details property. It is a value comment property with the following
+    values allowed:
+    u'Sessellift'
+    u'Gondel'
+    u'Linienbus'
+    u'Taxi'
+    u'Sonstige'
+    Alternatively, the value u'Nein' is allowed.
+    An empty string maps to (None, None).
+    
+    Examples:
+    u''                                       <=> (None, None)
+    u'Nein'                                   <=> (False, None)
+    u'Sessellift                              <=> (True, u'Sessellift')
+    u'Gondel (nur bis zur Hälfte)'            <=> (True, u'Gondel (nur bis zur Hälfte)')
+    u'Sessellift; Taxi'                       <=> (True, u'Sessellift; Taxi')
+    u'Sessellift (Wochenende); Taxi (6 Euro)' <=> (True, u'Sessellift (Wochenende); Taxi (6 Euro)')
+    """
+    def __init__(self):
+        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'}))))
+        
 
+class SledRental(BoolUnicodeTupleValidator):
+    """The value can be an empty string, u'Nein' or a comma-separated list of unicode strings with optional comments.
+    u''                                       <=> (None, None)
+    u'Nein'                                   <=> (False, None)
+    u'Talstation (nur mit Ticket); Schneealm' <=> (True, u'Talstation (nur mit Ticket); Schneealm')"""
+    def __init__(self):
+        BoolUnicodeTupleValidator.__init__(self, Loop(ValueCommentList()))
index df55e02faad68d1bb6f7166a48487562a1bed848..fabf193696752f36183bce93335f36c8a8ed55ae 100644 (file)
@@ -2,14 +2,14 @@
 # -*- coding: iso-8859-15 -*-
 # $Id$
 import wradmin.lib
+import wradmin.lib.mediawiki
 import wradmin.model
 
 
 def test_wikipage_to_wrsleddingcache1_2():
-    wiki_page = wradmin.model.Page()
-    wiki_page.page_id = 7
-    wiki_page.page_title = u"Kemater Alm"
-    wiki_page.old_text = u"""
+    page_id = 7
+    page_title = u"Kemater Alm"
+    old_text = u"""
 Text above
 {{Rodelbahnbox
 | Bild                 = Rodelbahn_Mitterer_Alm_04.jpg
@@ -28,7 +28,7 @@ Text above
 | In Übersichtskarte   = Ja
 }}
 Text below"""
-    sledding_cache = wradmin.lib.mediawiki.wikipage_to_wrsleddingcache1_2(wiki_page)
+    sledding_cache = wradmin.lib.mediawiki.wikipage_to_wrsleddingcache1_2(page_id, page_title, old_text)
     assert sledding_cache.page_id == 7
     assert sledding_cache.page_title == u"Kemater Alm"
     assert sledding_cache.length == 3500
@@ -50,16 +50,15 @@ Text below"""
 
 
 def test_wikipage_to_wrsleddingcache():
-    wiki_page = wradmin.model.Page()
-    wiki_page.page_id = 7
-    wiki_page.page_title = u"Kemater Alm"
-    wiki_page.old_text = u"""
+    page_id = 7
+    page_title = u"Kemater Alm"
+    old_text = u"""
 Text above
 {{Rodelbahnbox
 | Position             = 47.203959 N 11.308052 E
 | Position oben        = 
 | Höhe oben            = 1700
-| Position unten       = 
+| Position unten       = 47.200959 N 11.309052 E
 | Höhe unten           = 1200
 | Länge                = 3500
 | Schwierigkeit        = mittel
@@ -71,7 +70,7 @@ Text above
 | Aufstiegshilfe       = Gondel (unterer Teil)
 | Beleuchtungsanlage   = Ja
 | Beleuchtungstage     = 3 (Montag, Mittwoch, Freitag)
-| Rodelverleih         = Ja (Talstation Serlesbahnan)
+| Rodelverleih         = Talstation Serlesbahnan
 | Gütesiegel           = Tiroler Naturrodelbahn-Gütesiegel 2009 mittel
 | Webauskunft          = http://www.nösslachhütte.at/page9.php
 | Telefonauskunft      = +43-664-5487520 (Mitterer Alm)
@@ -80,39 +79,85 @@ Text above
 | Forumid              = 33
 }}
 Text below"""
-    sledding_cache = wradmin.lib.mediawiki.wikipage_to_wrsleddingcache(wiki_page)
+    sledding_cache = wradmin.lib.mediawiki.wikipage_to_wrsleddingcache(page_id, page_title, old_text)
     assert sledding_cache.page_id == 7
     assert sledding_cache.page_title == u"Kemater Alm"
-
     assert sledding_cache.position_latitude == 47.203959
     assert sledding_cache.position_longitude == 11.308052
-    assert sledding_cache.top_latitude == 47.203959
-    assert sledding_cache.top_longitude == 11.308052
-    assert sledding_cache.top_elevation == 2000
-    assert sledding_cache.bottom_latitude == 47.203959
-    assert sledding_cache.bottom_longitude == 11.308052
+    assert sledding_cache.top_latitude == None
+    assert sledding_cache.top_longitude == None
+    assert sledding_cache.top_elevation == 1700
+    assert sledding_cache.bottom_latitude == 47.200959
+    assert sledding_cache.bottom_longitude == 11.309052
     assert sledding_cache.bottom_elevation == 1200
-
     assert sledding_cache.length == 3500
-    assert sledding_cache.walktime == 90
+    assert sledding_cache.difficulty == 2
+    assert sledding_cache.avalanches == 1
+    assert sledding_cache.operator == u'Max Mustermann'
+    assert sledding_cache.public_transport == 3
+    assert sledding_cache.walkup_time == 90
     assert sledding_cache.walkup_separate == 1.0
-    sa.Column("walkup_separate_comment", types.Unicode(255)),    
-    assert sledding_cache.lift == False
-    sa.Column("lift_details", types.Unicode(255)),
+    assert sledding_cache.walkup_separate_comment == None
+    assert sledding_cache.lift == True
+    assert sledding_cache.lift_details == u'Gondel (unterer Teil)'
     assert sledding_cache.night_light == 1.0
-    sa.Column("night_light_days", types.Integer),
-    sa.Column("night_light_days_comment", types.Unicode(255)),
+    assert sledding_cache.night_light_comment == None
+    assert sledding_cache.night_light_days == 3
+    assert sledding_cache.night_light_days_comment == u'Montag, Mittwoch, Freitag'
     assert sledding_cache.sled_rental == True
-    assert sledding_cache.public_transport == 3
-    assert sledding_cache.avalanche = 1
+    assert sledding_cache.sled_rental_comment == u'Talstation Serlesbahnan'
+    assert sledding_cache.cachet == u'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel'
+    assert sledding_cache.information_web == u'http://www.nösslachhütte.at/page9.php'
+    assert sledding_cache.information_phone == u'+43-664-5487520 (Mitterer Alm)'
     assert sledding_cache.image == u"Rodelbahn_Mitterer_Alm_04.jpg"
-    assert sledding_cache.information == u"+43/664/5487520 (Mitterer Alm)"
-    assert sledding_cache.forum_threadid = 33
     assert sledding_cache.show_in_overview == True
+    assert sledding_cache.forum_id == 33
+    assert sledding_cache.under_construction == None
 
-    # assert sledding_cache.under_construction
+
+def test_wikipage_to_wrinncache1_2():
+    page_id = 10
+    page_title = u"Kemater Alm (Gasthaus)"
+    old_text = u"""
+Text above
+{{Gasthausbox
+| Bild                 = Rodelbahn_Birgitzer_Alm_01.jpg
+| Position             = 47.123456 N 11.123456 E
+| Höhe                 = 1808
+| Telefon (Festnetz)   = 
+| Telefon (Mobil)      = +43/664/5487520
+| Homepage             = http://www.birgitzeralm.at/
+| E-Mail               = office@example.com
+| Rauchfrei            = Ja
+}}
+Text below"""
+    inn_cache = wradmin.lib.mediawiki.wikipage_to_wrinncache1_2(page_id, page_title, old_text)
+
+
+def test_wikipage_to_wrinncache():
+    page_id = 10
+    page_title = u"Kemater Alm (Gasthaus)"
+    old_text = u"""
+Text above
+{{Gasthausbox
+| Position          = 47.123456 N 11.123456 E
+| Höhe              = 1808
+| Betreiber         = Max Mustermann
+| Sitzplätze        = 50
+| Übernachtung      = 20 Matrazenlager, 3 Doppelzimmer
+| Rauchfrei         = Ja
+| Rodelverleih      = 2 Euro (Ausweis erforderlich, Reservierung erwünscht)
+| Handyempfang      = A1; T-Mobile A
+| Homepage          = http://www.birgitzeralm.at/
+| E-Mail            = Nein
+| Telefon           = +43-664-5487520 (Birgitzer Alm); +43-512-123456 (wenn geschlossen)
+| Bild              = Rodelbahn_Birgitzer_Alm_01.jpg
+| Rodelbahnen       = [[Kemater Alm]]; [[Birgitzer Alm]]
+}}
+Text below"""
+    inn_cache = wradmin.lib.mediawiki.wikipage_to_wrinncache(page_id, page_title, old_text)
 
 
-def test_mediawiki_users():
+def _test_mediawiki_users():
     users = wradmin.lib.mediawiki.MediaWikiUsers(True)
     assert len(users.usernames) >= 1 # We have at least one user
\ No newline at end of file
index 96ca739969cfef4ed8dececa4c3720b582890a26..35fbbce02bbb30ae533487089db6bebce9d688a8 100644 (file)
@@ -1,43 +1,25 @@
+# -*- coding: iso-8859-15 -*-
 import wradmin.model.validators
 import formencode
 
-def test_bool_validator():
-    v =  wradmin.model.validators.GermanBool()
-    assert v.to_python(u'Ja') == True
-    assert v.to_python(u'Nein') == False
-    assert v.to_python(None) == None
+
+def test_NoneValidator():
+    v =  wradmin.model.validators.NoneValidator(wradmin.model.validators.Unicode())
     assert v.to_python(u'') == None
-    try:
-        v.to_python(u'Wrong')
-        assert True, u"The value 'Wrong' must not be accepted by the validator."
-    except formencode.Invalid: pass
-    
-    assert v.from_python(True) == u'Ja'
-    assert v.from_python(False) == u'Nein'
     assert v.from_python(None) == u''
 
 
-def test_GermanTristate_validator():
-    v = wradmin.model.validators.GermanTristate()
-    assert v.to_python(u'Ja') == (True, False)
-    assert v.to_python(u'Nein') == (False, True)
-    assert v.to_python(u'Teilweise') == (True,  True)
-    assert v.to_python(u'') == (None, None)
-    assert v.to_python(None) == (None, None)
-    try:
-        v.to_python(u'Wrong')
-        assert False, u"The value 'Wrong' must not be accepted by the validator."
-    except formencode.Invalid: pass
-
-    assert v.from_python((True, False)) == u'Ja'
-    assert v.from_python((False, True)) == u'Nein'
-    assert v.from_python((True, True)) == u'Teilweise'
-    assert v.from_python((None, None)) == u''
+def test_UnsignedNone():
+    v = wradmin.model.validators.UnsignedNone()
+    assert v.to_python(u'42') == 42
+    assert v.to_python(u'') == None
+    assert v.from_python(42) == u'42'
+    assert v.from_python(None) == u''
 
 
-def test_geo():
+def test_GeoNone():
     coord = u'47.076207 N 11.453553 E'
-    v = wradmin.model.validators.Geo()
+    v = wradmin.model.validators.GeoNone()
     (lat, lon) = v.to_python(coord)
     assert lat == 47.076207
     assert lon == 11.453553
@@ -47,38 +29,118 @@ def test_geo():
     assert v.from_python((None, None)) == u''
 
 
-def test_AustrianPhoneNumber():
-    v = wradmin.model.validators.AustrianPhoneNumber()
-    assert v.to_python(u'') is None
-    assert v.to_python(u'0512/12345678') == u'+43/512/12345678'
-    assert v.to_python(u'+43/512/12345678') == u'+43/512/12345678'
-    assert v.to_python(u'0512/1234567-89') == u'+43/512/1234567-89'
-    assert v.to_python(u'+43/512/1234567-89') == u'+43/512/1234567-89'
-    for n in [u'0512 / 12345678', u'0512-12345678', u'0049(0)8386/8113']:
-        try:
-            v.to_python(n) # has to throw an exception
-            assert False, u"The telephone number '%s' should throw an exception." % n
-        except formencode.Invalid: pass
-
-
-def test_PhoneInfo():
-    v = wradmin.model.validators.PhoneInfo()
-    assert v.to_python(u'') is None
-    assert v.to_python(u'0512/12345678 (Schnee Alm)') == u'+43/512/12345678 (Schnee Alm)'
-    assert v.to_python(u'+43/512/12345678 (Schnee Alm)') == u'+43/512/12345678 (Schnee Alm)'
-    assert v.to_python(u'0512/12345678 (Schnee (Winter) Alm)') == u'+43/512/12345678 (Schnee (Winter) Alm)'
-    for n in [u'0512/12345678', u'+43/512/12345678', u'+43 (Schnee Alm)', u'0512/12345678 ()', u'(Schnee Alm)', u'(Schnee Alm) +43/512/12345678', u'+43/512/12345678 Schnee Alm', u'(Schnee Alm)  +43/512/12345678']:
-        try:
-            v.to_python(n) # has to throw an exception
-            assert False, u"The telephone info '%s' should throw an exception." % n
-        except formencode.Invalid: pass        
+def test_GermanTristateTuple():
+    v = wradmin.model.validators.GermanTristateTuple()
+    assert v.to_python(u'') == (None, None)
+    assert v.to_python(u'Ja') == (True, False)
+    assert v.to_python(u'Nein') == (False, True)
+    assert v.to_python(u'Teilweise') == (True, True)
+    assert v.from_python((None, None)) == u''
+    assert v.from_python((False, True)) == u'Nein'
+    assert v.from_python((True, False)) == u'Ja'
+    assert v.from_python((True, True)) == u'Teilweise'
+
+
+def tes_GermanTristateFloat():
+    v = wradmin.model.validators.GermanTristateFloat()
+    assert v.to_python(u'') == None
+    assert v.to_python(u'Ja') == 1.0
+    assert v.to_python(u'Nein') == 0.0
+    assert v.to_python(u'Teilweise') == 0.5
+    assert v.from_python(None) == u''
+    assert v.from_python(0.0) == u'Nein'
+    assert v.from_python(1.0) == u'Ja'
+    assert v.from_python(0.5) == u'Teilweise'
+
 
 def test_ValueCommentList():
     v = wradmin.model.validators.ValueCommentList()
-    assert v.to_python('abc') == [('abc', None)]
+    assert v.to_python(u'abc') == [(u'abc', None)]
     assert v.to_python(u'abc def') == [(u'abc def', None)]
-    assert v.to_python('value (comment)') == [('value', 'comment')]
     assert v.to_python(u'value (comment)') == [(u'value', u'comment')]
-    assert v.to_python('value1 (comment); value2') == [('value1', 'comment'), ('value2', None)]
-    assert v.to_python('value1 (comment1); value2; value3 (comment3)') == [('value1', 'comment1'), ('value2', None), ('value3', 'comment3')]
-    assert v.to_python('value1 (comment1); value2 (test (not easy))') == [('value1', 'comment1'), ('value2', 'test (not easy)')]
\ No newline at end of file
+    assert v.to_python(u'value (comment)') == [(u'value', u'comment')]
+    assert v.to_python(u'value1 (comment); value2') == [(u'value1', u'comment'), (u'value2', None)]
+    assert v.to_python(u'value1 (comment1); value2; value3 (comment3)') == [(u'value1', u'comment1'), (u'value2', None), ('value3', 'comment3')]
+    assert v.to_python(u'value1 (comment1); value2 (test (not easy))') == [(u'value1', u'comment1'), (u'value2', u'test (not easy)')]
+
+
+def test_UrlNeinNone():
+    v = wradmin.model.validators.UrlNeinNone()
+    assert v.to_python(u'') == None
+    assert v.to_python(u'Nein') == u'Nein'
+    assert v.to_python(u'http://www.höttingeralm.at') == u'http://www.höttingeralm.at'
+    assert v.from_python(None) == u''
+    assert v.from_python(u'Nein') == u'Nein'
+    assert v.from_python(u'http://www.höttingeralm.at') == u'http://www.höttingeralm.at'
+
+
+def test_ValueCommentListNeinLoopNone():
+    v = wradmin.model.validators.ValueCommentListNeinLoopNone()
+    assert v.to_python(u'') == None
+    assert v.to_python(u'Nein') == u'Nein'
+    assert v.to_python(u'T-Mobile (gut); A1') == u'T-Mobile (gut); A1'
+    assert v.from_python(None) == u''
+    assert v.from_python(u'Nein') == u'Nein'
+    assert v.from_python(u'T-Mobile (gut); A1') == u'T-Mobile (gut); A1'
+    
+
+def test_PhoneCommentListNeinLoopNone():
+    v = wradmin.model.validators.PhoneCommentListNeinLoopNone(comments_are_optional=True)
+    assert v.to_python(u'') == None
+    assert v.to_python(u'Nein') == u'Nein'
+    assert v.to_python(u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456') == u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
+    assert v.from_python(None) == u''
+    assert v.from_python(u'Nein') == u'Nein'
+    assert v.from_python(u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456') == u'+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456'
+
+
+def test_EmailCommentListNeinLoopNone():
+    v = wradmin.model.validators.EmailCommentListNeinLoopNone()
+    assert v.to_python(u'') == None
+    assert v.to_python(u'Nein') == u'Nein'
+    assert v.to_python(u'first@example.com') == u'first@example.com'
+    assert v.to_python(u'first@example.com (Nur Winter); second@example.com') == u'first@example.com (Nur Winter); second@example.com'
+    assert v.from_python(None) == u''
+    assert v.from_python(u'Nein') == u'Nein'
+    assert v.from_python(u'first@example.com') == u'first@example.com'
+    assert v.from_python(u'first@example.com (Nur Winter); second@example.com') == u'first@example.com (Nur Winter); second@example.com'
+
+
+def test_GermanLift():
+    v = wradmin.model.validators.GermanLift()
+    assert v.to_python(u'') == (None, None)
+    assert v.to_python(u'Nein') == (False, None)
+    assert v.to_python(u'Sessellift (4 Euro)') == (True, u'Sessellift (4 Euro)')
+    assert v.from_python((None, None)) == u''
+    assert v.from_python((False, None)) == u'Nein'
+    assert v.from_python((True, u'Sessellift (4 Euro)')) == u'Sessellift (4 Euro)'
+
+
+def test_WikiPageListLoopNone():
+    v = wradmin.model.validators.WikiPageListLoopNone()
+    assert v.to_python(u'') == None
+    assert v.to_python(u'[[Birgitzer Alm]]; [[Kemater Alm]]') == u'[[Birgitzer Alm]]; [[Kemater Alm]]'
+    assert v.from_python(None) == u''
+    assert v.from_python(u'[[Birgitzer Alm]]; [[Kemater Alm]]') == u'[[Birgitzer Alm]]; [[Kemater Alm]]'
+
+
+def test_BoolUnicodeTupleValidator():
+    v = wradmin.model.validators.BoolUnicodeTupleValidator()
+    assert v.to_python(u'') == (None, None)
+    assert v.to_python(u'Nein') == (False, None)
+    assert v.to_python(u'any text') == (True, u'any text')
+    assert v.from_python((None, None)) == u''
+    assert v.from_python((False, None)) == u'Nein'
+    assert v.from_python((True, u'any text')) == u'any text'
+
+
+def test_SledRental():
+    v = wradmin.model.validators.SledRental()
+    assert v.to_python(u'') == (None, None)
+    assert v.to_python(u'Nein') == (False, None)
+    assert v.to_python(u'Ja') == (True, u'Ja')
+    assert v.to_python(u'Talstation (nur mit Ticket); Schneealm') == (True, u'Talstation (nur mit Ticket); Schneealm')
+    assert v.from_python((None, None)) == u''
+    assert v.from_python((False, None)) == u'Nein'
+    assert v.from_python((True, u'Talstation (nur mit Ticket); Schneealm')) == u'Talstation (nur mit Ticket); Schneealm'
+    assert v.from_python((True, u'Ja')) == u'Ja'