The request: I like to send out invitations to a wiki

I think it would be nice if I do not have to send people a mail that tells them to register. I rather would like to preregister them (give them a username) or to send them an email with an activation key.

This would also allow the superuser maybe to restrict registration to only those that were invited by him. Maybe it would also be possible to send many invitations at once to different mail addresses.

Yes, please, this is an extremely useful feature! -- AlvaroTejero

related requests

FeatureRequests/LoginHintToSendPasswordonlyWithoutUserCreation

FeatureRequests/DisableUserCreation

No Solution for v. 1.8.1-1

user.py and action/newaccount.py need patching. I think a new action (invite) would be better (still need to change user.py), That way actions_excluded can be used to exclude newaccount (see FeatureRequests/DisableUserCreation). -- Ro 2009-02-03 00:38:08

Solution for v. 1.7.2

Here is the step-by-step-instruction:

  1. Create "MakeInvitation" page by using this example MakeInvitation_v1.7.2.txt. You can use ACL on this page to restrict rights, but MakeInvitation macro have additional check.

  2. Copy "MakeInvitation.py" macro (MakeInvitation_v1.7.2.py) to macro folder of your moin instance

  3. Logon as superuser, load "MakeInvitation" page and follow instructions on page. Here is screenshot of this page:

MakeInvitation_v1.7.2.gif

I haven't change anything in user.py and userform.py (in 1.7.2 there isn't such file) and I haven't tested mailing part of this stuff because I haven't SMTP right now (sorry). But according to changes that have been made in 1.7.2 it seems to be work correctly without changing of user.py.

-- Renard 2008-11-20

Patch for Moin 1.5.4

For a solution using the command line for invitations see FeatureRequests/MoinSendMailAndCreateShouldCreateAlwaysAPassword and the given patch there.

For an integrated solution in the wiki see as follows:

Here are some patches for Moin1.5.4-1 - as far as I could do that with my limited knowledge on the Moin code, python (maybe the code is not PEP8 compatible)... Moin has already some built-in-function to create-account-and-email-data but this part of the code has never been used. So I tried to turn this part into a create-account-and-make-invitation feature. It worked on my system but is not heavily tested. Here is the step-by-step-instruction for patching:

Step 1: Download the macro MakeInvitation.py and install it in Moin's macro directory.

Step 2: Create a new page MakeInvitation in the underlay_dir as follows:

## Please edit system and help pages ONLY in the moinmaster wiki! For more
## information, please see MoinMaster:MoinPagesEditorGroup.
##master-page:Unknown-Page
##master-date:Unknown-Date
#acl MoinPagesEditorGroup:read,write,delete,revert All:read
#format wiki
#language en
[[MakeInvitation]]

= Making an invitation =
Please fill out '''[[GetText(Name)]]''' and '''[[GetText(Email)]]''' to create an account for a new user a send him an invitation to join the Wiki community.

(!) It is best to choose a WikiName (like Firstname``Lastname) as username to get changes and signatures linked back to the user's Wiki``Homepage.

(!) Moin generates automatically a 6-digit random password for the user. Please do overwrite it if you want to provide some other password for the user.


If you click on '''[[GetText(Create Profile)]] + [[GetText(Email)]] ''', a new user profile will be created and an email will be sent to the user. If this button is missing, email notification is not enabled on your system.

If you click on '''[[GetText(Cancel)]] ''', the input form will be cleared and a new password will be generated.

You may also change the acls of this page so that only superusers can view it. But this is not done here, but might be better.

