18
18
# Author: Matt Giuca, Will Grant
20
from ivle.webapp.base.rest import JSONRESTView, require_permission
21
import formencode.validators
22
from genshi.filters import HTMLFormFiller
24
from ivle.database import User
26
from ivle.pulldown_subj import enrol_user
27
from ivle.webapp import ApplicationRoot
28
from ivle.webapp.base.forms import BaseFormView, URLNameValidator
21
29
from ivle.webapp.base.xhtml import XHTMLView
22
30
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
23
from ivle.webapp.errors import NotFound, Unauthorized
27
# List of fields returned as part of the user JSON dictionary
28
# (as returned by the get_user action)
30
"login", "state", "unixid", "email", "nick", "fullname",
31
"admin", "studentid", "acct_exp", "pass_exp", "last_login",
35
class UserRESTView(JSONRESTView):
37
A REST interface to the user object.
39
def __init__(self, req, login):
40
super(UserRESTView, self).__init__(self, req, login)
41
self.context = ivle.database.User.get_by_login(req.store, login)
42
if self.context is None:
45
@require_permission('view')
48
user = ivle.util.object_to_dict(user_fields_list, self.context)
49
# Convert time stamps to nice strings
50
for k in 'pass_exp', 'acct_exp', 'last_login':
51
if user[k] is not None:
52
user[k] = unicode(user[k])
54
user['local_password'] = self.context.passhash is not None
57
class UserSettingsView(XHTMLView):
58
template = 'templates/user-settings.html'
62
def __init__(self, req, login):
63
self.context = ivle.database.User.get_by_login(req.store, login)
64
if self.context is None:
67
def populate(self, req, ctx):
68
self.plugin_scripts[Plugin] = ['settings.js']
69
req.scripts_init = ['revert_settings']
71
ctx['login'] = self.context.login
31
from ivle.webapp.admin.publishing import root_to_user, user_url
34
class UsersView(XHTMLView):
35
"""A list of all IVLE users."""
36
template = 'templates/users.html'
38
breadcrumb_text = 'Users'
40
def authorize(self, req):
41
return req.user and req.user.admin
43
def populate(self, req, ctx):
45
ctx['users'] = req.store.find(User).order_by(User.login)
48
class UserEditSchema(formencode.Schema):
49
nick = formencode.validators.UnicodeString(not_empty=True)
50
email = formencode.validators.Email(not_empty=False,
53
class UserEditView(BaseFormView):
54
"""A form to change a user's details."""
55
template = 'templates/user-edit.html'
61
return UserEditSchema()
63
def get_default_data(self, req):
64
return {'nick': self.context.nick,
65
'email': self.context.email
68
def save_object(self, req, data):
69
self.context.nick = data['nick']
70
self.context.email = unicode(data['email']) if data['email'] \
74
def populate(self, req, ctx):
75
super(UserEditView, self).populate(req, ctx)
76
ctx['format_datetime'] = ivle.date.make_date_nice
77
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
80
class UserAdminSchema(formencode.Schema):
81
admin = formencode.validators.StringBoolean(if_missing=False)
82
disabled = formencode.validators.StringBoolean(if_missing=False)
83
fullname = formencode.validators.UnicodeString(not_empty=True)
84
studentid = formencode.validators.UnicodeString(not_empty=False,
88
class UserAdminView(BaseFormView):
89
"""A form for admins to change more of a user's details."""
90
template = 'templates/user-admin.html'
93
def authorize(self, req):
94
"""Only allow access if the requesting user is an admin."""
95
return req.user and req.user.admin
99
return UserAdminSchema()
101
def get_default_data(self, req):
102
return {'admin': self.context.admin,
103
'disabled': self.context.state == u'disabled',
104
'fullname': self.context.fullname,
105
'studentid': self.context.studentid,
108
def save_object(self, req, data):
109
if self.context is req.user:
110
# Admin checkbox is disabled -- assume unchanged
111
data['admin'] = self.context.admin
112
data['disabled'] = self.context.state == u'disabled'
114
self.context.admin = data['admin']
115
if self.context.state in (u'enabled', u'disabled'):
116
self.context.state = (u'disabled' if data['disabled']
118
self.context.fullname = data['fullname'] \
119
if data['fullname'] else None
120
self.context.studentid = data['studentid'] \
121
if data['studentid'] else None
124
def populate(self, req, ctx):
125
super(UserAdminView, self).populate(req, ctx)
127
# Disable the admin checkbox if editing oneself
128
ctx['disable_admin'] = self.context is req.user
130
class PasswordChangeView(XHTMLView):
131
"""A form to change a user's password, with knowledge of the old one."""
132
template = 'templates/user-password-change.html'
136
def authorize(self, req):
137
"""Only allow access if the requesting user holds the permission,
138
and the target user has a password set. Otherwise we might be
139
clobbering external authn.
141
return super(PasswordChangeView, self).authorize(req) and \
142
self.context.passhash is not None
144
def populate(self, req, ctx):
146
if req.method == 'POST':
147
data = dict(req.get_fieldstorage())
148
if data.get('old_password') is None or \
149
not self.context.authenticate(data.get('old_password')):
150
error = 'Incorrect password.'
151
elif data.get('new_password') != data.get('new_password_again'):
152
error = 'New passwords do not match.'
153
elif not data.get('new_password'):
154
error = 'New password cannot be empty.'
156
self.context.password = data['new_password']
158
req.throw_redirect(req.uri)
161
ctx['user'] = self.context
164
class PasswordResetView(XHTMLView):
165
"""A form to reset a user's password, without knowledge of the old one."""
166
template = 'templates/user-password-reset.html'
169
def authorize(self, req):
170
"""Only allow access if the requesting user is an admin."""
171
return req.user and req.user.admin
173
def populate(self, req, ctx):
175
if req.method == 'POST':
176
data = dict(req.get_fieldstorage())
177
if data.get('new_password') != data.get('new_password_again'):
178
error = 'New passwords do not match.'
179
elif not data.get('new_password'):
180
error = 'New password cannot be empty.'
182
self.context.password = data['new_password']
184
req.throw_redirect(req.uri)
186
ctx['user'] = self.context
190
class UserNewSchema(formencode.Schema):
191
login = URLNameValidator() # XXX: Validate uniqueness.
192
admin = formencode.validators.StringBoolean(if_missing=False)
193
fullname = formencode.validators.UnicodeString(not_empty=True)
194
studentid = formencode.validators.UnicodeString(not_empty=False,
197
email = formencode.validators.Email(not_empty=False,
201
class UserNewView(BaseFormView):
202
"""A form for admins to create new users."""
203
template = 'templates/user-new.html'
206
def authorize(self, req):
207
"""Only allow access if the requesting user is an admin."""
208
return req.user and req.user.admin
212
return UserNewSchema()
214
def get_default_data(self, req):
217
def save_object(self, req, data):
218
data['nick'] = data['fullname']
219
data['email'] = unicode(data['email']) if data['email'] else None
220
userobj = User(**data)
221
req.store.add(userobj)
222
enrol_user(req.config, req.store, userobj)
73
227
class Plugin(ViewPlugin, MediaPlugin):
75
229
The Plugin class for the user plugin.
77
# Magic attribute: urls
78
# Sequence of pairs/triples of
79
# (regex str, handler class, kwargs dict)
80
# The kwargs dict is passed to the __init__ of the view object
82
('~:login/+settings', UserSettingsView),
83
('api/~:login', UserRESTView),
232
forward_routes = (root_to_user,)
233
reverse_routes = (user_url,)
234
views = [(ApplicationRoot, ('users', '+index'), UsersView),
235
(ApplicationRoot, ('users', '+new'), UserNewView),
236
(User, '+index', UserEditView),
237
(User, '+admin', UserAdminView),
238
(User, '+changepassword', PasswordChangeView),
239
(User, '+resetpassword', PasswordResetView),
243
('users', 'Users', 'Display and edit all users',
244
'users.png', 'users', 90, True)
247
public_forward_routes = forward_routes
248
public_reverse_routes = reverse_routes
86
250
media = 'user-media'