1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
1 |
# IVLE - Informatics Virtual Learning Environment
|
2 |
# Copyright (C) 2007-2009 The University of Melbourne
|
|
3 |
#
|
|
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.
|
|
8 |
#
|
|
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.
|
|
13 |
#
|
|
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
|
|
17 |
||
18 |
# Author: Matt Giuca, Will Grant
|
|
19 |
||
1294.1.1
by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView. |
20 |
import formencode |
21 |
import formencode.validators |
|
22 |
from genshi.filters import HTMLFormFiller |
|
23 |
||
1755
by William Grant
Add a form to create a user. |
24 |
from ivle.database import User |
25 |
import ivle.date |
|
26 |
from ivle.pulldown_subj import enrol_user |
|
1375
by William Grant
Add a user list -- as yet unlinked. |
27 |
from ivle.webapp import ApplicationRoot |
1755
by William Grant
Add a form to create a user. |
28 |
from ivle.webapp.base.forms import BaseFormView, URLNameValidator |
1099.1.34
by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was |
29 |
from ivle.webapp.base.xhtml import XHTMLView |
1099.1.99
by William Grant
Require that plugins providing media subclass MediaPlugin. |
30 |
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin |
1294.3.2
by William Grant
Router->Publisher |
31 |
from ivle.webapp.admin.publishing import root_to_user, user_url |
1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
32 |
|
1375
by William Grant
Add a user list -- as yet unlinked. |
33 |
|
34 |
class UsersView(XHTMLView): |
|
35 |
"""A list of all IVLE users."""
|
|
36 |
template = 'templates/users.html' |
|
1497
by Matt Giuca
Added Users tab to drop-down menu, for admins only. |
37 |
tab = 'users' |
1472
by William Grant
Add a 'Users' breadcrumb. |
38 |
breadcrumb_text = 'Users' |
1375
by William Grant
Add a user list -- as yet unlinked. |
39 |
|
40 |
def authorize(self, req): |
|
1504
by Matt Giuca
ivle/webapp/admin/user.py: Fixed crash when visiting admin-only user pages while logged out (None dereference). |
41 |
return req.user and req.user.admin |
1375
by William Grant
Add a user list -- as yet unlinked. |
42 |
|
43 |
def populate(self, req, ctx): |
|
44 |
ctx['req'] = req |
|
45 |
ctx['users'] = req.store.find(User).order_by(User.login) |
|
46 |
||
47 |
||
1294.1.1
by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView. |
48 |
class UserEditSchema(formencode.Schema): |
49 |
nick = formencode.validators.UnicodeString(not_empty=True) |
|
50 |
email = formencode.validators.Email(not_empty=False, |
|
51 |
if_missing=None) |
|
52 |
||
1681
by William Grant
Refactor UserEditView to use BaseFormView. |
53 |
class UserEditView(BaseFormView): |
1294.1.1
by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView. |
54 |
"""A form to change a user's details."""
|
55 |
template = 'templates/user-edit.html' |
|
1498
by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon |
56 |
tab = 'users' |
1294.1.1
by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView. |
57 |
permission = 'edit' |
58 |
||
1681
by William Grant
Refactor UserEditView to use BaseFormView. |
59 |
@property
|
60 |
def validator(self): |
|
61 |
return UserEditSchema() |
|
62 |
||
63 |
def get_default_data(self, req): |
|
64 |
return {'nick': self.context.nick, |
|
65 |
'email': self.context.email |
|
66 |
}
|
|
67 |
||
68 |
def save_object(self, req, data): |
|
69 |
self.context.nick = data['nick'] |
|
70 |
self.context.email = unicode(data['email']) if data['email'] \ |
|
71 |
else None |
|
72 |
return self.context |
|
1294.1.1
by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView. |
73 |
|
74 |
def populate(self, req, ctx): |
|
1681
by William Grant
Refactor UserEditView to use BaseFormView. |
75 |
super(UserEditView, self).populate(req, ctx) |
1294.1.7
by William Grant
Display account/password expiry times on +edit. |
76 |
ctx['format_datetime'] = ivle.date.make_date_nice |
77 |
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph |
|
1813
by Matt Giuca
ivle.webapp.admin.user: Fixed call to req.user.get_svn_url (now takes 1 argument, as of r1810). |
78 |
ctx['svn_url'] = req.user.get_svn_url(req.config) |
1782
by Matt Giuca
Added an entry on the user settings page to display the user's Subversion password. This was previously only possible through an arcane console command. Updated documentation. This fixes Launchpad bug #528450. |
79 |
ctx['svn_pass'] = req.user.svn_pass |
1294.1.7
by William Grant
Display account/password expiry times on +edit. |
80 |
|
1294.1.1
by William Grant
Add UserEditView, a non-AJAX partial replacement of UserSettingsView. |
81 |
|
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
82 |
class UserAdminSchema(formencode.Schema): |
83 |
admin = formencode.validators.StringBoolean(if_missing=False) |
|
1503
by David Coles
Admin: Allow enabling and disabling of users in admin UI |
84 |
disabled = formencode.validators.StringBoolean(if_missing=False) |
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
85 |
fullname = formencode.validators.UnicodeString(not_empty=True) |
86 |
studentid = formencode.validators.UnicodeString(not_empty=False, |
|
87 |
if_missing=None |
|
88 |
)
|
|
89 |
||
1680
by William Grant
Refactor UserAdminView to use BaseFormView. |
90 |
class UserAdminView(BaseFormView): |
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
91 |
"""A form for admins to change more of a user's details."""
|
92 |
template = 'templates/user-admin.html' |
|
1498
by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon |
93 |
tab = 'users' |
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
94 |
|
95 |
def authorize(self, req): |
|
96 |
"""Only allow access if the requesting user is an admin."""
|
|
1504
by Matt Giuca
ivle/webapp/admin/user.py: Fixed crash when visiting admin-only user pages while logged out (None dereference). |
97 |
return req.user and req.user.admin |
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
98 |
|
1680
by William Grant
Refactor UserAdminView to use BaseFormView. |
99 |
@property
|
100 |
def validator(self): |
|
101 |
return UserAdminSchema() |
|
102 |
||
103 |
def get_default_data(self, req): |
|
104 |
return {'admin': self.context.admin, |
|
105 |
'disabled': self.context.state == u'disabled', |
|
106 |
'fullname': self.context.fullname, |
|
107 |
'studentid': self.context.studentid, |
|
108 |
}
|
|
109 |
||
110 |
def save_object(self, req, data): |
|
111 |
if self.context is req.user: |
|
112 |
# Admin checkbox is disabled -- assume unchanged
|
|
113 |
data['admin'] = self.context.admin |
|
114 |
data['disabled'] = self.context.state == u'disabled' |
|
115 |
else: |
|
116 |
self.context.admin = data['admin'] |
|
117 |
if self.context.state in (u'enabled', u'disabled'): |
|
118 |
self.context.state = (u'disabled' if data['disabled'] |
|
119 |
else u'enabled') |
|
120 |
self.context.fullname = data['fullname'] \ |
|
121 |
if data['fullname'] else None |
|
122 |
self.context.studentid = data['studentid'] \ |
|
123 |
if data['studentid'] else None |
|
124 |
return self.context |
|
125 |
||
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
126 |
def populate(self, req, ctx): |
1680
by William Grant
Refactor UserAdminView to use BaseFormView. |
127 |
super(UserAdminView, self).populate(req, ctx) |
128 |
||
129 |
# Disable the admin checkbox if editing oneself
|
|
1501
by Matt Giuca
User administration page: Do not let an admin user change their own admin checkbox (disable and don't let it change). This would be bad. |
130 |
ctx['disable_admin'] = self.context is req.user |
1294.1.9
by William Grant
Add a UserAdminView, to set fullname/studentid/admin. |
131 |
|
1294.1.2
by William Grant
Add a non-AJAX password change view. |
132 |
class PasswordChangeView(XHTMLView): |
1294.1.5
by William Grant
Add a view for admins to reset other users' passwords. |
133 |
"""A form to change a user's password, with knowledge of the old one."""
|
1294.1.2
by William Grant
Add a non-AJAX password change view. |
134 |
template = 'templates/user-password-change.html' |
1498
by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon |
135 |
tab = 'users' |
1294.1.2
by William Grant
Add a non-AJAX password change view. |
136 |
permission = 'edit' |
137 |
||
1294.1.4
by William Grant
Forbid access to +changepassword if there is no passhash. |
138 |
def authorize(self, req): |
139 |
"""Only allow access if the requesting user holds the permission,
|
|
140 |
and the target user has a password set. Otherwise we might be
|
|
141 |
clobbering external authn.
|
|
142 |
"""
|
|
143 |
return super(PasswordChangeView, self).authorize(req) and \ |
|
144 |
self.context.passhash is not None |
|
145 |
||
1294.1.2
by William Grant
Add a non-AJAX password change view. |
146 |
def populate(self, req, ctx): |
147 |
error = None |
|
148 |
if req.method == 'POST': |
|
149 |
data = dict(req.get_fieldstorage()) |
|
150 |
if data.get('old_password') is None or \ |
|
151 |
not self.context.authenticate(data.get('old_password')): |
|
152 |
error = 'Incorrect password.' |
|
153 |
elif data.get('new_password') != data.get('new_password_again'): |
|
154 |
error = 'New passwords do not match.' |
|
155 |
elif not data.get('new_password'): |
|
156 |
error = 'New password cannot be empty.' |
|
157 |
else: |
|
158 |
self.context.password = data['new_password'] |
|
159 |
req.store.commit() |
|
160 |
req.throw_redirect(req.uri) |
|
161 |
||
1294.1.5
by William Grant
Add a view for admins to reset other users' passwords. |
162 |
ctx['req'] = req |
163 |
ctx['user'] = self.context |
|
164 |
ctx['error'] = error |
|
165 |
||
166 |
class PasswordResetView(XHTMLView): |
|
167 |
"""A form to reset a user's password, without knowledge of the old one."""
|
|
168 |
template = 'templates/user-password-reset.html' |
|
1498
by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon |
169 |
tab = 'users' |
1294.1.5
by William Grant
Add a view for admins to reset other users' passwords. |
170 |
|
171 |
def authorize(self, req): |
|
172 |
"""Only allow access if the requesting user is an admin."""
|
|
1504
by Matt Giuca
ivle/webapp/admin/user.py: Fixed crash when visiting admin-only user pages while logged out (None dereference). |
173 |
return req.user and req.user.admin |
1294.1.5
by William Grant
Add a view for admins to reset other users' passwords. |
174 |
|
175 |
def populate(self, req, ctx): |
|
176 |
error = None |
|
177 |
if req.method == 'POST': |
|
178 |
data = dict(req.get_fieldstorage()) |
|
179 |
if data.get('new_password') != data.get('new_password_again'): |
|
180 |
error = 'New passwords do not match.' |
|
181 |
elif not data.get('new_password'): |
|
182 |
error = 'New password cannot be empty.' |
|
183 |
else: |
|
184 |
self.context.password = data['new_password'] |
|
185 |
req.store.commit() |
|
186 |
req.throw_redirect(req.uri) |
|
187 |
||
1294.1.2
by William Grant
Add a non-AJAX password change view. |
188 |
ctx['user'] = self.context |
189 |
ctx['error'] = error |
|
190 |
||
1755
by William Grant
Add a form to create a user. |
191 |
|
192 |
class UserNewSchema(formencode.Schema): |
|
193 |
login = URLNameValidator() # XXX: Validate uniqueness. |
|
194 |
admin = formencode.validators.StringBoolean(if_missing=False) |
|
195 |
fullname = formencode.validators.UnicodeString(not_empty=True) |
|
196 |
studentid = formencode.validators.UnicodeString(not_empty=False, |
|
197 |
if_missing=None |
|
198 |
)
|
|
199 |
email = formencode.validators.Email(not_empty=False, |
|
200 |
if_missing=None) |
|
201 |
||
202 |
||
203 |
class UserNewView(BaseFormView): |
|
204 |
"""A form for admins to create new users."""
|
|
205 |
template = 'templates/user-new.html' |
|
206 |
tab = 'users' |
|
207 |
||
208 |
def authorize(self, req): |
|
209 |
"""Only allow access if the requesting user is an admin."""
|
|
210 |
return req.user and req.user.admin |
|
211 |
||
212 |
@property
|
|
213 |
def validator(self): |
|
214 |
return UserNewSchema() |
|
215 |
||
216 |
def get_default_data(self, req): |
|
217 |
return {} |
|
218 |
||
219 |
def save_object(self, req, data): |
|
220 |
data['nick'] = data['fullname'] |
|
221 |
data['email'] = unicode(data['email']) if data['email'] else None |
|
222 |
userobj = User(**data) |
|
223 |
req.store.add(userobj) |
|
224 |
enrol_user(req.config, req.store, userobj) |
|
225 |
||
226 |
return userobj |
|
227 |
||
228 |
||
1099.1.99
by William Grant
Require that plugins providing media subclass MediaPlugin. |
229 |
class Plugin(ViewPlugin, MediaPlugin): |
1099.1.1
by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick). |
230 |
"""
|
231 |
The Plugin class for the user plugin.
|
|
232 |
"""
|
|
1294.2.19
by William Grant
Port ivle.webapp.admin.user's views to the new dispatch system. |
233 |
|
1294.2.70
by William Grant
Split out ivle.webapp.admin's routes into annotated functions in ivle.webapp.traversal. |
234 |
forward_routes = (root_to_user,) |
235 |
reverse_routes = (user_url,) |
|
1755
by William Grant
Add a form to create a user. |
236 |
views = [(ApplicationRoot, ('users', '+index'), UsersView), |
237 |
(ApplicationRoot, ('users', '+new'), UserNewView), |
|
1679
by William Grant
Remove unused UserRESTView and associated infrastructure. |
238 |
(User, '+index', UserEditView), |
239 |
(User, '+admin', UserAdminView), |
|
240 |
(User, '+changepassword', PasswordChangeView), |
|
241 |
(User, '+resetpassword', PasswordResetView), |
|
1294.2.19
by William Grant
Port ivle.webapp.admin.user's views to the new dispatch system. |
242 |
]
|
1099.1.61
by William Grant
Port ivle.webapp.admin.user's media to the new framework. |
243 |
|
1497
by Matt Giuca
Added Users tab to drop-down menu, for admins only. |
244 |
tabs = [ |
245 |
('users', 'Users', 'Display and edit all users', |
|
1507
by William Grant
Shift the Users link to between Subjects and Help. |
246 |
'users.png', 'users', 90, True) |
1497
by Matt Giuca
Added Users tab to drop-down menu, for admins only. |
247 |
]
|
248 |
||
1294.2.138
by William Grant
Publish users in public mode. |
249 |
public_forward_routes = forward_routes |
250 |
public_reverse_routes = reverse_routes |
|
251 |
||
1099.1.61
by William Grant
Port ivle.webapp.admin.user's media to the new framework. |
252 |
media = 'user-media' |