Changeset 2479 in products


Ignore:
Timestamp:
Jun 4, 2010 12:29:17 PM (14 years ago)
Author:
mylan
Message:

#194: Inherit CaptcaRegisterForm? and CaptchaAddUserForm? from default forms, rename form classes

Location:
quintagroup.plonecaptchas/branches/plone4/quintagroup/plonecaptchas/browser
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • quintagroup.plonecaptchas/branches/plone4/quintagroup/plonecaptchas/browser/configure.zcml

    r2465 r2479  
    99      name="register" 
    1010      for="plone.app.layout.navigation.interfaces.INavigationRoot" 
    11       class=".register.RegistrationForm" 
     11      class=".register.CaptchaRegistrationForm" 
    1212      layer="..interfaces.IQGPloneCaptchas" 
    1313      permission="cmf.AddPortalMember" 
     
    1717      name="new-user" 
    1818      for="plone.app.layout.navigation.interfaces.INavigationRoot" 
    19       class=".register.AddUserForm" 
     19      class=".register.CaptchaAddUserForm" 
    2020      layer="..interfaces.IQGPloneCaptchas" 
    2121      permission="zope2.ManageUsers" 
  • quintagroup.plonecaptchas/branches/plone4/quintagroup/plonecaptchas/browser/register.py

    r2465 r2479  
    1 from zope.interface import Interface 
    2 from zope.component import getUtility 
     1from plone.app.users.browser.register import RegistrationForm 
     2from plone.app.users.browser.register import AddUserForm 
    33 
    4 from zope import schema 
    5 from zope.formlib import form 
    6 from zope.app.form.browser import TextWidget, CheckBoxWidget, ASCIIWidget 
    7 from zope.app.form.interfaces import WidgetInputError, InputErrors 
    8 from zope.component import getMultiAdapter 
     4class CaptchaRegistrationForm(RegistrationForm): 
     5    """Registration form with captacha.""" 
    96 
    10 from AccessControl import getSecurityManager 
    11 from Products.CMFCore.interfaces import ISiteRoot 
    12 from Products.CMFCore.utils import getToolByName 
    13 from Products.CMFPlone import PloneMessageFactory as _ 
    14  
    15 from Products.Five.formlib.formbase import PageForm 
    16 from ZODB.POSException import ConflictError 
    17  
    18 from Products.statusmessages.interfaces import IStatusMessage 
    19  
    20 from plone.app.users.userdataschema import IUserDataSchemaProvider 
    21  
    22 from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile 
    23  
    24 from plone.app.controlpanel.widgets import MultiCheckBoxVocabularyWidget 
    25  
    26 from zope.schema.vocabulary import SimpleVocabulary 
    27 from zope.site.hooks import getSite 
    28 from plone.protect import CheckAuthenticator 
    29  
    30 # Define constants from the Join schema that should be added to the 
    31 # vocab of the join fields setting in usergroupssettings controlpanel. 
    32 JOIN_CONST = ['username', 'password', 'email', 'mail_me'] 
    33  
    34  
    35 class IRegisterSchema(Interface): 
    36  
    37     username = schema.ASCIILine( 
    38         title=_(u'label_user_name', default=u'User Name'), 
    39         description=_(u'help_user_name_creation_casesensitive', 
    40                       default=u"Enter a user name, usually something " 
    41                                "like 'jsmith'. " 
    42                                "No spaces or special characters. " 
    43                                "Usernames and passwords are case sensitive, " 
    44                                "make sure the caps lock key is not enabled. " 
    45                                "This is the name used to log in.")) 
    46  
    47     password = schema.Password( 
    48         title=_(u'label_password', default=u'Password'), 
    49         description=_(u'help_password_creation', 
    50                       default=u'Minimum 5 characters.')) 
    51  
    52     password_ctl = schema.Password( 
    53         title=_(u'label_confirm_password', 
    54                 default=u'Confirm password'), 
    55         description=_(u'help_confirm_password', 
    56                       default=u"Re-enter the password. " 
    57                       "Make sure the passwords are identical.")) 
    58  
    59     mail_me = schema.Bool( 
    60         title=_(u'label_mail_password', 
    61                 default=u"Send a confirmation mail with a link to set the password"), 
    62         default=False) 
    63  
    64  
    65 class IAddUserSchema(Interface): 
    66  
    67     groups = schema.List( 
    68         title=_(u'label_add_to_groups', 
    69                 default=u'Add to the following groups:'), 
    70         description=u'', 
    71         required=False, 
    72         value_type=schema.Choice(vocabulary='Group Ids')) 
    73  
    74  
    75 def FullNameWidget(field, request): 
    76     """Widget for fullname field. 
    77     """ 
    78     field.description = _( 
    79         u'help_full_name_creation', 
    80         default=u"Enter full name, e.g. John Smith.") 
    81     widget = TextWidget(field, request) 
    82     return widget 
    83  
    84  
    85 def EmailWidget(field, request): 
    86     """Widget for email field. 
    87  
    88     Note that the email regular expression that is used for validation 
    89     only allows ascii, so we also use the ASCIIWidget here. 
    90     """ 
    91     field.description = _( 
    92         u'help_email_creation', 
    93         default=u"Enter an email address. " 
    94         "This is necessary in case the password is lost. " 
    95         "We respect your privacy, and will not give the address " 
    96         "away to any third parties or expose it anywhere.") 
    97     widget = ASCIIWidget(field, request) 
    98     return widget 
    99  
    100  
    101 def EmailAsLoginWidget(field, request): 
    102     """Widget for email field when emails are used as login names. 
    103     """ 
    104     field.description = _( 
    105         u'help_email_creation_for_login', 
    106         default=u"Enter an email address. This will be your login name. " 
    107         "We respect your privacy, and will not give the address away to any " 
    108         "third parties or expose it anywhere.") 
    109     widget = ASCIIWidget(field, request) 
    110     return widget 
    111  
    112  
    113 class NoCheckBoxWidget(CheckBoxWidget): 
    114     """ A widget used for _not_ displaying the checkbox. 
    115     """ 
    116  
    117     def __call__(self): 
    118         """Render the widget to HTML.""" 
    119         return "" 
    120  
    121  
    122 def CantChoosePasswordWidget(field, request): 
    123     """ Change the mail_me field widget so it doesn't display the checkbox """ 
    124  
    125     field.title = u'' 
    126     field.readonly = True 
    127     field.description = _( 
    128         u'label_password_change_mail', 
    129         default=u"A URL will be generated and e-mailed to you; " 
    130         "follow the link to reach a page where you can change your " 
    131         "password and complete the registration process.") 
    132     widget = NoCheckBoxWidget(field, request) 
    133     return widget 
    134  
    135  
    136 def CantSendMailWidget(field, request): 
    137     """ Change the mail_me field widget so it displays a warning. 
    138  
    139     This is meant for use in the 'add new user' form for admins to 
    140     tell them that a confirmation email with a password reset link 
    141     cannot be sent because the mailhost has not been setup. 
    142     """ 
    143  
    144     field.title = u'' 
    145     field.readonly = True 
    146     field.description = _( 
    147         u'label_cant_mail_password_reset', 
    148         default=u"Normally we would offer to send the user an email with " 
    149         "instructions to set a password on completion of this form. But this " 
    150         "site does not have a valid email setup. You can fix this in the " 
    151         "Mail settings.") 
    152     widget = NoCheckBoxWidget(field, request) 
    153     return widget 
    154  
    155  
    156 def getGroupIds(context): 
    157     site = getSite() 
    158     groups_tool = getToolByName(site, 'portal_groups') 
    159     groups = groups_tool.listGroups() 
    160     # Get group id, title tuples for each, omitting virtual group 'AuthenticatedUsers' 
    161     groupData = [] 
    162     for g in groups: 
    163         if g.id != 'AuthenticatedUsers': 
    164             groupData.append(('%s (%s)' % (g.getGroupTitleOrName(), g.id), g.id)) 
    165     # Sort by title 
    166     groupData.sort(key=lambda x: x[0].lower()) 
    167     return SimpleVocabulary.fromItems(groupData) 
    168  
    169  
    170 class BaseRegistrationForm(PageForm): 
    171     label = u"" 
    172     description = u"" 
    173  
    174     @property 
    175     def form_fields(self): 
    176         """ form_fields is dynamic in this form, to be able to handle 
    177         different join styles. 
    178         """ 
    179         portal_props = getToolByName(self.context, 'portal_properties') 
    180         props = portal_props.site_properties 
    181         use_email_as_login = props.getProperty('use_email_as_login') 
    182         registration_fields = list(props.getProperty('user_registration_fields')) 
    183  
    184         # Check on required join fields 
    185         if not 'username' in registration_fields and not use_email_as_login: 
    186             registration_fields.insert(0, 'username') 
    187  
    188         if 'username' in registration_fields and use_email_as_login: 
    189             registration_fields.remove('username') 
    190  
    191         if not 'email' in registration_fields: 
    192             # Perhaps only when use_email_as_login is true, but also 
    193             # for some other cases; the email field has always been 
    194             # required. 
    195             registration_fields.append('email') 
    196  
    197         if not 'password' in registration_fields: 
    198             if 'username' in registration_fields: 
    199                 base = registration_fields.index('username') 
    200             else: 
    201                 base = registration_fields.index('email') 
    202             registration_fields.insert(base + 1, 'password') 
    203  
    204         # Add password_ctl after password 
    205         if not 'password_ctl' in registration_fields: 
    206             registration_fields.insert( 
    207                 registration_fields.index('password') + 1, 'password_ctl') 
    208  
    209         # Add email_me after password_ctl 
    210         if not 'mail_me' in registration_fields: 
    211             registration_fields.insert( 
    212                 registration_fields.index('password_ctl') + 1, 'mail_me') 
    213  
    214         # We need fields from both schemata here. 
    215         util = getUtility(IUserDataSchemaProvider) 
    216         schema = util.getSchema() 
    217  
    218         all_fields = form.Fields(schema) + form.Fields(IRegisterSchema) 
    219  
    220         all_fields['fullname'].custom_widget = FullNameWidget 
    221         if use_email_as_login: 
    222             all_fields['email'].custom_widget = EmailAsLoginWidget 
    223         else: 
    224             all_fields['email'].custom_widget = EmailWidget 
    225  
    226         # Make sure some fields are really required; a previous call 
    227         # might have changed the default. 
    228         for name in ('password', 'password_ctl'): 
    229             all_fields[name].field.required = True 
    230  
    231         # Pass the list of join form fields as a reference to the 
    232         # Fields constructor, and return. 
    233         return form.Fields(*[all_fields[id] for id in registration_fields]) 
    234  
    235     # Actions validators 
    236     def validate_registration(self, action, data): 
    237         """ 
    238         specific business logic for this join form 
    239         note: all this logic was taken directly from the old 
    240         validate_registration.py script in 
    241         Products/CMFPlone/skins/plone_login/join_form_validate.vpy 
    242         """ 
    243  
    244         # CSRF protection 
    245         CheckAuthenticator(self.request) 
    246  
    247         registration = getToolByName(self.context, 'portal_registration') 
    248  
    249         errors = super(BaseRegistrationForm, self).validate(action, data) 
    250         # ConversionErrors have no field_name attribute... :-( 
    251         error_keys = [error.field_name for error in errors 
    252                       if hasattr(error, 'field_name')] 
    253  
    254         form_field_names = [f.field.getName() for f in self.form_fields] 
    255  
    256         portal = getUtility(ISiteRoot) 
    257         portal_props = getToolByName(self.context, 'portal_properties') 
    258         props = portal_props.site_properties 
    259         use_email_as_login = props.getProperty('use_email_as_login') 
    260  
    261         # passwords should match 
    262         if 'password' in form_field_names: 
    263             assert('password_ctl' in form_field_names) 
    264             # Skip this check if password fields already have an error 
    265             if not ('password' in error_keys or \ 
    266                     'password_ctl' in error_keys): 
    267                 password = self.widgets['password'].getInputValue() 
    268                 password_ctl = self.widgets['password_ctl'].getInputValue() 
    269                 if password != password_ctl: 
    270                     err_str = _(u'Passwords do not match.') 
    271                     errors.append(WidgetInputError('password', 
    272                                   u'label_password', err_str)) 
    273                     errors.append(WidgetInputError('password_ctl', 
    274                                   u'label_password', err_str)) 
    275                     self.widgets['password'].error = err_str 
    276                     self.widgets['password_ctl'].error = err_str 
    277  
    278         # Password field should have a minimum length of 5 
    279         if 'password' in form_field_names: 
    280             # Skip this check if password fields already have an error 
    281             if not 'password' in error_keys: 
    282                 password = self.widgets['password'].getInputValue() 
    283                 if password and len(password) < 5: 
    284                     err_str = _(u'Passwords must contain at least 5 letters.') 
    285                     errors.append(WidgetInputError( 
    286                             'password', u'label_password', err_str)) 
    287                     self.widgets['password'].error = err_str 
    288  
    289         username = '' 
    290         email = '' 
    291         try: 
    292             email = self.widgets['email'].getInputValue() 
    293         except InputErrors, exc: 
    294             # WrongType? 
    295             errors.append(exc) 
    296         if use_email_as_login: 
    297             username_field = 'email' 
    298             username = email 
    299         else: 
    300             username_field = 'username' 
    301             try: 
    302                 username = self.widgets['username'].getInputValue() 
    303             except InputErrors, exc: 
    304                 errors.append(exc) 
    305  
    306         # check if username is valid 
    307         # Skip this check if username was already in error list 
    308         if not username_field in error_keys: 
    309             portal = getUtility(ISiteRoot) 
    310             if username == portal.getId(): 
    311                 err_str = _(u"This username is reserved. Please choose a " 
    312                             "different name.") 
    313                 errors.append(WidgetInputError( 
    314                         username_field, u'label_username', err_str)) 
    315                 self.widgets[username_field].error = err_str 
    316  
    317  
    318         # check if username is allowed 
    319         if not username_field in error_keys: 
    320             if not registration.isMemberIdAllowed(username): 
    321                 err_str = _(u"The login name you selected is already in use " 
    322                             "or is not valid. Please choose another.") 
    323                 errors.append(WidgetInputError( 
    324                         username_field, u'label_username', err_str)) 
    325                 self.widgets[username_field].error = err_str 
    326  
    327  
    328         # Skip this check if email was already in error list 
    329         if not 'email' in error_keys: 
    330             if 'email' in form_field_names: 
    331                 if not registration.isValidEmail(email): 
    332                     err_str = _(u'You must enter a valid email address.') 
    333                     errors.append(WidgetInputError( 
    334                             'email', u'label_email', err_str)) 
    335                     self.widgets['email'].error = err_str 
    336  
    337         if 'password' in form_field_names and not 'password' in error_keys: 
    338             # Admin can either set a password or mail the user (or both). 
    339             if not (self.widgets['password'].getInputValue() or 
    340                     self.widgets['mail_me'].getInputValue()): 
    341                 err_str = _('msg_no_password_no_mail_me', 
    342                             default=u"You must set a password or choose to " 
    343                             "send an email.") 
    344                 errors.append(WidgetInputError( 
    345                         'password', u'label_password', err_str)) 
    346                 self.widgets['password'].error = err_str 
    347                 errors.append(WidgetInputError( 
    348                         'mail_me', u'label_mail_me', err_str)) 
    349                 self.widgets['mail_me'].error = err_str 
    350         return errors 
    351  
    352     @form.action(_(u'label_register', default=u'Register'), 
    353                  validator='validate_registration', name=u'register') 
    354     def action_join(self, action, data): 
    355         result = self.handle_join_success(data) 
    356         # XXX Return somewhere else, depending on what 
    357         # handle_join_success returns? 
    358         return self.context.unrestrictedTraverse('registered')() 
    359  
    360     def handle_join_success(self, data): 
    361         portal = getUtility(ISiteRoot) 
    362         registration = getToolByName(self.context, 'portal_registration') 
    363         portal_props = getToolByName(self.context, 'portal_properties') 
    364         props = portal_props.site_properties 
    365         use_email_as_login = props.getProperty('use_email_as_login') 
    366  
    367         if use_email_as_login: 
    368             # The username field is not shown as the email is going to 
    369             # be the username, but the field *is* needed further down 
    370             # the line. 
    371             data['username'] = data['email'] 
    372             # Set username in the form; at least needed for logging in 
    373             # immediately when password reset is bypassed. 
    374             self.request.form['form.username'] = data['email'] 
    375  
    376         username = data['username'] 
    377         password = data.get('password') or registration.generatePassword() 
    378  
    379         try: 
    380             registration.addMember(username, password, properties=data, 
    381                                    REQUEST=self.request) 
    382         except (AttributeError, ValueError), err: 
    383             IStatusMessage(self.request).addStatusMessage(err, type="error") 
    384             return 
    385  
    386         if portal.validate_email or data.get('mail_me'): 
    387             # We want to validate the email address (users cannot 
    388             # select their own passwords on the register form) or the 
    389             # admin has explicitly requested to send an email on the 
    390             # 'add new user' form. 
    391             try: 
    392                 # When all goes well, this call actually returns the 
    393                 # rendered mail_password_response template.  As a side 
    394                 # effect, this removes any status messages added to 
    395                 # the request so far, as they are already shown in 
    396                 # this template. 
    397                 response = registration.registeredNotify(username) 
    398                 return response 
    399             except ConflictError: 
    400                 # Let Zope handle this exception. 
    401                 raise 
    402             except Exception: 
    403                 ctrlOverview = getMultiAdapter((self.context, self.request), 
    404                                                name='overview-controlpanel') 
    405                 mail_settings_correct = not ctrlOverview.mailhost_warning() 
    406                 if mail_settings_correct: 
    407                     # The email settings are correct, so the most 
    408                     # likely cause of an error is a wrong email 
    409                     # address.  We remove the account: 
    410                     # Remove the account: 
    411                     self.context.acl_users.userFolderDelUsers( 
    412                         [username], REQUEST=self.request) 
    413                     IStatusMessage(self.request).addStatusMessage( 
    414                         _(u'status_fatal_password_mail', 
    415                           default=u"Failed to create your account: we were " 
    416                           "unable to send instructions for setting a password " 
    417                           "to your email address: ${address}", 
    418                           mapping={u'address': data.get('email', '')}), 
    419                         type='error') 
    420                     return 
    421                 else: 
    422                     # This should only happen when an admin registers 
    423                     # a user.  The admin should have seen a warning 
    424                     # already, but we warn again for clarity. 
    425                     IStatusMessage(self.request).addStatusMessage( 
    426                         _(u'status_nonfatal_password_mail', 
    427                           default=u"This account has been created, but we " 
    428                           "were unable to send instructions for setting a " 
    429                           "password to this email address: ${address}", 
    430                           mapping={u'address': data.get('email', '')}), 
    431                         type='warning') 
    432                     return 
    433  
    434         return 
    435  
    436  
    437 class RegistrationForm(BaseRegistrationForm): 
    438     """ Dynamically get fields from user data, through admin 
    439         config settings. 
    440     """ 
    441     label = _(u'heading_registration_form', default=u'Registration form') 
    442     description = u"" 
    443     template = ViewPageTemplateFile('register_form.pt') 
    444  
    445     @property 
    446     def showForm(self): 
    447         """The form should not be displayed to the user if the system is 
    448            incapable of sending emails and email validation is switched on 
    449            (users are not allowed to select their own passwords). 
    450         """ 
    451         ctrlOverview = getMultiAdapter((self.context, self.request), name='overview-controlpanel') 
    452         portal = getUtility(ISiteRoot) 
    453  
    454         # hide form iff mailhost_warning == True and validate_email == True 
    455         return not (ctrlOverview.mailhost_warning() and portal.getProperty('validate_email', True)) 
    456  
    457     @property 
    458     def form_fields(self): 
    459         if not self.showForm: 
    460             # We do not want to spend time calculating fields that 
    461             # will never get displayed. 
    462             return [] 
    463         portal = getUtility(ISiteRoot) 
    464         defaultFields = super(RegistrationForm, self).form_fields 
    465         # Can the user actually set his/her own password? 
    466         if portal.getProperty('validate_email', True): 
    467             # No? Remove the password fields. 
    468             defaultFields = defaultFields.omit('password', 'password_ctl') 
    469             # Show a message indicating that a password reset link 
    470             # will be mailed to the user. 
    471             defaultFields['mail_me'].custom_widget = CantChoosePasswordWidget 
    472         else: 
    473             # The portal is not interested in validating emails, and 
    474             # the user is not interested in getting an email with a 
    475             # link to set his password if he can set this password in 
    476             # the current form already. 
    477             defaultFields = defaultFields.omit('mail_me') 
    478  
    479         return defaultFields 
    480  
    481  
    482 class AddUserForm(BaseRegistrationForm): 
    483  
    484     label = _(u'heading_add_user_form', default=u'Add New User') 
    485     description = u"" 
    486     template = ViewPageTemplateFile('newuser_form.pt') 
    487  
    488     @property 
    489     def form_fields(self): 
    490         defaultFields = super(AddUserForm, self).form_fields 
    491  
    492         # The mail_me field needs special handling depending on the 
    493         # validate_email property and on the correctness of the mail 
    494         # settings. 
    495         ctrlOverview = getMultiAdapter((self.context, self.request), 
    496                                        name='overview-controlpanel') 
    497         mail_settings_correct = not ctrlOverview.mailhost_warning() 
    498         if not mail_settings_correct: 
    499             defaultFields['mail_me'].custom_widget = CantSendMailWidget 
    500         else: 
    501             # Make the password fields optional: either specify a 
    502             # password or mail the user (or both).  The validation 
    503             # will check that at least one of the options is chosen. 
    504             defaultFields['password'].field.required = False 
    505             defaultFields['password_ctl'].field.required = False 
    506             portal = getUtility(ISiteRoot) 
    507             if portal.getProperty('validate_email', True): 
    508                 defaultFields['mail_me'].field.default = True 
    509             else: 
    510                 defaultFields['mail_me'].field.default = False 
    511  
    512         # Append the manager-focused fields 
    513         allFields = defaultFields + form.Fields(IAddUserSchema) 
    514  
    515         allFields['groups'].custom_widget = MultiCheckBoxVocabularyWidget 
    516  
    517         return allFields 
    518  
    519     @form.action(_(u'label_register', default=u'Register'), 
    520                  validator='validate_registration', name=u'register') 
    521     def action_join(self, action, data): 
    522         errors = super(AddUserForm, self).handle_join_success(data) 
    523         portal_groups = getToolByName(self.context, 'portal_groups') 
    524  
    525         securityManager = getSecurityManager() 
    526         canManageUsers = securityManager.checkPermission('Manage users', 
    527                                                          self.context) 
    528         username = data['username'] 
    529  
    530         try: 
    531             # Add user to the selected group(s) 
    532             if 'groups' in data.keys() and canManageUsers: 
    533                 for groupname in data['groups']: 
    534                     group = portal_groups.getGroupById(groupname) 
    535                     group.addMember(username, REQUEST=self.request) 
    536         except (AttributeError, ValueError), err: 
    537             IStatusMessage(self.request).addStatusMessage(err, type="error") 
    538             return 
    539  
    540         IStatusMessage(self.request).addStatusMessage( 
    541             _(u"User added."), type='info') 
    542         self.request.response.redirect( 
    543             self.context.absolute_url() + 
    544             '/@@usergroup-userprefs?searchstring=' + username) 
     7class CaptchaAddUserForm(RegistrationForm): 
     8    """Add user form with captacha.""" 
Note: See TracChangeset for help on using the changeset viewer.