1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 The University of Melbourne
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# Author: Matt Giuca, Will Grant
21
import formencode.validators
22
from genshi.filters import HTMLFormFiller
24
from ivle.webapp import ApplicationRoot
25
from ivle.webapp.base.forms import BaseFormView
26
from ivle.webapp.base.xhtml import XHTMLView
27
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
28
from ivle.webapp.admin.publishing import root_to_user, user_url
29
from ivle.database import User
33
class UsersView(XHTMLView):
34
"""A list of all IVLE users."""
35
template = 'templates/users.html'
37
breadcrumb_text = 'Users'
39
def authorize(self, req):
40
return req.user and req.user.admin
42
def populate(self, req, ctx):
44
ctx['users'] = req.store.find(User).order_by(User.login)
47
class UserEditSchema(formencode.Schema):
48
nick = formencode.validators.UnicodeString(not_empty=True)
49
email = formencode.validators.Email(not_empty=False,
52
class UserEditView(XHTMLView):
53
"""A form to change a user's details."""
54
template = 'templates/user-edit.html'
58
def filter(self, stream, ctx):
59
return stream | HTMLFormFiller(data=ctx['data'])
61
def populate(self, req, ctx):
62
if req.method == 'POST':
63
data = dict(req.get_fieldstorage())
65
validator = UserEditSchema()
66
data = validator.to_python(data, state=req)
67
self.context.nick = data['nick']
68
self.context.email = unicode(data['email']) if data['email'] \
71
req.throw_redirect(req.uri)
72
except formencode.Invalid, e:
73
errors = e.unpack_errors()
75
data = {'nick': self.context.nick,
76
'email': self.context.email
80
ctx['format_datetime'] = ivle.date.make_date_nice
81
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
84
ctx['user'] = self.context
86
ctx['errors'] = errors
88
class UserAdminSchema(formencode.Schema):
89
admin = formencode.validators.StringBoolean(if_missing=False)
90
disabled = formencode.validators.StringBoolean(if_missing=False)
91
fullname = formencode.validators.UnicodeString(not_empty=True)
92
studentid = formencode.validators.UnicodeString(not_empty=False,
96
class UserAdminView(BaseFormView):
97
"""A form for admins to change more of a user's details."""
98
template = 'templates/user-admin.html'
101
def authorize(self, req):
102
"""Only allow access if the requesting user is an admin."""
103
return req.user and req.user.admin
107
return UserAdminSchema()
109
def get_default_data(self, req):
110
return {'admin': self.context.admin,
111
'disabled': self.context.state == u'disabled',
112
'fullname': self.context.fullname,
113
'studentid': self.context.studentid,
116
def save_object(self, req, data):
117
if self.context is req.user:
118
# Admin checkbox is disabled -- assume unchanged
119
data['admin'] = self.context.admin
120
data['disabled'] = self.context.state == u'disabled'
122
self.context.admin = data['admin']
123
if self.context.state in (u'enabled', u'disabled'):
124
self.context.state = (u'disabled' if data['disabled']
126
self.context.fullname = data['fullname'] \
127
if data['fullname'] else None
128
self.context.studentid = data['studentid'] \
129
if data['studentid'] else None
133
def populate(self, req, ctx):
134
super(UserAdminView, self).populate(req, ctx)
136
# Disable the admin checkbox if editing oneself
137
ctx['disable_admin'] = self.context is req.user
139
class PasswordChangeView(XHTMLView):
140
"""A form to change a user's password, with knowledge of the old one."""
141
template = 'templates/user-password-change.html'
145
def authorize(self, req):
146
"""Only allow access if the requesting user holds the permission,
147
and the target user has a password set. Otherwise we might be
148
clobbering external authn.
150
return super(PasswordChangeView, self).authorize(req) and \
151
self.context.passhash is not None
153
def populate(self, req, ctx):
155
if req.method == 'POST':
156
data = dict(req.get_fieldstorage())
157
if data.get('old_password') is None or \
158
not self.context.authenticate(data.get('old_password')):
159
error = 'Incorrect password.'
160
elif data.get('new_password') != data.get('new_password_again'):
161
error = 'New passwords do not match.'
162
elif not data.get('new_password'):
163
error = 'New password cannot be empty.'
165
self.context.password = data['new_password']
167
req.throw_redirect(req.uri)
170
ctx['user'] = self.context
173
class PasswordResetView(XHTMLView):
174
"""A form to reset a user's password, without knowledge of the old one."""
175
template = 'templates/user-password-reset.html'
178
def authorize(self, req):
179
"""Only allow access if the requesting user is an admin."""
180
return req.user and req.user.admin
182
def populate(self, req, ctx):
184
if req.method == 'POST':
185
data = dict(req.get_fieldstorage())
186
if data.get('new_password') != data.get('new_password_again'):
187
error = 'New passwords do not match.'
188
elif not data.get('new_password'):
189
error = 'New password cannot be empty.'
191
self.context.password = data['new_password']
193
req.throw_redirect(req.uri)
195
ctx['user'] = self.context
198
class Plugin(ViewPlugin, MediaPlugin):
200
The Plugin class for the user plugin.
203
forward_routes = (root_to_user,)
204
reverse_routes = (user_url,)
205
views = [(ApplicationRoot, 'users', UsersView),
206
(User, '+index', UserEditView),
207
(User, '+admin', UserAdminView),
208
(User, '+changepassword', PasswordChangeView),
209
(User, '+resetpassword', PasswordResetView),
213
('users', 'Users', 'Display and edit all users',
214
'users.png', 'users', 90, True)
217
public_forward_routes = forward_routes
218
public_reverse_routes = reverse_routes