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.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
25
29
from ivle.webapp.base.xhtml import XHTMLView
26
30
from ivle.webapp.base.plugins import ViewPlugin, MediaPlugin
27
from ivle.webapp.errors import NotFound, Unauthorized
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
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)
62
48
class UserEditSchema(formencode.Schema):
63
49
nick = formencode.validators.UnicodeString(not_empty=True)
64
50
email = formencode.validators.Email(not_empty=False,
67
class UserEditView(XHTMLView):
53
class UserEditView(BaseFormView):
68
54
"""A form to change a user's details."""
69
55
template = 'templates/user-edit.html'
71
57
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'])
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'] \
81
74
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
75
super(UserEditView, self).populate(req, ctx)
100
76
ctx['format_datetime'] = ivle.date.make_date_nice
101
77
ctx['format_datetime_short'] = ivle.date.format_datetime_for_paragraph
78
ctx['svn_url'] = req.user.get_svn_url(req.config, req)
79
ctx['svn_pass'] = req.user.svn_pass
104
ctx['user'] = self.context
106
ctx['errors'] = errors
108
82
class UserAdminSchema(formencode.Schema):
109
83
admin = formencode.validators.StringBoolean(if_missing=False)
84
disabled = formencode.validators.StringBoolean(if_missing=False)
110
85
fullname = formencode.validators.UnicodeString(not_empty=True)
111
86
studentid = formencode.validators.UnicodeString(not_empty=False,
115
class UserAdminView(XHTMLView):
90
class UserAdminView(BaseFormView):
116
91
"""A form for admins to change more of a user's details."""
117
92
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
95
def authorize(self, req):
126
96
"""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'])
97
return req.user and req.user.admin
101
return UserAdminSchema()
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,
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'
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']
120
self.context.fullname = data['fullname'] \
121
if data['fullname'] else None
122
self.context.studentid = data['studentid'] \
123
if data['studentid'] else None
132
126
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
127
super(UserAdminView, self).populate(req, ctx)
129
# Disable the admin checkbox if editing oneself
130
ctx['disable_admin'] = self.context is req.user
160
132
class PasswordChangeView(XHTMLView):
161
133
"""A form to change a user's password, with knowledge of the old one."""
162
134
template = 'templates/user-password-change.html'
164
136
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
138
def authorize(self, req):
172
139
"""Only allow access if the requesting user holds the permission,
173
140
and the target user has a password set. Otherwise we might be
226
188
ctx['user'] = self.context
227
189
ctx['error'] = error
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,
199
email = formencode.validators.Email(not_empty=False,
203
class UserNewView(BaseFormView):
204
"""A form for admins to create new users."""
205
template = 'templates/user-new.html'
208
def authorize(self, req):
209
"""Only allow access if the requesting user is an admin."""
210
return req.user and req.user.admin
214
return UserNewSchema()
216
def get_default_data(self, req):
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)
229
229
class Plugin(ViewPlugin, MediaPlugin):
231
231
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),
234
forward_routes = (root_to_user,)
235
reverse_routes = (user_url,)
236
views = [(ApplicationRoot, ('users', '+index'), UsersView),
237
(ApplicationRoot, ('users', '+new'), UserNewView),
238
(User, '+index', UserEditView),
239
(User, '+admin', UserAdminView),
240
(User, '+changepassword', PasswordChangeView),
241
(User, '+resetpassword', PasswordResetView),
245
('users', 'Users', 'Display and edit all users',
246
'users.png', 'users', 90, True)
249
public_forward_routes = forward_routes
250
public_reverse_routes = reverse_routes
245
252
media = 'user-media'