Commit c6a2d574 authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

* use fields processing method to check the form

* proper validation error handling using default cw mecanism
* rewrite tests

rely on cw 3.7.2

--HG--
branch : stable
parent 13e2af3e6a45
......@@ -9,7 +9,7 @@ msgstr ""
#, python-format
msgid ""
"\n"
"Hello %(firstname)s %(surname)s,\n"
"Hello %(firstname-subject)s %(surname-subject)s,\n"
"\n"
"thanks for registering on %(base_url)s.\n"
"\n"
......@@ -25,8 +25,8 @@ msgstr ""
#, python-format
msgid ""
"Congratulations, your registration is complete. Welcome %(firstname)s %"
"(surname)s !"
"Congratulations, your registration is complete. Welcome %(firstname-subject)"
"s %(surname-subject)s !"
msgstr ""
msgid "Invalid registration data. Please try registering again."
......@@ -40,20 +40,11 @@ msgstr ""
msgid "captcha"
msgstr ""
msgid "i18n_register_user"
msgstr "s'enregistrer"
msgid "incorrect captcha value"
msgstr ""
msgid "i18n_register_login"
msgstr "login"
msgid "passwords are different"
msgstr ""
msgid "i18n_register_user"
msgstr "register"
msgid "please copy the letters from the image"
msgstr ""
msgid "required attribute"
msgstr ""
msgid "unable to check captcha, please try again"
msgstr ""
......@@ -9,7 +9,7 @@ msgstr ""
#, python-format
msgid ""
"\n"
"Hello %(firstname)s %(surname)s,\n"
"Hello %(firstname-subject)s %(surname-subject)s,\n"
"\n"
"thanks for registering on %(base_url)s.\n"
"\n"
......@@ -19,7 +19,7 @@ msgid ""
"See you soon on %(base_url)s !\n"
msgstr ""
"\n"
"Bonjour %(firstname)s %(surname)s,\n"
"Bonjour %(firstname-subject)s %-subject(surname)s,\n"
"\n"
"Merci de vous être enregistré sur %(base_url)s.\n"
"\n"
......@@ -34,11 +34,11 @@ msgstr "Confirmez votre enregistrement sur %(base_url)s"
#, python-format
msgid ""
"Congratulations, your registration is complete. Welcome %(firstname)s %"
"(surname)s !"
"Congratulations, your registration is complete. Welcome %(firstname-subject)"
"s %(surname-subject)s !"
msgstr ""
"Félicitations, vote compte est maintenant activé. Bienvenue %(firstname)s %"
"(surname)s !"
"Félicitations, vote compte est maintenant activé. Bienvenue %(firstname-"
"subject)s %(surname-subject)s !"
msgid "Invalid registration data. Please try registering again."
msgstr ""
......@@ -54,20 +54,11 @@ msgstr ""
msgid "captcha"
msgstr "captcha"
msgid "i18n_register_login"
msgstr "identifiant"
msgid "i18n_register_user"
msgstr "s'enregistrer"
msgid "incorrect captcha value"
msgstr "valeur de captcha incorrecte"
msgid "passwords are different"
msgstr "le mot de passe et la confirmation diffèrent"
msgid "please copy the letters from the image"
msgstr "veuillez copier les lettres de l'image."
msgid "required attribute"
msgstr "attribut requis"
msgid "unable to check captcha, please try again"
msgstr "impossible de vérifier le captcha, veuillez réessayer"
......@@ -12,122 +12,143 @@ class RegistrationTC(CubicWebTC):
captcha_value = u'captcha value'
data = {'firstname': u'Toto', 'surname': u'Toto',
'use_email': u'toto@secondweb.fr',
'login': u'toto',
'upassword': 'toto', 'upassword-confirm': 'toto',
data = {'firstname-subject': u'Toto', 'surname-subject': u'Toto',
'address-subject': u'toto@secondweb.fr',
'login-subject': u'toto',
'upassword-subject': 'toto',
'upassword-subject-confirm': 'toto',
'captcha': captcha_value}
def setup_database(self):
self.config.global_set_option('registration-cypher-seed', u'dummy cypher key')
super(RegistrationTC, self).setup_database()
def _check_user_not_created(self):
self.restore_connection()
rset = self.execute('CWUser X WHERE X login %(login)s',
{'login': self.data['login-subject']})
self.failIf(rset)
def _check_error(self, req, path,
expected_path='registration',
expected_errors=None,
expected_params=None,
expected_formvalues=None):
path, params = self.expect_redirect_publish(req, path)
self.assertEquals(path, expected_path)
self.assertEquals(params, expected_params or {})
forminfo = req.get_session_data('registration')
if forminfo is None:
self.failIf(expected_errors or expected_formvalues)
else:
self.assertEquals(forminfo['eidmap'], {})
self.assertEquals(forminfo['values'], expected_formvalues or {})
error = forminfo['error']
self.assertEquals(error.entity, None)
self.assertEquals(error.errors, expected_errors or {})
def _posted_form(self, *skipkeys):
data = self.data.copy()
for key in skipkeys:
data.pop(key, None)
if not '__errorurl' in skipkeys:
data['__errorurl'] = 'registration'
return data
def test_registration_form(self):
req = self.request()
req.form = {'firstname': u'Toto'}
req.form = {'firstname-subject': u'Toto'}
pageinfo = self.view('registration', req=req, rset=None)
ns = {'ns': pageinfo.default_ns}
# check form field names
names = pageinfo.etree.xpath('//ns:form[@id="registrationForm"]//ns:input[@type!="hidden"]/@name',
namespaces=ns)
namespaces=ns)
self.assertEquals(set(names), set(self.data))
# check form field value
firstname = pageinfo.etree.xpath('//ns:input[@name="firstname"]/@value',
firstname = pageinfo.etree.xpath('//ns:input[@name="firstname-subject"]/@value',
namespaces=ns)
self.assertEquals(firstname, [req.form['firstname']])
def _check_redirect(self, ctrl, urlpath, **urlparams):
try:
ctrl._cw.set_session_data('captcha', self.captcha_value)
ctrl.publish()
self.fail('should redirect to ' + urlpath)
except Redirect, ex:
url, params = (ex.location.split('?')+[''])[:2]
self.assertEquals(url, self.config['base-url'] + urlpath)
self.assertDictEquals(dict(cgi.parse_qsl(params)), urlparams)
self.assertEquals(firstname, [req.form['firstname-subject']])
def test_send_mail_ok(self):
self.login('anon')
req = self.request()
req.form = self.data.copy()
ctrl = self.vreg['controllers'].select('registration_sendmail', req,
appli=self.app)
# check redirect
self._check_redirect(ctrl, '', __message='Your registration email has been sent. Follow instructions in there to activate your account.')
req.form = self._posted_form()
req.set_session_data('captcha', self.captcha_value)
path, params = self.expect_redirect_publish(req, 'registration_sendmail')
self.assertEquals(path, '')
self.assertEquals(params, {'__message': 'Your registration email has been sent. Follow instructions in there to activate your account.'})
# check email contains activation url...
URL_RE = re.compile('(%s[^.]+)\.' % self.config['base-url'])
text = MAILBOX[-1].message.get_payload(decode=True)
url = URL_RE.search(text).group(1)
# ... and the registration key contains all data
key = dict(cgi.parse_qsl(urlparse.urlsplit(url)[3]))['key']
d = self.data.copy()
d.pop('upassword-confirm')
d = self._posted_form('upassword-subject-confirm')
self.assertDictEquals(decrypt(key, self.config['registration-cypher-seed']), d)
def test_send_mail_failure(self):
req = self.request()
for param, msg in (('login', 'required attribute'),
('captcha', 'incorrect captcha value')):
req.form = self.data.copy()
req.form.pop(param)
ctrl = self.vreg['controllers'].select('registration_sendmail',
req, appli=self.app)
ctrl._cw.set_session_data('captcha', self.captcha_value)
try:
ctrl.publish()
self.fail('should raise ValidationError')
except ValidationError, e:
self.assertDictEquals(e.errors, {param: msg})
# check captcha has expired
self.assertEquals(ctrl._cw.get_session_data('captcha'), None)
def _confirm_ctrl(self, key=None):
for param, msg, val in (('login-subject', 'required field', None),
('captcha', 'required field', None),
('captcha', 'incorrect captcha value', 'abc')):
req = self.request()
if val is None:
req.form = self._posted_form(param)
else:
req.form = self._posted_form()
req.form[param] = val
req.set_session_data('captcha', self.captcha_value)
#expected
self._check_error(req, 'registration_sendmail',
expected_formvalues=req.form,
expected_errors={param: msg})
self.assertEquals(req.get_session_data('captcha'), None)
def _confirm_req(self, key=None):
self.login('anon')
req = self.request()
req.form = {'key': key or encrypt(self.data, self.config['registration-cypher-seed'])}
return self.vreg['controllers'].select('registration_confirm', req,
appli=self.app)
data = self._posted_form('upassword-subject-confirm')
if key is None:
key = encrypt(data, self.config['registration-cypher-seed'])
req.form = {'key': key}
return req
#self.vreg['controllers'].select('registration_confirm', req,
#appli=self.app)
def test_confirm_ok(self):
ctrl = self._confirm_ctrl()
self._check_redirect(ctrl, '', __message=u'Congratulations, your registration is complete. Welcome %(firstname)s %(surname)s !' % self.data)
ctrl._cw.cnx.commit()
ctrl._cw.cnx.close()
req = self._confirm_req()
path, params = self.expect_redirect_publish(req, 'registration_confirm')
self.assertEquals(path, '')
self.assertEquals(params, {'__message': u'Congratulations, your registration is complete. Welcome %(firstname-subject)s %(surname-subject)s !' % self.data})
self.restore_connection()
rset = self.execute('Any U WHERE U login %(login)s, U firstname %(firstname)s, '
'U surname %(surname)s, U use_email M, M address %(use_email)s',
self.data)
'U surname %(surname)s, U use_email M, M address %(address)s',
dict((k.replace('-subject', ''), v)
for k, v in self.data.items()))
self.failUnless(rset.rowcount)
def _check_user_not_created(self):
self.restore_connection()
self.failIf(self.execute('CWUser X WHERE X login %(login)s', self.data).rowcount)
def test_confirm_failure_login_already_used(self):
self.create_user(self.data['login'])
self.create_user(self.data['login-subject'])
self.commit()
ctrl = self._confirm_ctrl()
# check user is redirected to a url without its password in the url params
d = self.data.copy()
d.pop('login'), d.pop('upassword'), d.pop('upassword-confirm')
d['__message'] = u'login : the value "%(login)s" is already used, use another one' % self.data
self._check_redirect(ctrl, 'register', **d)
req = self._confirm_req()
self._check_error(req, 'registration_confirm',
expected_formvalues=self._posted_form('upassword-subject',
'upassword-subject-confirm'),
expected_errors={'login-subject': 'the value "%(login-subject)s" is already used, use another one' % self.data})
def test_confirm_failure_invalid_data(self):
ctrl = self._confirm_ctrl(u'dummykey')
self._check_redirect(ctrl, 'register', __message=u'Invalid registration data. Please try registering again.')
req = self._confirm_req('dummykey')
self._check_error(req, 'registration_confirm', 'register',
expected_params={'__message': 'Invalid registration data. '
'Please try registering again.'})
self._check_user_not_created()
def test_confirm_failure_email_already_used(self):
self.create_user('test')
self.execute('INSERT EmailAddress X: U use_email X, X address %(use_email)s '
'WHERE U login "test"', self.data)
self.execute('INSERT EmailAddress X: U use_email X, X address %(address)s '
'WHERE U login "test"', {'address': self.data['address-subject']})
self.commit()
ctrl = self._confirm_ctrl()
d = self.data.copy()
d.pop('login'), d.pop('upassword'), d.pop('upassword-confirm')
d['__message'] = u'address : the value "%(use_email)s" is already used, use another one' % self.data
self._check_redirect(ctrl, 'register', **d)
req = self._confirm_req()
req.form['__errorurl'] = 'registration'
self._check_user_not_created()
if __name__ == '__main__':
......
"""anonymous registration form and views
:organization: Logilab / SecondWeb
:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"
from logilab.common.decorators import clear_cache
from yams.schema import role_name
from cubicweb import mail, crypto
from cubicweb.view import StartupView
from cubicweb.web import Redirect, ValidationError
from cubicweb.web import Redirect, ValidationError, ProcessFormError
from cubicweb.web import controller, form, captcha
from cubicweb.web import formwidgets as fw, formfields as ff
from cubicweb.web.views import forms, basecomponents, urlrewrite
def qname(attr):
return role_name(attr, 'subject')
class RegistrationForm(forms.FieldsForm):
......@@ -18,18 +31,23 @@ class RegistrationForm(forms.FieldsForm):
def action(self):
return self._cw.build_url(u'registration_sendmail')
login = ff.StringField(widget=fw.TextInput(), required=True)
upassword = ff.StringField(widget=fw.PasswordInput(), required=True)
use_email = ff.StringField(widget=fw.TextInput(), required=True)
firstname = ff.StringField(widget=fw.TextInput())
surname = ff.StringField(widget=fw.TextInput())
# properly name fields according to validation errors that may be raised by
# Repository.register_user
login = ff.StringField(widget=fw.TextInput(), role='subject',
# we don't want to see 'authenticate'
label=_('i18n_register_login'),
required=True)
upassword = ff.StringField(widget=fw.PasswordInput(), role='subject',
required=True)
address = ff.StringField(widget=fw.TextInput(), role='subject',
required=True)
firstname = ff.StringField(widget=fw.TextInput(), role='subject')
surname = ff.StringField(widget=fw.TextInput(), role='subject')
captcha = ff.StringField(widget=captcha.CaptchaWidget(), required=True,
label=_('captcha'),
help=_('please copy the letters from the image'))
# XXX move captcha validation to a CaptchaField
class RegistrationFormView(form.FormViewMixIn, StartupView):
__regid__ = 'registration'
......@@ -37,11 +55,10 @@ class RegistrationFormView(form.FormViewMixIn, StartupView):
form = self._cw.vreg['forms'].select('registration', self._cw)
self.w(form.render(display_progress_div=False))
class RegistrationSendMailController(controller.Controller):
__regid__ = 'registration_sendmail'
content = _(u'''
Hello %(firstname)s %(surname)s,
Hello %(firstname-subject)s %(surname-subject)s,
thanks for registering on %(base_url)s.
......@@ -54,7 +71,7 @@ See you soon on %(base_url)s !
def publish(self, rset=None):
data = self.checked_data()
recipient = data['use_email']
recipient = data[qname('address')]
msg = self.build_email(recipient, data)
self._cw.vreg.config.sendmails([(msg, (recipient,))])
raise Redirect(self.success_redirect_url())
......@@ -63,37 +80,36 @@ See you soon on %(base_url)s !
'''only basic data check here (required attributes and password
confirmation check)
'''
fieldsform = self._cw.vreg['forms'].select('registration', self._cw)
form = self._cw.vreg['forms'].select('registration', self._cw)
form.formvalues = {} # init fields value cache
data = {}
errors = {}
for field in fieldsform._fields_:
value = self._cw.form.get(field.name, u'').strip()
if not value:
if field.required:
errors[field.name] = self._cw._('required attribute')
data[field.name] = value
if data['upassword'] != self._cw.form.get('upassword-confirm'):
errors['upassword'] = _('passwords are different')
captcha = self._cw.get_session_data('captcha', None, pop=True)
if captcha is None:
errors[None] = self._cw._('unable to check captcha, please try again')
elif data['captcha'].lower() != captcha.lower():
errors['captcha'] = self._cw._('incorrect captcha value')
for field in form.fields:
try:
for field, value in field.process_posted(form):
if value is not None:
data[field.role_name()] = value
except ProcessFormError, exc:
errors[field.role_name()] = unicode(exc)
if errors:
raise ValidationError(None, errors)
return data
def build_email(self, recipient, data):
if not data.get('firstname') or data.get('surtname'):
data['firstname'] = data['login']
activationurl = self.activation_url(data) # build url before modifying data
data.setdefault(qname('firstname'), '')
data.setdefault(qname('surname'), '')
if not (data.get(qname('firstname')) or data.get(qname('surname'))):
data[qname('firstname')] = data[qname('login')]
data.update({'base_url': self._cw.vreg.config['base-url'],
'url': self.activation_url(data)})
'url': activationurl})
content = self._cw._(self.content) % data
subject = self._cw._(self.subject) % data
return mail.format_mail({}, [recipient], content=content,
subject=subject, config=self._cw.vreg.config)
def activation_url(self, data):
data.pop(qname('upassword') + '-confirm', None)
key = crypto.encrypt(data, self._cw.vreg.config['registration-cypher-seed'])
return self._cw.build_url('registration_confirm', key=key)
......@@ -111,20 +127,20 @@ class RegistrationConfirmController(controller.Controller):
try:
data = crypto.decrypt(req.form['key'],
req.vreg.config['registration-cypher-seed'])
self.debug('registration data: %s', data)
login = data[qname('login')]
password = data.pop(qname('upassword'))
except:
msg = req._(u'Invalid registration data. Please try registering again.')
raise Redirect(req.build_url(u'register', __message=msg))
login, password = data.pop('login'), data.pop('upassword')
try:
self.appli.repo.register_user(login, password, email=data['use_email'],
firstname=data['firstname'],
surname=data['surname'])
except ValidationError, e:
raise Redirect(self.failure_redirect_url(data, e.errors))
req.form = data # hijack for proper validation error handling
self.appli.repo.register_user(login, password,
email=data.get(qname('address')),
firstname=data.get(qname('firstname')),
surname=data.get(qname('surname')))
# force new authentication (anon until there)
clear_cache(req, 'get_authorization')
req.form['__login'] = login
req.form['__password'] = password
clear_cache(req, 'get_authorization') # force new authentication (anon until there)
if req.cnx:
req.cnx.close()
req.cnx = None
......@@ -135,17 +151,9 @@ class RegistrationConfirmController(controller.Controller):
assert req.user.login == login
raise Redirect(self.success_redirect_url(data))
def failure_redirect_url(self, data, errors):
# be sure not to get a password in your redirect url
data.pop('upassword', None)
data.pop('upassword-confirm', None)
data['__message'] = u'<br/>'.join(u'%s : %s' % (k,v)
for (k,v) in errors.iteritems())
return self._cw.build_url(u'register', **data)
def success_redirect_url(self, data):
msg = self._cw._(u'Congratulations, your registration is complete. '
'Welcome %(firstname)s %(surname)s !')
'Welcome %(firstname-subject)s %(surname-subject)s !')
return self._cw.build_url('', __message=msg%data)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment