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&amp;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.
  • [get | view] (2005-03-27 02:58:08, 7.1 KB) [[attachment:createuser.patch]]
  • [get | view] (2005-05-10 07:25:53, 22.4 KB) [[attachment:userform.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.