Step 3 Patch user.py

   1 --- user_orig.py	2006-07-01 20:56:00.000000000 +0200
   2 +++ user.py	2006-11-25 16:38:42.000000000 +0100
   3 @@ -936,7 +936,7 @@
   4              markup = '%s:%s' % (wikiname, pagename.replace(" ","_")) 
   5          return markup
   6  
   7 -    def mailAccountData(self, cleartext_passwd=None):
   8 +    def mailAccountData(self, cleartext_passwd=None, invitation=False):
   9          from MoinMoin.util import mail
  10          from MoinMoin.wikiutil import getSysPage
  11          _ = self._request.getText
  12 @@ -955,7 +955,9 @@
  13              self.enc_password = pwd
  14              self.save()
  15  
  16 -        text = '\n' + _("""\
  17 +        if not invitation:
  18 +
  19 +            text = '\n' + _("""\
  20  Login Name: %s
  21  
  22  Login Password: %s
  23 @@ -964,7 +966,7 @@
  24  """, formatted=False) % (
  25                          self.name, self.enc_password, self._request.getBaseURL(), getSysPage(self._request, 'UserPreferences').page_name)
  26  
  27 -        text = _("""\
  28 +            text = _("""\
  29  Somebody has requested to submit your account data to this email address.
  30  
  31  If you lost your password, please use the data below and just enter the
  32 @@ -974,6 +976,32 @@
  33  After successfully logging in, it is of course a good idea to set a new and known password.
  34  """, formatted=False) + text
  35  
  36 +        else:
  37 +
  38 +            text = '\n' + _("""\
  39 +Login Name: %s
  40 +
  41 +Login Password: %s
  42 +
  43 +Login URL: %s/%s?action=login
  44 +""", formatted=False) % (
  45 +                        self.name, cleartext_passwd, self._request.getBaseURL(), getSysPage(self._request, 'UserPreferences').page_name)
  46 +
  47 +            text = _("""\
  48 +Welcome to the %(sitename)s Wiki!
  49 +
  50 +You are invited to join our wiki community and share your ideas with us.
  51 +Please use the login name and password below to log in. To ease login, you can
  52 +also use copy and paste.
  53 +Please do also make sure that in your browser's settings cookies are enabled
  54 +for the login url so that authentications does really work and is kept while
  55 +navigating in the wiki.
  56 +
  57 +After successfully logging in, it is of course a good idea to set a new password
  58 +of your choice.
  59 +""", formatted=False) % {'sitename': self._cfg.sitename or "Wiki"}  + text
  60 +
  61 +        
  62  
  63          subject = _('[%(sitename)s] Your wiki account data',
  64                      formatted=False) % {'sitename': self._cfg.sitename or "Wiki"}

moin1-5-4-1user.diff

user_moin1-5-4-1_patched.py

Step 4 Patch userform.py

   1 --- userform_orig.py	2006-04-25 01:19:00.000000000 +0200
   2 +++ userform.py	2006-11-25 16:42:46.000000000 +0100
   3 @@ -154,9 +154,12 @@
   4              # save data
   5              theuser.save()
   6              if form.has_key('create_and_mail'):
   7 -                theuser.mailAccountData()
   8 -            
   9 -            result = _("User account created! You can use this account to login now...")
  10 +                result1 = _("User account created.") # ToDo: Add translation for this!!
  11 +                result2 = theuser.mailAccountData(form.get('password', [''])[0],True)
  12 +                result = "%s %s" % (result1, _(result2)) # This assumes that all msg from mailAccountData have translations
  13 +            else:
  14 +                result = _("User account created! You can use this account to login now...")
  15 +
  16              if _debug:
  17                  result = result + util.dumpFormData(form)
  18              return result
  19 @@ -477,7 +480,7 @@
  20          _ = self._
  21          self.make_form()
  22  
  23 -        if self.request.user.isSuperUser():
  24 +        if self.request.user.isSuperUser() and create_only == False:
  25              ticket = wikiutil.createTicket()
  26              self.make_row(_('Select User'), [self._user_select()])
  27              self._form.append(html.INPUT(type="hidden", name="ticket", value="%s" % ticket))
  28 @@ -588,22 +591,50 @@
  29                  ('create', _('Create Profile')),
  30                  ('cancel', _('Cancel')),
  31              ]
  32 -            for key, label, type, length, textafter in self.cfg.user_form_fields:
  33 -                if key in ('name', 'password', 'password2', 'email'):
  34 -                    self.make_row(_(label),
  35 -                              [ html.INPUT(type=type, size=length, name=key,
  36 -                                           value=''),
  37 -                                ' ', _(textafter), ])
  38 +            if create_only == False:
  39 +                for key, label, type, length, textafter in self.cfg.user_form_fields:
  40 +                    if key in ('name', 'password', 'password2', 'email'):
  41 +                        self.make_row(_(label),
  42 +                                  [ html.INPUT(type=type, size=length, name=key,
  43 +                                               value=''),
  44 +                                    ' ', _(textafter), ])
  45 +            else:
  46 +                # We are in invitation mode; create some dummy password
  47 +                from random import choice
  48 +                letters = "abcdefghijklmnopqrstuvwxyz"
  49 +                letters += "0123456789"
  50 +                pwd = ''
  51 +                for i in range(6):
  52 +                    pwd += choice(letters)
  53 +                    
  54 +                for key, label, type, length, textafter in self.cfg.user_form_fields:
  55 +                    if key in ('name', 'password', 'password2', 'email'):
  56 +                        
  57 +                        if key == 'password' or key == 'password2':
  58 +                            self.make_row(_(label),
  59 +                                      [ html.INPUT(type=type, size=length, name=key,
  60 +                                                   value='%s' % pwd),
  61 +                                        ' ', _(textafter), ])
  62 +                        else:
  63 +                            self.make_row(_(label),
  64 +                                  [ html.INPUT(type=type, size=length, name=key,
  65 +                                               value=''),
  66 +                                    ' ', _(textafter), ])
  67 +            
  68  
  69          if self.cfg.mail_enabled:
  70              buttons.append(("account_sendmail", _('Mail me my account data')))
  71  
  72          if create_only:
  73 -            buttons = [("create_only", _('Create Profile'))]
  74 +            # We don't want to have a create profile button in invitation mode
  75 +            #buttons = [("create_only", _('Create Profile'))]
  76 +            buttons = []
  77              if self.cfg.mail_enabled:
  78                  buttons.append(("create_and_mail", "%s + %s" %
  79                                  (_('Create Profile'), _('Email'))))
  80  
  81 +            buttons.append(('cancel', _('Cancel')))
  82 +
  83          # Add buttons
  84          button_cell = []
  85          for name, label in buttons:

