Attachment 'userform.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - UserPreferences Form and User Browser
4
5 @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
6 @license: GNU GPL, see COPYING for details.
7 """
8
9 import string, time, re, Cookie
10 from MoinMoin import config, user, util, wikiutil, wikiacl
11 from MoinMoin.util import web, mail, datetime
12 from MoinMoin.widget import html
13
14 _debug = 0
15
16
17 #############################################################################
18 ### Form POST Handling
19 #############################################################################
20
21 def savedata(request):
22 """ Handle POST request of the user preferences form.
23
24 Return error msg or None.
25 """
26 return UserSettingsHandler(request).handleData()
27
28
29 class UserSettingsHandler:
30
31 def __init__(self, request):
32 """ Initialize user settings form. """
33 self.request = request
34 self._ = request.getText
35 self.cfg = request.cfg
36
37 def decodePageList(self, key):
38 """ Decode list of pages from form input
39
40 Each line is a page name, empty lines ignored.
41
42 Items can use '_' as spaces, needed by [name_with_spaces label]
43 format used in quicklinks. We do not touch those names here, the
44 underscores are handled later by the theme code.
45
46 @param key: the form key to get
47 @rtype: list of unicode strings
48 @return: list of normalized names
49 """
50 text = self.request.form.get(key, [''])[0]
51 text = text.replace('\r', '')
52 items = []
53 for item in text.split('\n'):
54 item = item.strip()
55 if not item:
56 continue
57 # Normalize names - except [name_with_spaces label]
58 if not (item.startswith('[') and item.endswith(']')):
59 item = self.request.normalizePagename(item)
60 items.append(item)
61 return items
62
63 def handleData(self):
64 _ = self._
65 form = self.request.form
66
67 if form.has_key('logout'):
68 # clear the cookie in the browser and locally. Does not
69 # check if we have a valid user logged, just make sure we
70 # don't have one after this call.
71 self.request.deleteCookie()
72 return _("Cookie deleted. You are now logged out.")
73
74 if form.has_key('login_sendmail'):
75 if not self.cfg.mail_smarthost:
76 return _("""This wiki is not enabled for mail processing.
77 Contact the owner of the wiki, who can enable email.""")
78 try:
79 email = form['email'][0].lower()
80 except KeyError:
81 return _("Please provide a valid email address!")
82
83 text = ''
84 users = user.getUserList(self.request)
85 for uid in users:
86 theuser = user.User(self.request, uid)
87 if theuser.valid and theuser.email.lower() == email:
88 text = "%s\n\nID: %s\nName: %s\nPassword: %s\nLogin URL: %s/?action=userform&uid=%s" % (
89 text, theuser.id, theuser.name, theuser.enc_password, self.request.getBaseURL(), theuser.id)
90
91 if not text:
92 return _("Found no account matching the given email address '%(email)s'!") % {'email': wikiutil.escape(email)}
93
94 mailok, msg = util.mail.sendmail(self.request, [email],
95 'Your wiki account data', text, mail_from=self.cfg.mail_from)
96 return wikiutil.escape(msg)
97
98 if form.has_key('login'):
99 # Trying to login with a user name and a password
100
101 # Require valid user name
102 name = form.get('username', [''])[0]
103 if not user.isValidName(self.request, name):
104 return _("""Invalid user name {{{'%s'}}}.
105 Name may contain any Unicode alpha numeric character, with optional one
106 space between words. Group page name is not allowed.""") % wikiutil.escape(name)
107
108 # Check that user exists
109 if not user.getUserId(self.request, name):
110 return _('Unknown user name: {{{"%s"}}}. Please enter'
111 ' user name and password.') % name
112
113 # Require password
114 password = form.get('password',[None])[0]
115 if not password:
116 return _("Missing password. Please enter user name and"
117 " password.")
118
119 # Load the user data and check for validness
120 theuser = user.User(self.request, name=name, password=password)
121 if not theuser.valid:
122 return _("Sorry, wrong password.")
123
124 # Save the user and send a cookie
125 self.request.user = theuser
126 self.request.setCookie()
127
128 elif form.has_key('uid'):
129 # Trying to login with the login URL, soon to be removed!
130 try:
131 uid = form['uid'][0]
132 except KeyError:
133 return _("Bad relogin URL.")
134
135 # Load the user data and check for validness
136 theuser = user.User(self.request, uid)
137 if not theuser.valid:
138 return _("Unknown user.")
139
140 # Save the user and send a cookie
141 self.request.user = theuser
142 self.request.setCookie()
143
144 else:
145 createuseracl = wikiacl.CreateUserAccessControlList(self.request).may(self.request, self.request.user.name, "write")
146
147 # Save user profile
148 theuser = user.User(self.request)
149
150 # Require non-empty name
151 try:
152 theuser.name = form['username'][0]
153 except KeyError:
154 return _("Empty user name. Please enter a user name.")
155
156 # Don't allow users with invalid names
157 if not user.isValidName(self.request, theuser.name):
158 return _("""Invalid user name {{{'%s'}}}.
159 Name may contain any Unicode alpha numeric character, with optional one
160 space between words. Group page name is not allowed.""") % wikiutil.escape(theuser.name)
161
162 # Is this an existing user trying to change information or a new user?
163 # Name required to be unique. Check if name belong to another user.
164 newuser = 1
165
166 # create key is for existing users creating new users
167 # save key is for users creating or updating themselves
168 if form.has_key('save'):
169 if user.getUserId(self.request, theuser.name):
170 if theuser.name != self.request.user.name:
171 return _("This user name already belongs to somebody else.")
172 else:
173 newuser = 0
174
175 # Now that we know if it's a new user or not, we can check the acl
176 if newuser and not createuseracl:
177 return _("You are not allowed to create a user account.")
178
179 # create key is for existing users creating new users
180 # save key is for users creating or updating themselves
181 if newuser and form.has_key('create'):
182 theuserisnew = self.request
183 theuserisnew.saved_cookie = ''
184 theuserisnew.auth_username = ''
185 theuser = user.User(theuserisnew)
186 theuser.name = form['username'][0]
187
188 # try to get the password and pw repeat
189 password = form.get('password', [''])[0]
190 password2 = form.get('password2',[''])[0]
191
192 # Check if password is given and matches with password repeat
193 if password != password2:
194 return _("Passwords don't match!")
195 if not password and newuser:
196 return _("Please specify a password!")
197 # Encode password
198 if password and not password.startswith('{SHA}'):
199 try:
200 theuser.enc_password = user.encodePassword(password)
201 except UnicodeError, err:
202 # Should never happen
203 return "Can't encode password: %s" % str(err)
204
205 # try to get the (optional) email
206 email = form.get('email', [''])[0]
207 theuser.email = email.strip()
208
209 # Require email if acl is enabled
210 if not theuser.email and self.cfg.acl_enabled:
211 return _("Please provide your email address. If you loose your"
212 " login information, you can get it by email.")
213
214 # Email required to be unique
215 # See also MoinMoin/scripts/moin_usercheck.py
216 if theuser.email:
217 users = user.getUserList(self.request)
218 for uid in users:
219 if uid == theuser.id:
220 continue
221 thisuser = user.User(self.request, uid)
222 if thisuser.email == theuser.email:
223 return _("This email already belongs to somebody else.")
224
225
226 # editor size
227 theuser.edit_rows = util.web.getIntegerInput(self.request, 'edit_rows', theuser.edit_rows, 10, 60)
228
229 # time zone
230 theuser.tz_offset = util.web.getIntegerInput(self.request, 'tz_offset', theuser.tz_offset, -84600, 84600)
231
232 # datetime format
233 try:
234 dt_d_combined = UserSettings._date_formats.get(form['datetime_fmt'][0], '')
235 theuser.datetime_fmt, theuser.date_fmt = dt_d_combined.split(' & ')
236 except (KeyError, ValueError):
237 pass
238
239 # try to get the (optional) theme
240 theme_name = form.get('theme_name', [self.cfg.theme_default])[0]
241 if theme_name != theuser.theme_name:
242 # if the theme has changed, load the new theme
243 # so the user has a direct feedback
244 # WARNING: this should be refactored (i.e. theme load
245 # after userform handling), cause currently the
246 # already loaded theme is just replaced (works cause
247 # nothing has been emitted yet)
248 theuser.theme_name = theme_name
249 if self.request.loadTheme(theuser.theme_name) > 0:
250 theme_name = wikiutil.escape(theme_name)
251 return _("The theme '%(theme_name)s' could not be loaded!") % locals()
252
253 # User CSS URL
254 theuser.css_url = form.get('css_url', [''])[0]
255
256 # try to get the (optional) preferred language
257 theuser.language = form.get('language', [''])[0]
258
259 # checkbox options
260 if not newuser:
261 for key, label in user.User._checkbox_fields:
262 value = form.get(key, ["0"])[0]
263 try:
264 value = int(value)
265 except ValueError:
266 pass
267 else:
268 setattr(theuser, key, value)
269
270 # quicklinks for navibar
271 theuser.quicklinks = self.decodePageList('quicklinks')
272
273 # subscription for page change notification
274 theuser.subscribed_pages = self.decodePageList('subscribed_pages')
275
276 # save data and send cookie
277 theuser.save()
278
279 # create key is for existing users creating new users
280 # save key is for users creating or updating themselves
281 if form.has_key('save'):
282 self.request.user = theuser
283 self.request.setCookie()
284
285 result = _("User preferences saved!")
286 if _debug:
287 result = result + util.dumpFormData(form)
288 return result
289
290
291 #############################################################################
292 ### Form Generation
293 #############################################################################
294
295 class UserSettings:
296 """ User login and settings management. """
297
298 _date_formats = { # datetime_fmt & date_fmt
299 'iso': '%Y-%m-%d %H:%M:%S & %Y-%m-%d',
300 'us': '%m/%d/%Y %I:%M:%S %p & %m/%d/%Y',
301 'euro': '%d.%m.%Y %H:%M:%S & %d.%m.%Y',
302 'rfc': '%a %b %d %H:%M:%S %Y & %a %b %d %Y',
303 }
304
305 def __init__(self, request):
306 """ Initialize user settings form.
307 """
308 self.request = request
309 self._ = request.getText
310 self.cfg = request.cfg
311
312 def _tz_select(self):
313 """ Create time zone selection. """
314 tz = 0
315 if self.request.user.valid:
316 tz = int(self.request.user.tz_offset)
317
318 options = []
319 now = time.time()
320 for halfhour in range(-47, 48):
321 offset = halfhour * 1800
322 t = now + offset
323
324 options.append((
325 str(offset),
326 '%s [%s%s:%s]' % (
327 time.strftime(self.cfg.datetime_fmt, util.datetime.tmtuple(t)),
328 "+-"[offset < 0],
329 string.zfill("%d" % (abs(offset) / 3600), 2),
330 string.zfill("%d" % (abs(offset) % 3600 / 60), 2),
331 ),
332 ))
333
334 return util.web.makeSelection('tz_offset', options, str(tz))
335
336
337 def _dtfmt_select(self):
338 """ Create date format selection. """
339 _ = self._
340 try:
341 dt_d_combined = '%s & %s' % (self.request.user.datetime_fmt, self.request.user.date_fmt)
342 selected = [
343 k for k, v in self._date_formats.items()
344 if v == dt_d_combined][0]
345 except IndexError:
346 selected = ''
347 options = [('', _('Default'))] + self._date_formats.items()
348
349 return util.web.makeSelection('datetime_fmt', options, selected)
350
351
352 def _lang_select(self):
353 """ Create language selection. """
354 from MoinMoin import i18n
355 from MoinMoin.i18n import NAME
356 _ = self._
357 cur_lang = self.request.user.valid and self.request.user.language or ''
358 langs = i18n.wikiLanguages().items()
359 langs.sort(lambda x,y,NAME=NAME: cmp(x[1][NAME], y[1][NAME]))
360 options = [('', _('<Browser setting>', formatted=False))]
361 for lang in langs:
362 name = lang[1][NAME]
363 options.append((lang[0], name))
364
365 return util.web.makeSelection('language', options, cur_lang)
366
367 def _theme_select(self):
368 """ Create theme selection. """
369 cur_theme = self.request.user.valid and self.request.user.theme_name or self.cfg.theme_default
370 options = []
371 for theme in wikiutil.getPlugins('theme', self.request.cfg):
372 options.append((theme, theme))
373
374 return util.web.makeSelection('theme_name', options, cur_theme)
375
376 def make_form(self):
377 """ Create the FORM, and the TABLE with the input fields
378 """
379 sn = self.request.getScriptname()
380 pi = self.request.getPathinfo()
381 action = u"%s%s" % (sn, pi)
382 self._form = html.FORM(action=action)
383 self._table = html.TABLE(border="0")
384
385 # Use the user interface language and direction
386 lang_attr = self.request.theme.ui_lang_attr()
387 self._form.append(html.Raw('<div class="userpref"%s>' % lang_attr))
388
389 self._form.append(html.INPUT(type="hidden", name="action", value="userform"))
390 self._form.append(self._table)
391 self._form.append(html.Raw("</div>"))
392
393
394 def make_row(self, label, cell, **kw):
395 """ Create a row in the form table.
396 """
397 self._table.append(html.TR().extend([
398 html.TD(**kw).extend([html.B().append(label), ' ']),
399 html.TD().extend(cell),
400 ]))
401
402
403 def asHTML(self):
404 """ Create the complete HTML form code. """
405 _ = self._
406 self.make_form()
407
408 createuseracl = wikiacl.CreateUserAccessControlList(self.request).may(self.request, self.request.user.name, "write")
409
410 if self.request.user.valid:
411 # User preferences interface
412 buttons = [
413 ('save', _('Save'))
414 ]
415 if createuseracl:
416 buttons.append(('create', _('Create Profile')))
417 buttons.append(('logout', _('Logout')))
418 else:
419 # Login / register interface
420 buttons = [
421 # IMPORTANT: login should be first to be the default
422 # button when a user click enter.
423 ('login', _('Login')),
424 ]
425 if createuseracl:
426 buttons.append(("save", _('Create Profile')))
427 if self.cfg.mail_smarthost:
428 buttons.append(("login_sendmail", _('Mail me my account data')))
429
430 self.make_row(_('Name'), [
431 html.INPUT(
432 type="text", size="36", name="username", value=self.request.user.name
433 ),
434 ' ', _('(Use FirstnameLastname)', formatted=False),
435 ])
436
437 self.make_row(_('Password'), [
438 html.INPUT(
439 type="password", size="36", name="password",
440 ),
441 ' ',
442 ])
443
444 if self.request.user.valid or createuseracl:
445 self.make_row(_('Password repeat'), [
446 html.INPUT(
447 type="password", size="36", name="password2",
448 ),
449 ' ', _('(Only when changing passwords)'),
450 ])
451
452 if self.cfg.mail_smarthost and not createuseracl:
453 self.make_row(_('Email'), [
454 html.INPUT(
455 type="text", size="36", name="email", value=self.request.user.email
456 ),
457 ' ', _('(Only for mailing your account data)', formatted=False),
458 ])
459 elif createuseracl:
460 self.make_row(_('Email'), [
461 html.INPUT(
462 type="text", size="36", name="email", value=self.request.user.email
463 ),
464 ' ',
465 ])
466
467
468 # Show options only if already logged in
469 if self.request.user.valid:
470
471 if not self.cfg.theme_force:
472 self.make_row(_('Preferred theme'), [self._theme_select()])
473
474 self.make_row(_('User CSS URL'), [
475 html.INPUT(
476 type="text", size="40", name="css_url", value=self.request.user.css_url
477 ),
478 ' ', _('(Leave it empty for disabling user CSS)'),
479 ])
480
481 self.make_row(_('Editor size'), [
482 html.INPUT(type="text", size="3", maxlength="3",
483 name="edit_rows", value=str(self.request.user.edit_rows)),
484 ])
485
486 self.make_row(_('Time zone'), [
487 _('Your time is'), ' ',
488 self._tz_select(),
489 html.BR(),
490 _('Server time is'), ' ',
491 time.strftime(self.cfg.datetime_fmt, util.datetime.tmtuple()),
492 ' (UTC)',
493 ])
494
495 self.make_row(_('Date format'), [self._dtfmt_select()])
496
497 self.make_row(_('Preferred language'), [self._lang_select()])
498
499 # boolean user options
500 bool_options = []
501 checkbox_fields = user.User._checkbox_fields
502 _ = self.request.getText
503 checkbox_fields.sort(lambda a, b: cmp(a[1](_), b[1](_)))
504 for key, label in checkbox_fields:
505 bool_options.extend([
506 html.INPUT(type="checkbox", name=key, value="1",
507 checked=getattr(self.request.user, key, 0)),
508 ' ', label(_), html.BR(),
509 ])
510 self.make_row(_('General options'), bool_options, valign="top")
511
512 self.make_row(_('Quick links'), [
513 html.TEXTAREA(name="quicklinks", rows="6", cols="50")
514 .append('\n'.join(self.request.user.getQuickLinks())),
515 ], valign="top")
516
517 # subscribed pages
518 if self.cfg.mail_smarthost:
519 # Get list of subscribe pages, DO NOT sort! it should
520 # stay in the order the user entered it in his input
521 # box.
522 notifylist = self.request.user.getSubscriptionList()
523
524 warning = []
525 if not self.request.user.email:
526 warning = [
527 html.BR(),
528 html.SMALL(Class="warning").append(
529 _("This list does not work, unless you have"
530 " entered a valid email address!")
531 )]
532
533 self.make_row(
534 html.Raw(_('Subscribed wiki pages (one regex per line)')),
535 [html.TEXTAREA(name="subscribed_pages", rows="6", cols="50").append(
536 '\n'.join(notifylist)),
537 ] + warning,
538 valign="top"
539 )
540
541 # Add buttons
542 button_cell = []
543 for name, label in buttons:
544 button_cell.extend([
545 html.INPUT(type="submit", name=name, value=label),
546 ' ',
547 ])
548 self.make_row('', button_cell)
549
550 return unicode(self._form)
551
552
553 def getUserForm(request):
554 """ Return HTML code for the user settings. """
555 return UserSettings(request).asHTML()
556
557
558 #############################################################################
559 ### User account administration
560 #############################################################################
561
562 def do_user_browser(request):
563 """ Browser for SystemAdmin macro. """
564 from MoinMoin.util.dataset import TupleDataset, Column
565 from MoinMoin.Page import Page
566 _ = request.getText
567
568 data = TupleDataset()
569 data.columns = [
570 #Column('id', label=('ID'), align='right'),
571 Column('name', label=('Username')),
572 Column('email', label=('Email')),
573 #Column('action', label=_('Action')),
574 ]
575
576 # Iterate over users
577 for uid in user.getUserList(request):
578 account = user.User(request, uid)
579
580 userhomepage = Page(request, account.name)
581 if userhomepage.exists():
582 namelink = userhomepage.link_to(request)
583 else:
584 namelink = account.name
585
586 data.addRow((
587 #request.formatter.code(1) + uid + request.formatter.code(0),
588 request.formatter.rawHTML(namelink),
589 (request.formatter.url(1, 'mailto:' + account.email, 'external', pretty_url=1, unescaped=1) +
590 request.formatter.text(account.email) +
591 request.formatter.url(0)),
592 #'',
593 ))
594
595 if data:
596 from MoinMoin.widget.browser import DataBrowserWidget
597
598 browser = DataBrowserWidget(request)
599 browser.setData(data)
600 return browser.toHTML()
601
602 # No data
603 return ''
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.