1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
# IVLE - Informatics Virtual Learning Environment
# Copyright (C) 2007-2009 The University of Melbourne
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# Author: Matt Giuca, Will Grant
import formencode
import formencode.validators
from genshi.filters import HTMLFormFiller
from ivle.database import User
import ivle.date
from ivle.pulldown_subj import enrol_user
from ivle.webapp import ApplicationRoot
from ivle.webapp.base.forms import BaseFormView, URLNameValidator
from ivle.webapp.base.xhtml import XHTMLView
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
from ivle.webapp.admin.publishing import root_to_user, user_url
class UsersView(XHTMLView):
"""A list of all IVLE users."""
template = 'templates/users.html'
tab = 'users'
breadcrumb_text = 'Users'
def authorize(self, req):
return req.user and req.user.admin
def populate(self, req, ctx):
ctx['req'] = req
ctx['users'] = req.store.find(User).order_by(User.login)
class UserEditSchema(formencode.Schema):
nick = formencode.validators.UnicodeString(not_empty=True)
email = formencode.validators.Email(not_empty=False,
if_missing=None)
class UserEditView(BaseFormView):
"""A form to change a user's details."""
template = 'templates/user-edit.html'
tab = 'users'
permission = 'edit'
@property
def validator(self):
return UserEditSchema()
def get_default_data(self, req):
return {'nick': self.context.nick,
'email': self.context.email
}
def save_object(self, req, data):
self.context.nick = data['nick']
self.context.email = unicode(data['email']) if data['email'] \
else None
return self.context
def populate(self, req, ctx):
super(UserEditView, self).populate(req, ctx)
ctx['format_datetime'] = ivle.date.make_date_nice
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
ctx['svn_url'] = req.user.get_svn_url(req.config)
ctx['svn_pass'] = req.user.svn_pass
class UserAdminSchema(formencode.Schema):
admin = formencode.validators.StringBoolean(if_missing=False)
disabled = formencode.validators.StringBoolean(if_missing=False)
fullname = formencode.validators.UnicodeString(not_empty=True)
studentid = formencode.validators.UnicodeString(not_empty=False,
if_missing=None
)
class UserAdminView(BaseFormView):
"""A form for admins to change more of a user's details."""
template = 'templates/user-admin.html'
tab = 'users'
def authorize(self, req):
"""Only allow access if the requesting user is an admin."""
return req.user and req.user.admin
@property
def validator(self):
return UserAdminSchema()
def get_default_data(self, req):
return {'admin': self.context.admin,
'disabled': self.context.state == u'disabled',
'fullname': self.context.fullname,
'studentid': self.context.studentid,
}
def save_object(self, req, data):
if self.context is req.user:
# Admin checkbox is disabled -- assume unchanged
data['admin'] = self.context.admin
data['disabled'] = self.context.state == u'disabled'
else:
self.context.admin = data['admin']
if self.context.state in (u'enabled', u'disabled'):
self.context.state = (u'disabled' if data['disabled']
else u'enabled')
self.context.fullname = data['fullname'] \
if data['fullname'] else None
self.context.studentid = data['studentid'] \
if data['studentid'] else None
return self.context
def populate(self, req, ctx):
super(UserAdminView, self).populate(req, ctx)
# Disable the admin checkbox if editing oneself
ctx['disable_admin'] = self.context is req.user
class PasswordChangeView(XHTMLView):
"""A form to change a user's password, with knowledge of the old one."""
template = 'templates/user-password-change.html'
tab = 'users'
permission = 'edit'
def authorize(self, req):
"""Only allow access if the requesting user holds the permission,
and the target user has a password set. Otherwise we might be
clobbering external authn.
"""
return super(PasswordChangeView, self).authorize(req) and \
self.context.passhash is not None
def populate(self, req, ctx):
error = None
if req.method == 'POST':
data = dict(req.get_fieldstorage())
if data.get('old_password') is None or \
not self.context.authenticate(data.get('old_password')):
error = 'Incorrect password.'
elif data.get('new_password') != data.get('new_password_again'):
error = 'New passwords do not match.'
elif not data.get('new_password'):
error = 'New password cannot be empty.'
else:
self.context.password = data['new_password']
req.store.commit()
req.throw_redirect(req.uri)
ctx['req'] = req
ctx['user'] = self.context
ctx['error'] = error
class PasswordResetView(XHTMLView):
"""A form to reset a user's password, without knowledge of the old one."""
template = 'templates/user-password-reset.html'
tab = 'users'
def authorize(self, req):
"""Only allow access if the requesting user is an admin."""
return req.user and req.user.admin
def populate(self, req, ctx):
error = None
if req.method == 'POST':
data = dict(req.get_fieldstorage())
if data.get('new_password') != data.get('new_password_again'):
error = 'New passwords do not match.'
elif not data.get('new_password'):
error = 'New password cannot be empty.'
else:
self.context.password = data['new_password']
req.store.commit()
req.throw_redirect(req.uri)
ctx['user'] = self.context
ctx['error'] = error
class UserNewSchema(formencode.Schema):
login = URLNameValidator() # XXX: Validate uniqueness.
admin = formencode.validators.StringBoolean(if_missing=False)
fullname = formencode.validators.UnicodeString(not_empty=True)
studentid = formencode.validators.UnicodeString(not_empty=False,
if_missing=None
)
email = formencode.validators.Email(not_empty=False,
if_missing=None)
class UserNewView(BaseFormView):
"""A form for admins to create new users."""
template = 'templates/user-new.html'
tab = 'users'
def authorize(self, req):
"""Only allow access if the requesting user is an admin."""
return req.user and req.user.admin
@property
def validator(self):
return UserNewSchema()
def get_default_data(self, req):
return {}
def save_object(self, req, data):
data['nick'] = data['fullname']
data['email'] = unicode(data['email']) if data['email'] else None
userobj = User(**data)
req.store.add(userobj)
enrol_user(req.config, req.store, userobj)
return userobj
class Plugin(ViewPlugin, MediaPlugin):
"""
The Plugin class for the user plugin.
"""
forward_routes = (root_to_user,)
reverse_routes = (user_url,)
views = [(ApplicationRoot, ('users', '+index'), UsersView),
(ApplicationRoot, ('users', '+new'), UserNewView),
(User, '+index', UserEditView),
(User, '+admin', UserAdminView),
(User, '+changepassword', PasswordChangeView),
(User, '+resetpassword', PasswordResetView),
]
tabs = [
('users', 'Users', 'Display and edit all users',
'users.png', 'users', 90, True)
]
public_forward_routes = forward_routes
public_reverse_routes = reverse_routes
media = 'user-media'
|