moin1-5-4-1userform.diff

userform_moin1-5-4-1_patched.py

Step 5 For full multilang support you have to add some translations, see comments in MakeInvitation.py

That's it! Maybe you are also interested in some related patch of mine: FeatureRequests/PossibilityToSetQuicklinksAndSubscriptionsOnAccountCreation. Both work nicely together.

-- OliverSiemoneit 2006-11-25 20:10:10

To the main developers: is this in time to enter 1.6.0?

Patch for moin-1-6-main-7c58e8af1a97

For Moin 1.6 I have moved a lot of stuff in the macro itself. To userform.py only minor changes to the eventhandling have to be done:

   1 --- userform_old.py	2006-12-09 13:32:00.000000000 +0100
   2 +++ userform.py	2006-12-10 22:32:04.000000000 +0100
   3 @@ -142,11 +142,14 @@
   4                          return _("This email already belongs to somebody else.")
   5  
   6              # save data
   7 -            theuser.save()
   8              if form.has_key('create_and_mail'):
   9 -                theuser.mailAccountData()
  10 -
  11 -            result = _("User account created! You can use this account to login now...")
  12 +                # ToDo: Add translation for _("User account created.")
  13 +                result1 = _("User account created.")
  14 +                result2 = theuser.mailAccountData(form.get('password', [''])[0], True)
  15 +                # This assumes that all msg from mailAccountData (result2) have translations
  16 +                result = "%s %s" % (result1, _(result2))
  17 +            else:
  18 +                result = _("User account created! You can use this account to login now...")
  19              if _debug:
  20                  result = result + util.dumpFormData(form)
  21              return result

userform.diff

The diff for the user.py looks complicated but changes are only made to the mailAccountData function. And these changes are mainly the mail text:

   1     def mailAccountData(self, cleartext_passwd=None, invitation=False):
   2         from MoinMoin.mail import sendmail
   3         from MoinMoin.wikiutil import getSysPage
   4         _ = self._request.getText
   5 
   6         if not self.enc_password: # generate pw if there is none yet
   7             from random import randint
   8             import base64
   9 
  10             charset = 'utf-8'
  11             pwd = "%s%d" % (str(time.time()), randint(0, 65535))
  12             pwd = pwd.encode(charset)
  13 
  14             pwd = sha.new(pwd).digest()
  15             pwd = '{SHA}%s' % base64.encodestring(pwd).rstrip()
  16 
  17             self.enc_password = pwd
  18             self.save()
  19 
  20         if not invitation:
  21             text = '\n' + _("""\
  22 Login Name: %s
  23 
  24 Login Password: %s
  25 
  26 Login URL: %s/%s?action=login
  27 """, formatted=False) % (
  28                         self.name, self.enc_password, self._request.getBaseURL(), getSysPage(self._request, 'UserPreferences').page_name)
  29 
  30             text = _("""\
  31 Somebody has requested to submit your account data to this email address.
  32 
  33 If you lost your password, please use the data below and just enter the
  34 password AS SHOWN into the wiki's password form field (use copy and paste
  35 for that).
  36 
  37 After successfully logging in, it is of course a good idea to set a new and known password.
  38 """, formatted=False) + text
  39 
  40         else:
  41             text = '\n' + _("""\
  42 Login Name: %s
  43 
  44 Login Password: %s
  45 
  46 Login URL: %s/%s?action=login
  47 """, formatted=False) % (
  48                         self.name, cleartext_passwd, self._request.getBaseURL(), getSysPage(self._request, 'UserPreferences').page_name)
  49 
  50             text = _("""\
  51 Welcome to the %(sitename)s Wiki!
  52 
  53 You are invited to join our wiki community and share your ideas with us.
  54 Please use the login name and password below to log in. To ease login, you can
  55 also use copy and paste.
  56 Please do also make sure that in your browser's settings cookies are enabled
  57 for the login url so that authentications does really work and is kept while
  58 navigating in the wiki.
  59 
  60 After successfully logging in, it is of course a good idea to set a new password
  61 of your choice.
  62 """, formatted=False) % {'sitename': self._cfg.sitename or "Wiki"}  + text
  63 
  64 
  65         subject = _('[%(sitename)s] Your wiki account data',
  66                     formatted=False) % {'sitename': self._cfg.sitename or "Wiki"}
  67         mailok, msg = sendmail.sendmail(self._request, [self.email], subject,
  68                                     text, mail_from=self._cfg.mail_from)
  69         return msg

