]> ToastFreeware Gitweb - philipp/winterrodeln/wradmin.git/blob - wradmin/wradmin/lib/mediawiki.py
ea05d27ca6468404e4814eb5d822dcb27ef64a49
[philipp/winterrodeln/wradmin.git] / wradmin / wradmin / lib / mediawiki.py
1 #!/usr/bin/python2.5
2 # -*- coding: iso-8859-15 -*-
3 # $Id$
4 "MediaWiki communication functions"
5 import datetime
6 import re
7
8 from authkit.users import UsersReadOnly, md5
9 import formencode, formencode.national
10 import logging
11 log = logging.getLogger(__name__)
12
13 import wradmin.model as model
14 import wradmin.model.validators
15
16
17 # Converter functions
18 # -------------------
19
20 def to_bool(value):
21     return model.validators.GermanBool().to_python(value)
22
23
24 def to_unsigned(value):
25     "Requires a positive number"
26     return formencode.validators.Int(min=0).to_python(value)
27
28
29 def to_date(value):
30     "Parses a date in the form 'yyy-mm-dd'"
31     return model.validators.DateConverter().to_python(value)
32
33
34 def to_geo(value):
35     "Formats to coordinates '47.076207 N 11.453553 E' to the (latitude, longitude) tuplet."
36     return model.validators.Geo().to_python(value)
37
38
39 def to_title(value):
40     """Line 2237 of includes/Title.php says: $this->mTextform = str_replace( '_', ' ', $dbkey );
41     No not check for None because a missing title is an error"""
42     return value.replace(u'_', u' ')
43
44
45 def to_tristate(value):
46     """Does the following conversion:
47     None         -> (None, None)
48     u'Ja'        -> (True, False)
49     u'Teilweise' -> (True,  True)
50     u'Nein'      -> (False, True)"""
51     return model.validators.GermanTristate().to_python(value)
52
53
54 def to_email(value):
55     return formencode.validators.Email().to_python(value)
56
57
58 def to_url(value):
59     return formencode.validators.URL().to_python(value)
60
61
62 def to_phone(value):
63     return model.validators.AustrianPhoneNumber(messages={'phoneFormat': u"Telefonnummer %%(value)s muss das Format 0123/456789 oder +43/123/456789 haben"}).to_python(value)
64
65
66 def to_phone_info(value):
67     return model.validators.PhoneInfo(messages={'phoneInfo': u"Bitte verwenden Sie ein Format wie '0512/123456 (Schnee Alm)'."}).to_python(value)
68
69
70 def to_valuecommentlist(value):
71     """A value-comment list looks like one of the following lines:
72         value
73         value (optional comment)
74         value1; value2
75         value1; value2 (optional comment)
76         value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
77         value1 (optional comment1); value2 (optional comment2); value3 (otional comment3)
78     This function returns the value-comment list as list of tuples. If no comment is present, None is specified."""
79     return model.validators.ValueCommentList().to_python(value)
80
81
82 def conv(fnct, value, fieldname):
83     "Like one of the to_xxx functions (e.g. to_bool), but adds the field name to the error message"
84     try: return fnct(value)
85     except formencode.Invalid, e: raise formencode.Invalid(_(u"Conversion error in field '%s': %s") % (fieldname, unicode_e(e)), e.value, e.state)
86
87
88 def unicode_e(exception):
89     """Does "unicode(exception)" as it should be. This is a workaround for bug http://bugs.python.org/issue2517
90     that is not fixed in python 2.5.2.
91     Details of bug: "unicode(Exception(u'\xe4'))" raises an UnicodeEncodeError exception."""
92     if exception.message: return unicode(exception.message)
93     return unicode(exception)
94
95
96 def wikipage_to_wrsleddingcache1_2(wiki_page):
97     """Converts a sledding route wiki page (wradmin.model.page_table) 
98     to a sledding route wrsleddingcache database record (version 1.2) (wradmin.model.wrsleddingcache1_2_table)."""
99     sl = model.WrSleddingCache1_2()
100     sl.page_id = wiki_page.page_id
101     sl.page_title = to_title(wiki_page.page_title)
102     
103     # Match Rodelbahnbox
104     wikitext = wiki_page.old_text
105     regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
106     match = regexp.search(wikitext)
107     if not match:
108         raise Exception(u"No 'Rodelbahnbox' found")
109     box = match.group(1)
110     
111     # Process Rodelbahnbox
112     for property in box.split('|'):
113         property = property.strip()
114         if property == u'Rodelbahnbox': continue
115         key_value = property.split('=')
116         if len(key_value) != 2:
117             raise Exception(u"Property '%s' has unexpected format" % key_value)
118         key = key_value[0].strip()
119         value = key_value[1].strip()
120         if key == u'Rodelbahnnummer': pass
121         elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
122         elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
123         elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
124         elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
125         elif key == u'Aufstieg getrennt': sl.walkup_separate = conv(to_bool, value, u'Aufstieg getrennt')
126         elif key == u'Lift': sl.lift = conv(to_bool, value, u'Lift')
127         elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
128         elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
129         elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
130         elif key == u'Bild': sl.image = value
131         elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
132         elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
133         elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
134         elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
135         elif key == u'Lawinengefahr':
136             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)
137         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
138     sl.under_construction = None
139     
140     # Match Forumlink (e.g. {{Forumlink|68}})
141     match = re.search(u"\{\{Forumlink\|(\d+)\}\}", wikitext)
142     if match: sl.forum_id = match.group(1)
143     
144     return sl
145
146
147 def wikipage_to_wrsleddingcache(wiki_page):
148     """Converts a sledding route wiki page (wradmin.model.page_table) 
149     to a sledding route wrsleddingcache database record (wradmin.model.wrsleddingcache_table).
150     Raises a RuntimeError if the format is not OK
151     sledding_wiki is a column of tabe "page".
152     Returns the WrSleddingCache class"""
153     sl = model.WrSleddingCache()
154     sl.page_id = wiki_page.page_id
155     sl.page_title = to_title(wiki_page.page_title)
156     errors = [] # List of errors with localized messages
157     
158     # Match Rodelbahnbox
159     wikitext = wiki_page.old_text
160     regexp = re.compile(u"\{\{(Rodelbahnbox[^\}]*)\}\}", re.DOTALL)
161     match = regexp.search(wikitext)
162     if not match:
163         raise RuntimeError(_(u"No 'Rodelbahnbox' found"))
164     box = match.group(1)
165     
166     # Process Rodelbahnbox
167     for property in box.split('|'):
168         property = property.strip()
169         if property == u'Rodelbahnbox': continue
170         key_value = property.split('=')
171         if len(key_value) != 2:
172             raise RuntimeError(_(u"Property '%s' has unexpected format") % key_value)
173         key = key_value[0].strip()
174         value = key_value[1].strip()
175         if key in [u'Rodelbahnnummer', u'Lift']:
176             errors.append(_("Property '%s' is not supported anymore, see %s.") % (key, 'http://www.winterrodeln.org/wiki/Vorlage:Rodelbahnbox'))
177         elif key == u'Position': (sl.position_latitude, sl.position_longitude) = conv(to_geo, value, u'Position') # '47.583333 N 15.75 E'
178         elif key == u'Position oben': (sl.top_latitude, sl.top_longitude) = conv(to_geo, value, u'Position oben') # '47.583333 N 15.75 E'
179         
180
181 | Höhe oben            = 1700
182 | Position unten       = 
183 | Höhe unten           = 1200
184 | Länge                = 3500
185 | Schwierigkeit        = mittel
186 | Lawinen              = kaum
187 | Betreiber            = Max Mustermann
188 | Öffentliche Anreise  = Mittelmäßig
189 | Gehzeit              = 90
190 | Aufstieg getrennt    = Ja
191 | Aufstiegshilfe       = Gondel (unterer Teil)
192 | Beleuchtungsanlage   = Ja
193 | Beleuchtungstage     = 3 (Montag, Mittwoch, Freitag)
194 | Rodelverleih         = Ja (Talstation Serlesbahnan)
195 | Gütesiegel           = Tiroler Naturrodelbahn-Gütesiegel 2009 mittel
196 | Webauskunft          = http://www.nösslachhütte.at/page9.php
197 | Telefonauskunft      = +43-664-5487520 (Mitterer Alm)
198 | Bild                 = Rodelbahn_Mitterer_Alm_04.jpg
199 | In Übersichtskarte   = Ja
200 | Forumid              = 33
201             
202     sa.Column("top_elevation", types.Integer),
203     sa.Column("bottom_latitude", types.Float),
204     sa.Column("bottom_longitude", types.Float),
205     sa.Column("bottom_elevation", types.Integer),
206     sa.Column("length", types.Integer),
207     sa.Column("difficulty", types.Integer),
208     sa.Column("avalanches", types.Integer),
209     sa.Column("operator", types.Unicode(255)),
210     sa.Column("public_transport", types.Integer),
211     sa.Column("walkup_time", types.Integer),
212     sa.Column("walkup_separate", types.Float),
213     sa.Column("walkup_separate_comment", types.Unicode(255)),
214     sa.Column("lift", types.Boolean),
215     sa.Column("lift_details", types.Unicode(255)),
216     sa.Column("night_light", types.Float),
217     sa.Column("night_light_days", types.Integer),
218     sa.Column("night_light_days_comment", types.Unicode(255)),
219     sa.Column("sled_rental", types.Boolean),
220     sa.Column("cachet", types.Unicode(255)),
221     sa.Column("information_web", types.Unicode(255)),
222     sa.Column("information_phone", types.Unicode(255)),
223     sa.Column("image", types.Unicode(255)),
224     sa.Column("show_in_overview", types.Boolean),
225     sa.Column("forum_id", types.Integer),
226     sa.Column("under_construction", types.Boolean),
227     )
228
229             
230             
231             
232             
233             
234             
235         elif key == u'Bild': sl.image = value
236
237         elif key == u'Länge': sl.length = conv(to_unsigned, value, u'Länge')
238         elif key == u'Gehzeit': sl.walktime = conv(to_unsigned, value, u'Gehzeit')
239         elif key == u'Höhe oben': sl.height_top = conv(to_unsigned, value, u'Höhe oben')
240         elif key == u'Höhe unten': sl.height_bottom = conv(to_unsigned, value, u'Höhe unten')
241         elif key == u'Aufstieg getrennt':
242             tristate = conv(to_tristate, value, u'Aufstieg getrennt')
243             if tristate == (True, False): sl.walkup_separate = 1.0
244             elif tristate == (True,  True): sl.walkup_separate = 0.5
245             elif tristate == (False, True): sl.walkup_separate = 0.0
246         elif key == u'Aufstiegshilfe':
247             valuecommentlist = conv(to_valuecommentlist, value, u'Aufstiegshilfe')
248             lift = len(valuecommentlist) > 0
249             for value, comment in valuecommentlist:
250                 if value == u'Nein':
251                     if len(valuecommentlist) != 1: raise formencode.Invalid('"Nein" kann mit keiner anderen Aufstiegshilfe kombiniert werden.', value, None)
252                     lift = False
253                 elif not value in [u'Sessellift', u'Gondel', u'Linienbus', u'Taxi', u'Sonstige']:
254                     raise formencode.Invalid(u'"%s" ist keine gültige Aufstiegshilfe.' % value, value, None)
255             sl.lift = lift
256             sl.lift_detail = model.validators.ValueCommentList().from_python(valuecommentlist)
257         elif key == u'Beleuchtung': sl.night_light = conv(to_bool, value, u'Beleuchtung')
258         elif key == u'Rodelverleih': sl.sledge_rental = conv(to_bool, value, u'Rodelverleih')
259         elif key == u'Öffentliche Anreise': sl.public_transport = conv(to_bool, value, u'Öffentliche Anreise')
260         elif key == u'Auskunft': sl.information = conv(to_phone_info, value, u'Auskunft')
261         elif key == u'In Übersichtskarte': sl.show_in_overview = conv(to_bool, value, u'In Übersichtskarte')
262         elif key == u'Aufnahmedatum': sl.creation_date = conv(to_date, value, u'Aufnahmedatum') # '2006-03-15'
263         elif key == u'Lawinengefahr':
264             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)
265         else: raise formencode.Invalid(u"Unbekannte Eigenschaft der Rodelbahnbox: '%s' (mit Wert '%s')" % (key, value), value, None)
266     sl.under_construction = None
267     
268     # Match Forumlink (e.g. {{Forumlink|68}})
269     match = re.search(u"\{\{Forumlink\|(\d+)\}\}", wikitext)
270     if match: sl.forum_id = match.group(1)
271     
272     return sl
273
274
275 # User management
276 # ---------------
277
278 class MediaWikiUsers(UsersReadOnly):
279     def __init__(self, data=None, encrypt=None):
280         UsersReadOnly.__init__(self, data, encrypt)
281
282         # Initialize class fields
283         self.usernames = []
284         self.passwords = {}
285         self.roles = {}
286         self.groups = {}
287         self.user_ids = {} # MediaWiki user_id field of the database
288         self.real_names = {} # Real names of the users
289         self.emails = {} # E-Mail addresses of the users
290         
291         # Query database
292         con = model.meta.engine.connect()
293         sql = "SELECT user_id, user_name, user_real_name, user_password, user_email FROM user, user_groups WHERE ug_user=user_id AND ug_group='beauftragte'"
294         result = con.execute(sql)
295         for row in result:
296             user_id, username, real_name, password, email = row
297             username = username.lower()
298             role = []
299             group = None
300             
301             self.usernames.append(username)
302             self.passwords[username] = password
303             self.roles[username] = role
304             self.groups[username] = group
305             self.user_ids[username] = user_id
306             self.real_names[username] = real_name
307             self.emails[username] = email
308         con.close()
309         log.info("%d users loaded from the MediaWiki database" % len(self.usernames))
310     
311     
312     def user_has_password(self, username, password):
313         """
314         Passwords are case sensitive.
315         Returns ``True`` if the user has the password specified, ``False`` otherwise. 
316         Raises an exception if the user doesn't exist.
317         
318         See http://www.winterrodeln.org/trac/wiki/MediaWikiAuthorization
319         """
320         pwd = self.user_password(username)
321         # Example: pwd = ':B:d25b2886:41e46c952790b1b442aac4f24f7ea7a8'
322         pwd_parts = pwd.split(':') # password_parts = ['', 'B', 'd25b2886', '41e46c952790b1b442aac4f24f7ea7a8']
323         if len(pwd_parts) == 4 and pwd_parts[1] == 'B':
324             salt, pwd_md5 = tuple(pwd_parts[2:4]) # salt = 'd25b2886'; pwd_md5 = '41e46c952790b1b442aac4f24f7ea7a8'
325         else:
326             raise AuthKitError("Password in the MediaWiki database format has an unexpected format ('%s' instead of e.g. ':B:d25b2886:41e46c952790b1b442aac4f24f7ea7a8')" % pwd)
327         # log.info("user: '%s'; md5 of salt+' '+entered_pwd: '%s'; md5-part of DB-pwd: %s" % (username, md5(salt + '-' + md5(password)), pwd_md5))
328         return md5(salt + '-' + md5(password)) == pwd_md5