Index: main/waeup.sirp/trunk/src/waeup/sirp/authentication.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/authentication.py	(revision 7146)
+++ main/waeup.sirp/trunk/src/waeup/sirp/authentication.py	(revision 7147)
@@ -9,5 +9,6 @@
 from zope.securitypolicy.interfaces import IPrincipalRoleManager
 from zope.securitypolicy.principalrole import principalRoleManager
-from waeup.sirp.interfaces import IUserAccount, IAuthPluginUtility
+from waeup.sirp.interfaces import (
+    IUserAccount, IAuthPluginUtility, IPasswordValidator)
 
 def setup_authentication(pau):
@@ -170,4 +171,16 @@
         site = grok.getSite()
         return site['users']
+
+class PasswordValidator(grok.GlobalUtility):
+
+  grok.implements(IPasswordValidator)
+
+  def validate_password(self, pw, pw_repeat):
+       errors = []
+       if len(pw) < 3:
+         errors.append('Password must have at least 3 chars.')
+       if pw != pw_repeat:
+         errors.append('Passwords do not match.')
+       return errors
 
 @grok.subscribe(IUserAccount, grok.IObjectRemovedEvent)
Index: main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py	(revision 7146)
+++ main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py	(revision 7147)
@@ -195,4 +195,13 @@
         value_type = schema.Choice(source=RoleSource()))
 
+class IPasswordValidator(Interface):
+    """A password validator utility.
+    """
+
+    def validate_password(password, password_repeat):
+        """Validates a password by comparing it with
+        control password and checking some other requirements.
+        """
+
 
 class IUserContainer(IWAeUPObject):
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py	(revision 7146)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py	(revision 7147)
@@ -33,5 +33,6 @@
 from zope.session.interfaces import ISession
 from waeup.sirp.authentication import PrincipalInfo, get_principal_role_manager
-from waeup.sirp.interfaces import IAuthPluginUtility, IUserAccount
+from waeup.sirp.interfaces import (
+    IAuthPluginUtility, IUserAccount, IPasswordValidator)
 from waeup.sirp.students.interfaces import IStudent
 
@@ -228,11 +229,7 @@
             return None
 
-        if password != password_repeat:
-            # At least protect against erraneous password input
-            return None
-
-        if len(password) < 3:
-            # XXX: these checks should be generalized somehow, as we
-            # do the same stuff in password setting views.
+        validator = getUtility(IPasswordValidator)
+        errors = validator.validate_password(password, password_repeat)
+        if errors:
             return None
 
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 7146)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 7147)
@@ -34,5 +34,6 @@
 from waeup.sirp.browser.viewlets import (
     ManageActionButton, PrimaryNavTab, AddActionButton)
-from waeup.sirp.interfaces import IWAeUPObject, IUserAccount, IExtFileStore
+from waeup.sirp.interfaces import (
+    IWAeUPObject, IUserAccount, IExtFileStore, IPasswordValidator)
 from waeup.sirp.widgets.datewidget import (
     FriendlyDateWidget, FriendlyDateDisplayWidget,
@@ -53,5 +54,5 @@
 from waeup.sirp.students.utils import (
     get_payment_details, get_accommodation_details, select_bed,
-    render_pdf, validatePassword)
+    render_pdf)
 from waeup.sirp.browser.resources import toggleall
 from waeup.sirp.authentication import get_principal_role_manager
@@ -408,36 +409,24 @@
         password_ctl = form.get('control_password', None)
         if password:
-            if (password != password_ctl):
-                self.flash('Passwords do not match.')
-            else:
-                # XXX: This is too early. PW should only be saved if there
-                #      are no (other) errors left in form.
-                IUserAccount(self.context).setPassword(password)
-                write_log_message(self, 'password changed')
-
-        # The following is now done by contextual_reg_num_source validation
-        #self.reg_number = form.get('form.reg_number', None)
-        #if self.reg_number:
-        #    hitlist = search(query=self.reg_number,searchtype='reg_number', view=self)
-        #    if hitlist and hitlist[0].student_id != self.context.student_id:
-        #        self.flash('Registration number exists.')
-        #        return
-        #self.matric_number = form.get('form.matric_number', None)
-        #if self.matric_number:
-        #    hitlist = search(query=self.matric_number,
-        #        searchtype='matric_number', view=self)
-        #    if hitlist and hitlist[0].student_id != self.context.student_id:
-        #        self.flash('Matriculation number exists.')
-        #        return
-
+            validator = getUtility(IPasswordValidator)
+            errors = validator.validate_password(password, password_ctl)
+            if errors:
+                self.flash( ' '.join(errors))
+                return
+        changed_fields = self.applyData(self.context, **data)
         # Turn list of lists into single list
-        changed_fields = self.applyData(self.context, **data)
         if changed_fields:
             changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
-        fields_string = ' + '.join(changed_fields)
-        self.context._p_changed = True
+        else:
+            changed_fields = []
+        if password:
+            # Now we know that the form has no errors and can set password ...
+            IUserAccount(self.context).setPassword(password)
+            changed_fields.append('password')
+        # ... and execute transition
         if form.has_key('transition') and form['transition']:
             transition_id = form['transition']
             self.wf_info.fireTransition(transition_id)
+        fields_string = ' + '.join(changed_fields)
         self.flash('Form has been saved.')
         if fields_string:
@@ -1523,14 +1512,12 @@
         password_ctl = form.get('change_password_repeat', None)
         if password:
-            if (password != password_ctl):
-                self.flash('Passwords do not match.')
+            validator = getUtility(IPasswordValidator)
+            errors = validator.validate_password(password, password_ctl)
+            if not errors:
+                IUserAccount(self.context).setPassword(password)
+                write_log_message(self, 'saved: password')
+                self.flash('Password changed.')
             else:
-                error = validatePassword(password)
-                if not error:
-                    IUserAccount(self.context).setPassword(password)
-                    write_log_message(self, 'password changed')
-                    self.flash('Password changed.')
-                else:
-                    self.flash(error)
+                self.flash( ' '.join(errors))
         return
 
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 7146)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 7147)
@@ -657,5 +657,5 @@
             name="change_password_repeat").value = 'pw'
         self.browser.getControl("Save").click()
-        self.assertTrue('Value is too short' in self.browser.contents)
+        self.assertTrue('Password must have at least' in self.browser.contents)
         self.browser.getControl(name="change_password").value = 'new_password'
         self.browser.getControl(
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py	(revision 7146)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py	(revision 7147)
@@ -33,9 +33,4 @@
     student['studycourse'].previous_verdict = verdict
     return
-
-def validatePassword(password):
-    if len(password) < 4:
-        return u'Value is too short.'
-    return None
 
 # To be specified in customization packages, see also the view which