mailAccountData.py

And here is the new macro, which does the whole display stuff now..

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - MakeInvitation macro
   4 
   5     Syntax:
   6         [[MakeInvitation]]
   7 
   8     Lot of code take from userform.py
   9     MoinMoin - userform.py
  10     @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
  11     @license: GNU GPL, see COPYING for details.
  12     
  13     MoinMoin - MakeInvitation Macro
  14     @copyright: 2006 by Oliver Siemoneit
  15     @license: GNU GPL, see COPYING for details.
  16 """
  17 
  18 from MoinMoin.widget import html
  19 
  20 
  21 def make_row(table, label, cell, **kw):
  22     """ Create a row in the form table.
  23     """
  24     table.append(html.TR().extend([
  25         html.TD(**kw).extend([html.B().append(label), '   ']),
  26         html.TD().extend(cell),
  27     ]))
  28     return table
  29 
  30 def execute(macro, args):
  31     request = macro.request
  32     _ = request.getText
  33     formatter = macro.formatter
  34 
  35     # Check if user is superuser. If not: return with error msg
  36     if not request.user.isSuperUser():
  37         err = _('You are not allowed to perform this action.')
  38         return err
  39 
  40     sn = request.getScriptname()
  41     pi = request.getPathinfo()
  42     action = u"%s%s" % (sn, pi)
  43     form = html.FORM(action=action)
  44     table = html.TABLE(border="0")
  45 
  46     # Create 6 digits dummy password
  47     from random import choice
  48     letters = "abcdefghijklmnopqrstuvwxyz"
  49     letters += "0123456789"
  50     pwd = ''
  51     for i in range(6):
  52         pwd += choice(letters)
  53         
  54     # Add form fields                
  55     for key, label, type, length, textafter in request.cfg.user_form_fields:
  56         if key in ('name', 'password', 'password2', 'email'):
  57                         
  58             if key == 'password' or key == 'password2':
  59                 table = make_row(table, _(label),
  60                       [ html.INPUT(type=type, size=length, name=key,
  61                                    value='%s' % pwd),
  62                         ' ',])
  63             else:
  64                 table = make_row(table, _(label),
  65                       [ html.INPUT(type=type, size=length, name=key,
  66                                    value=''),
  67                         ' ', _(textafter), ])
  68 
  69     # Add buttons
  70     buttons = []
  71     if request.cfg.mail_enabled:
  72         buttons.append(("create_and_mail", "%s + %s" %
  73                         (_('Create Profile'), _('Email'))))
  74 
  75     buttons.append(('cancel', _('Cancel')))
  76 
  77     button_cell = []
  78     for name, label in buttons:
  79         if not name in request.cfg.user_form_remove:
  80             button_cell.extend([
  81                 html.INPUT(type="submit", name=name, value=label),
  82                 ' ',
  83             ])
  84     make_row(table,'', button_cell)
  85 
  86 
  87     # Use the user interface language and direction
  88     lang_attr = request.theme.ui_lang_attr()
  89     form.append(html.Raw('<div class="userprefs"%s>' % lang_attr))
  90     form.append(html.INPUT(type="hidden", name="action", value="userform"))
  91     form.append(table)
  92     form.append(html.Raw("</div>"))
  93    
  94     return unicode(form)

MakeInvitation1-6-dev.py

The new underlay page form where the marco is called from is unchanged. You can get it for download here

Please note: I could not these the changes since at the moment I have only a moin standalone version with no mail functionality. These changes are ports of the given patch above and should theoretically work

This patch goes also nicely together with some others patches where I have tried to modularize the log in process:

Open problems:

-- OliverSiemoneit 2006-12-10 22:18:05


This Macro is great! I have a feature request for a future version of this macro. I currently have an ACL group called InvitedGroup. I use it to distiguish between people I specifically invited to our wiki verse those who register themselves. I Manually need to add those to InvitedGroup that I send an invite using MakeInvitation. I would be great if the macro did it automatically. Thanks again for a great extension! -- MauriceRabb 2007-01-14 17:47:44

see also FeatureRequests/DifferentMessageFromSuperuserMailAccountData -- ReimarBauer 2008-11-20 08:27:23


CategoryFeaturePatched

MoinMoin: FeatureRequests/MakeInvitations (last edited 2009-02-03 00:38:08 by Ro)