21
21
import formencode.validators
22
22
from genshi.filters import HTMLFormFiller
24
from ivle.webapp.base.rest import JSONRESTView, require_permission
24
from ivle.webapp import ApplicationRoot
25
from ivle.webapp.base.forms import BaseFormView
25
26
from ivle.webapp.base.xhtml import XHTMLView
26
27
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
27
from ivle.webapp.errors import NotFound, Unauthorized
28
from ivle.webapp.admin.publishing import root_to_user, user_url
29
from ivle.database import User
32
# List of fields returned as part of the user JSON dictionary
33
# (as returned by the get_user action)
35
"login", "state", "unixid", "email", "nick", "fullname",
36
"admin", "studentid", "acct_exp", "pass_exp", "last_login",
40
class UserRESTView(JSONRESTView):
42
A REST interface to the user object.
44
def __init__(self, req, login):
45
super(UserRESTView, self).__init__(self, req, login)
46
self.context = ivle.database.User.get_by_login(req.store, login)
47
if self.context is None:
50
@require_permission('view')
53
user = ivle.util.object_to_dict(user_fields_list, self.context)
54
# Convert time stamps to nice strings
55
for k in 'pass_exp', 'acct_exp', 'last_login':
56
if user[k] is not None:
57
user[k] = unicode(user[k])
59
user['local_password'] = self.context.passhash is not None
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)
62
47
class UserEditSchema(formencode.Schema):
63
48
nick = formencode.validators.UnicodeString(not_empty=True)
64
49
email = formencode.validators.Email(not_empty=False,
67
class UserEditView(XHTMLView):
52
class UserEditView(BaseFormView):
68
53
"""A form to change a user's details."""
69
54
template = 'templates/user-edit.html'
71
56
permission = 'edit'
73
def __init__(self, req, login):
74
self.context = ivle.database.User.get_by_login(req.store, login)
75
if self.context is None:
78
def filter(self, stream, ctx):
79
return stream | HTMLFormFiller(data=ctx['data'])
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'] \
81
73
def populate(self, req, ctx):
82
if req.method == 'POST':
83
data = dict(req.get_fieldstorage())
85
validator = UserEditSchema()
86
data = validator.to_python(data, state=req)
87
self.context.nick = data['nick']
88
self.context.email = unicode(data['email']) if data['email'] \
91
req.throw_redirect(req.uri)
92
except formencode.Invalid, e:
93
errors = e.unpack_errors()
95
data = {'nick': self.context.nick,
96
'email': self.context.email
74
super(UserEditView, self).populate(req, ctx)
100
75
ctx['format_datetime'] = ivle.date.make_date_nice
101
76
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
104
ctx['user'] = self.context
106
ctx['errors'] = errors
108
79
class UserAdminSchema(formencode.Schema):
109
80
admin = formencode.validators.StringBoolean(if_missing=False)
81
disabled = formencode.validators.StringBoolean(if_missing=False)
110
82
fullname = formencode.validators.UnicodeString(not_empty=True)
111
83
studentid = formencode.validators.UnicodeString(not_empty=False,
115
class UserAdminView(XHTMLView):
87
class UserAdminView(BaseFormView):
116
88
"""A form for admins to change more of a user's details."""
117
89
template = 'templates/user-admin.html'
120
def __init__(self, req, login):
121
self.context = ivle.database.User.get_by_login(req.store, login)
122
if self.context is None:
125
92
def authorize(self, req):
126
93
"""Only allow access if the requesting user is an admin."""
127
return req.user.admin
129
def filter(self, stream, ctx):
130
return stream | HTMLFormFiller(data=ctx['data'])
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
132
123
def populate(self, req, ctx):
133
if req.method == 'POST':
134
data = dict(req.get_fieldstorage())
136
validator = UserAdminSchema()
137
data = validator.to_python(data, state=req)
139
self.context.admin = data['admin']
140
self.context.fullname = data['fullname'] \
141
if data['fullname'] else None
142
self.context.studentid = data['studentid'] \
143
if data['studentid'] else None
145
req.throw_redirect(req.uri)
146
except formencode.Invalid, e:
147
errors = e.unpack_errors()
149
data = {'admin': self.context.admin,
150
'fullname': self.context.fullname,
151
'studentid': self.context.studentid,
156
ctx['user'] = self.context
158
ctx['errors'] = errors
124
super(UserAdminView, self).populate(req, ctx)
126
# Disable the admin checkbox if editing oneself
127
ctx['disable_admin'] = self.context is req.user
160
129
class PasswordChangeView(XHTMLView):
161
130
"""A form to change a user's password, with knowledge of the old one."""
162
131
template = 'templates/user-password-change.html'
164
133
permission = 'edit'
166
def __init__(self, req, login):
167
self.context = ivle.database.User.get_by_login(req.store, login)
168
if self.context is None:
171
135
def authorize(self, req):
172
136
"""Only allow access if the requesting user holds the permission,
173
137
and the target user has a password set. Otherwise we might be
231
190
The Plugin class for the user plugin.
233
# Magic attribute: urls
234
# Sequence of pairs/triples of
235
# (regex str, handler class, kwargs dict)
236
# The kwargs dict is passed to the __init__ of the view object
238
('~:login/+edit', UserEditView),
239
('~:login/+admin', UserAdminView),
240
('~:login/+changepassword', PasswordChangeView),
241
('~:login/+resetpassword', PasswordResetView),
242
('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
245
210
media = 'user-media'