diff --git a/edbob/pyramid/static/css/edbob.css b/edbob/pyramid/static/css/edbob.css
index 5417a07..e09bcb0 100644
--- a/edbob/pyramid/static/css/edbob.css
+++ b/edbob/pyramid/static/css/edbob.css
@@ -142,6 +142,10 @@ body > #container {
margin: 8px 20px auto auto;
}
+#login a.username {
+ font-weight: bold;
+}
+
#user-menu {
float: left;
}
diff --git a/edbob/pyramid/static/css/forms.css b/edbob/pyramid/static/css/forms.css
index 3047756..b698cfd 100644
--- a/edbob/pyramid/static/css/forms.css
+++ b/edbob/pyramid/static/css/forms.css
@@ -38,11 +38,18 @@ div.fieldset {
div.field-wrapper {
clear: both;
- overflow: auto;
min-height: 30px;
+ overflow: auto;
+ padding: 5px;
+}
+
+div.field-wrapper.error {
+ background-color: #ddcccc;
+ border: 2px solid #dd6666;
}
div.field-wrapper label {
+ color: #000000;
display: block;
float: left;
width: 140px;
diff --git a/edbob/pyramid/templates/edbob/base.mako b/edbob/pyramid/templates/base.mako
similarity index 96%
rename from edbob/pyramid/templates/edbob/base.mako
rename to edbob/pyramid/templates/base.mako
index f3af0bf..b026322 100644
--- a/edbob/pyramid/templates/edbob/base.mako
+++ b/edbob/pyramid/templates/base.mako
@@ -37,7 +37,7 @@
% if request.user:
- logged in as
${request.user.display_name}
+ ${h.link_to(request.user.display_name, url('change_password'), class_='username')}
(${h.link_to("logout", url('logout'))})
% else:
${h.link_to("login", url('login'))}
diff --git a/edbob/pyramid/templates/change_password.mako b/edbob/pyramid/templates/change_password.mako
new file mode 100644
index 0000000..e00d2a5
--- /dev/null
+++ b/edbob/pyramid/templates/change_password.mako
@@ -0,0 +1,15 @@
+<%inherit file="/base.mako" />
+
+<%def name="title()">Change Password%def>
+
+
diff --git a/edbob/pyramid/views/auth.py b/edbob/pyramid/views/auth.py
index 09344aa..3f55619 100644
--- a/edbob/pyramid/views/auth.py
+++ b/edbob/pyramid/views/auth.py
@@ -26,17 +26,50 @@
``edbob.pyramid.views.auth`` -- Auth Views
"""
-import formencode
from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
+
+import formencode
from pyramid_simpleform import Form
-from pyramid_simpleform.renderers import FormRenderer
+import pyramid_simpleform.renderers
+
+from webhelpers.html import tags
+from webhelpers.html.builder import HTML
import edbob
-from edbob.db.auth import authenticate_user
+from edbob.db.auth import authenticate_user, set_user_password
from edbob.pyramid import Session
+from edbob.util import prettify
+class FormRenderer(pyramid_simpleform.renderers.FormRenderer):
+ """
+ Customized form renderer. Provides some extra methods for convenience.
+ """
+
+ # Note that as of this writing, this renderer is used only by the
+ # ``change_password`` view. This should probably change, and this class
+ # definition should be moved elsewhere.
+
+ def field_div(self, name, field, label=None):
+ errors = self.errors_for(name)
+ if errors:
+ errors = [HTML.tag('div', class_='field-error', c=x) for x in errors]
+ errors = tags.literal('').join(errors)
+
+ label = HTML.tag('label', for_=name, c=label or prettify(name))
+ inner = HTML.tag('div', class_='field', c=field)
+
+ outer_class = 'field-wrapper'
+ if errors:
+ outer_class += ' error'
+ outer = HTML.tag('div', class_=outer_class, c=(errors or '') + label + inner)
+ return outer
+
+ def referrer_field(self):
+ return self.hidden('referrer', value=self.form.request.get_referrer())
+
+
class UserLogin(formencode.Schema):
allow_extra_fields = True
filter_extra_fields = True
@@ -92,6 +125,47 @@ def logout(request):
return HTTPFound(location=referrer, headers=headers)
+class CurrentPasswordCorrect(formencode.validators.FancyValidator):
+
+ def _to_python(self, value, state):
+ user = state
+ if not authenticate_user(user.username, value, session=Session()):
+ raise formencode.Invalid("The password is incorrect.", value, state)
+ return value
+
+
+class ChangePassword(formencode.Schema):
+
+ allow_extra_fields = True
+ filter_extra_fields = True
+
+ current_password = formencode.All(
+ formencode.validators.NotEmpty(),
+ CurrentPasswordCorrect())
+
+ new_password = formencode.validators.NotEmpty()
+ confirm_password = formencode.validators.NotEmpty()
+
+ chained_validators = [formencode.validators.FieldsMatch(
+ 'new_password', 'confirm_password')]
+
+
+def change_password(request):
+ """
+ Allows a user to change his or her password.
+ """
+
+ if not request.user:
+ return HTTPFound(location=request.route_url('home'))
+
+ form = Form(request, schema=ChangePassword, state=request.user)
+ if form.validate():
+ set_user_password(request.user, form.data['new_password'])
+ return HTTPFound(location=request.get_referrer())
+
+ return {'form': FormRenderer(form)}
+
+
def includeme(config):
config.add_route('login', '/login')
@@ -99,3 +173,6 @@ def includeme(config):
config.add_route('logout', '/logout')
config.add_view(logout, route_name='logout')
+
+ config.add_route('change_password', '/change-password')
+ config.add_view(change_password, route_name='change_password', renderer='/change_password.mako')