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.xhtml import XHTMLView
26
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
27
from ivle.webapp.admin.publishing import root_to_user, user_url
28
from ivle.database import User
32
class UsersView(XHTMLView):
33
"""A list of all IVLE users."""
34
template = 'templates/users.html'
36
breadcrumb_text = 'Users'
38
def authorize(self, req):
39
return req.user and req.user.admin
41
def populate(self, req, ctx):
43
ctx['users'] = req.store.find(User).order_by(User.login)
46
class UserEditSchema(formencode.Schema):
47
nick = formencode.validators.UnicodeString(not_empty=True)
48
email = formencode.validators.Email(not_empty=False,
51
class UserEditView(XHTMLView):
52
"""A form to change a user's details."""
53
template = 'templates/user-edit.html'
57
def filter(self, stream, ctx):
58
return stream | HTMLFormFiller(data=ctx['data'])
60
def populate(self, req, ctx):
61
if req.method == 'POST':
62
data = dict(req.get_fieldstorage())
64
validator = UserEditSchema()
65
data = validator.to_python(data, state=req)
66
self.context.nick = data['nick']
67
self.context.email = unicode(data['email']) if data['email'] \
70
req.throw_redirect(req.uri)
71
except formencode.Invalid, e:
72
errors = e.unpack_errors()
74
data = {'nick': self.context.nick,
75
'email': self.context.email
79
ctx['format_datetime'] = ivle.date.make_date_nice
80
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
83
ctx['user'] = self.context
85
ctx['errors'] = errors
87
class UserAdminSchema(formencode.Schema):
88
admin = formencode.validators.StringBoolean(if_missing=False)
89
disabled = formencode.validators.StringBoolean(if_missing=False)
90
fullname = formencode.validators.UnicodeString(not_empty=True)
91
studentid = formencode.validators.UnicodeString(not_empty=False,
95
class UserAdminView(XHTMLView):
96
"""A form for admins to change more of a user's details."""
97
template = 'templates/user-admin.html'
100
def authorize(self, req):
101
"""Only allow access if the requesting user is an admin."""
102
return req.user and req.user.admin
104
def filter(self, stream, ctx):
105
return stream | HTMLFormFiller(data=ctx['data'])
107
def populate(self, req, ctx):
108
if req.method == 'POST':
109
data = dict(req.get_fieldstorage())
111
validator = UserAdminSchema()
112
data = validator.to_python(data, state=req)
114
if self.context is req.user:
115
# Admin checkbox is disabled -- assume unchanged
116
data['admin'] = self.context.admin
117
data['disabled'] = self.context.state == u'disabled'
119
self.context.admin = data['admin']
120
if self.context.state in (u'enabled', u'disabled'):
121
self.context.state = (u'disabled' if data['disabled']
123
self.context.fullname = data['fullname'] \
124
if data['fullname'] else None
125
self.context.studentid = data['studentid'] \
126
if data['studentid'] else None
128
req.throw_redirect(req.uri)
129
except formencode.Invalid, e:
130
errors = e.unpack_errors()
132
data = {'admin': self.context.admin,
133
'disabled': self.context.state == u'disabled',
134
'fullname': self.context.fullname,
135
'studentid': self.context.studentid,
140
ctx['user'] = self.context
141
# Disable the Admin checkbox if editing oneself
142
ctx['disable_admin'] = self.context is req.user
144
ctx['errors'] = errors
146
class PasswordChangeView(XHTMLView):
147
"""A form to change a user's password, with knowledge of the old one."""
148
template = 'templates/user-password-change.html'
152
def authorize(self, req):
153
"""Only allow access if the requesting user holds the permission,
154
and the target user has a password set. Otherwise we might be
155
clobbering external authn.
157
return super(PasswordChangeView, self).authorize(req) and \
158
self.context.passhash is not None
160
def populate(self, req, ctx):
162
if req.method == 'POST':
163
data = dict(req.get_fieldstorage())
164
if data.get('old_password') is None or \
165
not self.context.authenticate(data.get('old_password')):
166
error = 'Incorrect password.'
167
elif data.get('new_password') != data.get('new_password_again'):
168
error = 'New passwords do not match.'
169
elif not data.get('new_password'):
170
error = 'New password cannot be empty.'
172
self.context.password = data['new_password']
174
req.throw_redirect(req.uri)
177
ctx['user'] = self.context
180
class PasswordResetView(XHTMLView):
181
"""A form to reset a user's password, without knowledge of the old one."""
182
template = 'templates/user-password-reset.html'
185
def authorize(self, req):
186
"""Only allow access if the requesting user is an admin."""
187
return req.user and req.user.admin
189
def populate(self, req, ctx):
191
if req.method == 'POST':
192
data = dict(req.get_fieldstorage())
193
if data.get('new_password') != data.get('new_password_again'):
194
error = 'New passwords do not match.'
195
elif not data.get('new_password'):
196
error = 'New password cannot be empty.'
198
self.context.password = data['new_password']
200
req.throw_redirect(req.uri)
202
ctx['user'] = self.context
205
class Plugin(ViewPlugin, MediaPlugin):
207
The Plugin class for the user plugin.
210
forward_routes = (root_to_user,)
211
reverse_routes = (user_url,)
212
views = [(ApplicationRoot, 'users', UsersView),
213
(User, '+index', UserEditView),
214
(User, '+admin', UserAdminView),
215
(User, '+changepassword', PasswordChangeView),
216
(User, '+resetpassword', PasswordResetView),
220
('users', 'Users', 'Display and edit all users',
221
'users.png', 'users', 90, True)
224
public_forward_routes = forward_routes
225
public_reverse_routes = reverse_routes