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.webapp import ApplicationRoot
25
from ivle.webapp.base.forms import BaseFormView
21
26
from ivle.webapp.base.xhtml import XHTMLView
22
27
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
"rolenm", "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
@require_permission('edit')
58
def PATCH(self, req, data):
59
# XXX Admins can set extra fields
60
# Note: Cannot change password here (use change_password named op)
62
for f in user_fields_list:
65
if isinstance(field, str):
66
field = unicode(field)
67
setattr(self.context, f, field)
71
class UserSettingsView(XHTMLView):
72
template = 'user-settings.html'
76
def __init__(self, req, login):
77
self.context = ivle.database.User.get_by_login(req.store, login)
78
if self.context is None:
81
def populate(self, req, ctx):
82
self.plugin_scripts[Plugin] = ['settings.js']
83
req.scripts_init = ['revert_settings']
85
ctx['login'] = self.context.login
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(BaseFormView):
53
"""A form to change a user's details."""
54
template = 'templates/user-edit.html'
60
return UserEditSchema()
62
def get_default_data(self, req):
63
return {'nick': self.context.nick,
64
'email': self.context.email
67
def save_object(self, req, data):
68
self.context.nick = data['nick']
69
self.context.email = unicode(data['email']) if data['email'] \
73
def populate(self, req, ctx):
74
super(UserEditView, self).populate(req, ctx)
75
ctx['format_datetime'] = ivle.date.make_date_nice
76
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
79
class UserAdminSchema(formencode.Schema):
80
admin = formencode.validators.StringBoolean(if_missing=False)
81
disabled = formencode.validators.StringBoolean(if_missing=False)
82
fullname = formencode.validators.UnicodeString(not_empty=True)
83
studentid = formencode.validators.UnicodeString(not_empty=False,
87
class UserAdminView(BaseFormView):
88
"""A form for admins to change more of a user's details."""
89
template = 'templates/user-admin.html'
92
def authorize(self, req):
93
"""Only allow access if the requesting user is an admin."""
94
return req.user and req.user.admin
98
return UserAdminSchema()
100
def get_default_data(self, req):
101
return {'admin': self.context.admin,
102
'disabled': self.context.state == u'disabled',
103
'fullname': self.context.fullname,
104
'studentid': self.context.studentid,
107
def save_object(self, req, data):
108
if self.context is req.user:
109
# Admin checkbox is disabled -- assume unchanged
110
data['admin'] = self.context.admin
111
data['disabled'] = self.context.state == u'disabled'
113
self.context.admin = data['admin']
114
if self.context.state in (u'enabled', u'disabled'):
115
self.context.state = (u'disabled' if data['disabled']
117
self.context.fullname = data['fullname'] \
118
if data['fullname'] else None
119
self.context.studentid = data['studentid'] \
120
if data['studentid'] else None
123
def populate(self, req, ctx):
124
super(UserAdminView, self).populate(req, ctx)
126
# Disable the admin checkbox if editing oneself
127
ctx['disable_admin'] = self.context is req.user
129
class PasswordChangeView(XHTMLView):
130
"""A form to change a user's password, with knowledge of the old one."""
131
template = 'templates/user-password-change.html'
135
def authorize(self, req):
136
"""Only allow access if the requesting user holds the permission,
137
and the target user has a password set. Otherwise we might be
138
clobbering external authn.
140
return super(PasswordChangeView, self).authorize(req) and \
141
self.context.passhash is not None
143
def populate(self, req, ctx):
145
if req.method == 'POST':
146
data = dict(req.get_fieldstorage())
147
if data.get('old_password') is None or \
148
not self.context.authenticate(data.get('old_password')):
149
error = 'Incorrect password.'
150
elif data.get('new_password') != data.get('new_password_again'):
151
error = 'New passwords do not match.'
152
elif not data.get('new_password'):
153
error = 'New password cannot be empty.'
155
self.context.password = data['new_password']
157
req.throw_redirect(req.uri)
160
ctx['user'] = self.context
163
class PasswordResetView(XHTMLView):
164
"""A form to reset a user's password, without knowledge of the old one."""
165
template = 'templates/user-password-reset.html'
168
def authorize(self, req):
169
"""Only allow access if the requesting user is an admin."""
170
return req.user and req.user.admin
172
def populate(self, req, ctx):
174
if req.method == 'POST':
175
data = dict(req.get_fieldstorage())
176
if data.get('new_password') != data.get('new_password_again'):
177
error = 'New passwords do not match.'
178
elif not data.get('new_password'):
179
error = 'New password cannot be empty.'
181
self.context.password = data['new_password']
183
req.throw_redirect(req.uri)
185
ctx['user'] = self.context
87
188
class Plugin(ViewPlugin, MediaPlugin):
89
190
The Plugin class for the user plugin.
91
# Magic attribute: urls
92
# Sequence of pairs/triples of
93
# (regex str, handler class, kwargs dict)
94
# The kwargs dict is passed to the __init__ of the view object
96
('~:login/+settings', UserSettingsView),
97
('api/~:login', UserRESTView),
193
forward_routes = (root_to_user,)
194
reverse_routes = (user_url,)
195
views = [(ApplicationRoot, 'users', UsersView),
196
(User, '+index', UserEditView),
197
(User, '+admin', UserAdminView),
198
(User, '+changepassword', PasswordChangeView),
199
(User, '+resetpassword', PasswordResetView),
203
('users', 'Users', 'Display and edit all users',
204
'users.png', 'users', 90, True)
207
public_forward_routes = forward_routes
208
public_reverse_routes = reverse_routes
100
210
media = 'user-media'