root / astakos / im / admin / views.py @ e015e9e6
History | View | Annotate | Download (18 kB)
1 |
# Copyright 2011 GRNET S.A. All rights reserved.
|
---|---|
2 |
#
|
3 |
# Redistribution and use in source and binary forms, with or
|
4 |
# without modification, are permitted provided that the following
|
5 |
# conditions are met:
|
6 |
#
|
7 |
# 1. Redistributions of source code must retain the above
|
8 |
# copyright notice, this list of conditions and the following
|
9 |
# disclaimer.
|
10 |
#
|
11 |
# 2. Redistributions in binary form must reproduce the above
|
12 |
# copyright notice, this list of conditions and the following
|
13 |
# disclaimer in the documentation and/or other materials
|
14 |
# provided with the distribution.
|
15 |
#
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
# POSSIBILITY OF SUCH DAMAGE.
|
28 |
#
|
29 |
# The views and conclusions contained in the software and
|
30 |
# documentation are those of the authors and should not be
|
31 |
# interpreted as representing official policies, either expressed
|
32 |
# or implied, of GRNET S.A.
|
33 |
|
34 |
import logging |
35 |
import socket |
36 |
import csv |
37 |
|
38 |
from functools import wraps |
39 |
from math import ceil |
40 |
from smtplib import SMTPException |
41 |
|
42 |
from django.core.mail import send_mail |
43 |
from django.http import HttpResponse, HttpResponseRedirect |
44 |
from django.shortcuts import redirect |
45 |
from django.template.loader import render_to_string |
46 |
from django.utils.http import urlencode |
47 |
from django.utils.translation import ugettext as _ |
48 |
from django.core.urlresolvers import reverse |
49 |
from django.contrib import messages |
50 |
from django.db import transaction |
51 |
|
52 |
from astakos.im.models import AstakosUser, Invitation |
53 |
from astakos.im.util import get_context, get_current_site |
54 |
from astakos.im.forms import * |
55 |
from astakos.im.views import render_response, index |
56 |
from astakos.im.admin.forms import AdminProfileForm |
57 |
from astakos.im.admin.forms import AdminUserCreationForm |
58 |
from astakos.im.settings import BYPASS_ADMIN_AUTH, ADMIN_PAGE_LIMIT, DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL |
59 |
|
60 |
logger = logging.getLogger(__name__) |
61 |
|
62 |
def requires_admin(func): |
63 |
"""
|
64 |
Decorator checkes whether the request.user is a superuser and if not
|
65 |
redirects to login page.
|
66 |
"""
|
67 |
@wraps(func)
|
68 |
def wrapper(request, *args): |
69 |
if not BYPASS_ADMIN_AUTH: |
70 |
if request.user.is_anonymous():
|
71 |
next = urlencode({'next': request.build_absolute_uri()})
|
72 |
login_uri = reverse(index) + '?' + next |
73 |
return HttpResponseRedirect(login_uri)
|
74 |
if not request.user.is_superuser: |
75 |
return HttpResponse('Forbidden', status=403) |
76 |
return func(request, *args)
|
77 |
return wrapper
|
78 |
|
79 |
@requires_admin
|
80 |
def admin(request, template_name='admin.html', extra_context={}): |
81 |
"""
|
82 |
Renders the admin page
|
83 |
|
84 |
If the ``request.user`` is not a superuser redirects to login page.
|
85 |
|
86 |
**Arguments**
|
87 |
|
88 |
``template_name``
|
89 |
A custom template to use. This is optional; if not specified,
|
90 |
this will default to ``admin.html``.
|
91 |
|
92 |
``extra_context``
|
93 |
An dictionary of variables to add to the template context.
|
94 |
|
95 |
**Template:**
|
96 |
|
97 |
admin.html or ``template_name`` keyword argument.
|
98 |
|
99 |
**Template Context:**
|
100 |
|
101 |
The template context is extended by:
|
102 |
|
103 |
* tab: the name of the active tab
|
104 |
* stats: dictionary containing the number of all and prending users
|
105 |
"""
|
106 |
stats = {} |
107 |
stats['users'] = AstakosUser.objects.count()
|
108 |
stats['pending'] = AstakosUser.objects.filter(is_active = False).count() |
109 |
|
110 |
invitations = Invitation.objects.all() |
111 |
stats['invitations'] = invitations.count()
|
112 |
stats['invitations_consumed'] = invitations.filter(is_consumed=True).count() |
113 |
|
114 |
kwargs = {'tab': 'home', 'stats': stats} |
115 |
context = get_context(request, extra_context,**kwargs) |
116 |
return render_response(template_name, context_instance = context)
|
117 |
|
118 |
@requires_admin
|
119 |
def users_list(request, template_name='users_list.html', extra_context={}): |
120 |
"""
|
121 |
Displays the list of all users.
|
122 |
|
123 |
If the ``request.user`` is not a superuser redirects to login page.
|
124 |
|
125 |
**Arguments**
|
126 |
|
127 |
``template_name``
|
128 |
A custom template to use. This is optional; if not specified,
|
129 |
this will default to ``users_list.html``.
|
130 |
|
131 |
``extra_context``
|
132 |
An dictionary of variables to add to the template context.
|
133 |
|
134 |
**Template:**
|
135 |
|
136 |
users_list.html or ``template_name`` keyword argument.
|
137 |
|
138 |
**Template Context:**
|
139 |
|
140 |
The template context is extended by:
|
141 |
|
142 |
* users: list of users fitting in current page
|
143 |
* filter: search key
|
144 |
* pages: the number of pages
|
145 |
* prev: the previous page
|
146 |
* next: the current page
|
147 |
|
148 |
**Settings:**
|
149 |
|
150 |
* ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
|
151 |
"""
|
152 |
users = AstakosUser.objects.order_by('id')
|
153 |
|
154 |
filter = request.GET.get('filter', '') |
155 |
if filter: |
156 |
if filter.startswith('-'): |
157 |
users = users.exclude(username__icontains=filter[1:]) |
158 |
else:
|
159 |
users = users.filter(username__icontains=filter)
|
160 |
|
161 |
try:
|
162 |
page = int(request.GET.get('page', 1)) |
163 |
except ValueError: |
164 |
page = 1
|
165 |
offset = max(0, page - 1) * ADMIN_PAGE_LIMIT |
166 |
limit = offset + ADMIN_PAGE_LIMIT |
167 |
|
168 |
npages = int(ceil(1.0 * users.count() / ADMIN_PAGE_LIMIT)) |
169 |
prev = page - 1 if page > 1 else None |
170 |
next = page + 1 if page < npages else None |
171 |
|
172 |
kwargs = {'users':users[offset:limit],
|
173 |
'filter':filter, |
174 |
'pages':range(1, npages + 1), |
175 |
'prev':prev,
|
176 |
'next':next} |
177 |
context = get_context(request, extra_context,**kwargs) |
178 |
return render_response(template_name, context_instance = context)
|
179 |
|
180 |
@requires_admin
|
181 |
def users_info(request, user_id, template_name='users_info.html', extra_context={}): |
182 |
"""
|
183 |
Displays the specific user profile.
|
184 |
|
185 |
If the ``request.user`` is not a superuser redirects to login page.
|
186 |
|
187 |
**Arguments**
|
188 |
|
189 |
``template_name``
|
190 |
A custom template to use. This is optional; if not specified,
|
191 |
this will default to ``users_info.html``.
|
192 |
|
193 |
``extra_context``
|
194 |
An dictionary of variables to add to the template context.
|
195 |
|
196 |
**Template:**
|
197 |
|
198 |
users_info.html or ``template_name`` keyword argument.
|
199 |
|
200 |
**Template Context:**
|
201 |
|
202 |
The template context is extended by:
|
203 |
|
204 |
* user: the user instance identified by ``user_id`` keyword argument
|
205 |
"""
|
206 |
if not extra_context: |
207 |
extra_context = {} |
208 |
user = AstakosUser.objects.get(id=user_id) |
209 |
return render_response(template_name,
|
210 |
form = AdminProfileForm(instance=user), |
211 |
user = user, |
212 |
context_instance = get_context(request, extra_context)) |
213 |
|
214 |
@requires_admin
|
215 |
def users_modify(request, user_id, template_name='users_info.html', extra_context={}): |
216 |
"""
|
217 |
Update the specific user information. Upon success redirects to ``user_info`` view.
|
218 |
|
219 |
If the ``request.user`` is not a superuser redirects to login page.
|
220 |
"""
|
221 |
user = AstakosUser.objects.get(id = user_id) |
222 |
form = AdminProfileForm(request.POST, instance=user) |
223 |
if form.is_valid():
|
224 |
form.save() |
225 |
return users_info(request, user.id, template_name, extra_context)
|
226 |
return render_response(template_name,
|
227 |
form = form, |
228 |
context_instance = get_context(request, extra_context)) |
229 |
|
230 |
@requires_admin
|
231 |
def users_delete(request, user_id): |
232 |
"""
|
233 |
Deletes the specified user
|
234 |
|
235 |
If the ``request.user`` is not a superuser redirects to login page.
|
236 |
"""
|
237 |
user = AstakosUser.objects.get(id=user_id) |
238 |
user.delete() |
239 |
return redirect(users_list)
|
240 |
|
241 |
@requires_admin
|
242 |
def pending_users(request, template_name='pending_users.html', extra_context={}): |
243 |
"""
|
244 |
Displays the list of the pending users.
|
245 |
|
246 |
If the ``request.user`` is not a superuser redirects to login page.
|
247 |
|
248 |
**Arguments**
|
249 |
|
250 |
``template_name``
|
251 |
A custom template to use. This is optional; if not specified,
|
252 |
this will default to ``users_list.html``.
|
253 |
|
254 |
``extra_context``
|
255 |
An dictionary of variables to add to the template context.
|
256 |
|
257 |
**Template:**
|
258 |
|
259 |
pending_users.html or ``template_name`` keyword argument.
|
260 |
|
261 |
**Template Context:**
|
262 |
|
263 |
The template context is extended by:
|
264 |
|
265 |
* users: list of pending users fitting in current page
|
266 |
* filter: search key
|
267 |
* pages: the number of pages
|
268 |
* prev: the previous page
|
269 |
* next: the current page
|
270 |
|
271 |
**Settings:**
|
272 |
|
273 |
* ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
|
274 |
"""
|
275 |
users = AstakosUser.objects.order_by('id')
|
276 |
|
277 |
users = users.filter(is_active = False)
|
278 |
|
279 |
filter = request.GET.get('filter', '') |
280 |
if filter: |
281 |
if filter.startswith('-'): |
282 |
users = users.exclude(username__icontains=filter[1:]) |
283 |
else:
|
284 |
users = users.filter(username__icontains=filter)
|
285 |
|
286 |
try:
|
287 |
page = int(request.GET.get('page', 1)) |
288 |
except ValueError: |
289 |
page = 1
|
290 |
offset = max(0, page - 1) * ADMIN_PAGE_LIMIT |
291 |
limit = offset + ADMIN_PAGE_LIMIT |
292 |
|
293 |
npages = int(ceil(1.0 * users.count() / ADMIN_PAGE_LIMIT)) |
294 |
prev = page - 1 if page > 1 else None |
295 |
next = page + 1 if page < npages else None |
296 |
kwargs = {'users':users[offset:limit],
|
297 |
'filter':filter, |
298 |
'pages':range(1, npages + 1), |
299 |
'page':page,
|
300 |
'prev':prev,
|
301 |
'next':next} |
302 |
return render_response(template_name,
|
303 |
context_instance = get_context(request, extra_context,**kwargs)) |
304 |
|
305 |
def _send_greeting(request, user, template_name): |
306 |
sitename, sitedomain = get_current_site(request, use_https=request.is_secure()) |
307 |
subject = _('Welcome to %s' % sitename)
|
308 |
baseurl = request.build_absolute_uri('/').rstrip('/') |
309 |
message = render_to_string(template_name, { |
310 |
'user': user,
|
311 |
'url': sitedomain,
|
312 |
'baseurl': baseurl,
|
313 |
'site_name': sitename,
|
314 |
'support': DEFAULT_CONTACT_EMAIL % sitename.lower()})
|
315 |
sender = DEFAULT_FROM_EMAIL % sitename |
316 |
send_mail(subject, message, sender, [user.email]) |
317 |
logger.info('Sent greeting %s', user)
|
318 |
|
319 |
@requires_admin
|
320 |
@transaction.commit_manually
|
321 |
def users_activate(request, user_id, template_name='pending_users.html', extra_context={}, email_template_name='welcome_email.txt'): |
322 |
"""
|
323 |
Activates the specific user and sends an email. Upon success renders the
|
324 |
``template_name`` keyword argument if exists else renders ``pending_users.html``.
|
325 |
|
326 |
If the ``request.user`` is not a superuser redirects to login page.
|
327 |
|
328 |
**Arguments**
|
329 |
|
330 |
``template_name``
|
331 |
A custom template to use. This is optional; if not specified,
|
332 |
this will default to ``users_list.html``.
|
333 |
|
334 |
``extra_context``
|
335 |
An dictionary of variables to add to the template context.
|
336 |
|
337 |
**Templates:**
|
338 |
|
339 |
pending_users.html or ``template_name`` keyword argument.
|
340 |
welcome_email.txt or ``email_template_name`` keyword argument.
|
341 |
|
342 |
**Template Context:**
|
343 |
|
344 |
The template context is extended by:
|
345 |
|
346 |
* users: list of pending users fitting in current page
|
347 |
* filter: search key
|
348 |
* pages: the number of pages
|
349 |
* prev: the previous page
|
350 |
* next: the current page
|
351 |
"""
|
352 |
user = AstakosUser.objects.get(id=user_id) |
353 |
user.is_active = True
|
354 |
user.save() |
355 |
status = messages.SUCCESS |
356 |
try:
|
357 |
_send_greeting(request, user, email_template_name) |
358 |
message = _('Greeting sent to %s' % user.email)
|
359 |
transaction.commit() |
360 |
except (SMTPException, socket.error) as e: |
361 |
status = messages.ERROR |
362 |
name = 'strerror'
|
363 |
message = getattr(e, name) if hasattr(e, name) else e |
364 |
transaction.rollback() |
365 |
messages.add_message(request, status, message) |
366 |
|
367 |
users = AstakosUser.objects.order_by('id')
|
368 |
users = users.filter(is_active = False)
|
369 |
|
370 |
try:
|
371 |
page = int(request.POST.get('page', 1)) |
372 |
except ValueError: |
373 |
page = 1
|
374 |
offset = max(0, page - 1) * ADMIN_PAGE_LIMIT |
375 |
limit = offset + ADMIN_PAGE_LIMIT |
376 |
|
377 |
npages = int(ceil(1.0 * users.count() / ADMIN_PAGE_LIMIT)) |
378 |
prev = page - 1 if page > 1 else None |
379 |
next = page + 1 if page < npages else None |
380 |
kwargs = {'users':users[offset:limit],
|
381 |
'filter':'', |
382 |
'pages':range(1, npages + 1), |
383 |
'page':page,
|
384 |
'prev':prev,
|
385 |
'next':next} |
386 |
return render_response(template_name,
|
387 |
context_instance = get_context(request, extra_context,**kwargs)) |
388 |
|
389 |
@requires_admin
|
390 |
def invitations_list(request, template_name='invitations_list.html', extra_context={}): |
391 |
"""
|
392 |
Displays a list with the Invitations.
|
393 |
|
394 |
If the ``request.user`` is not a superuser redirects to login page.
|
395 |
|
396 |
**Arguments**
|
397 |
|
398 |
``template_name``
|
399 |
A custom template to use. This is optional; if not specified,
|
400 |
this will default to ``invitations_list.html``.
|
401 |
|
402 |
``extra_context``
|
403 |
An dictionary of variables to add to the template context.
|
404 |
|
405 |
**Templates:**
|
406 |
|
407 |
invitations_list.html or ``template_name`` keyword argument.
|
408 |
|
409 |
**Template Context:**
|
410 |
|
411 |
The template context is extended by:
|
412 |
|
413 |
* invitations: list of invitations fitting in current page
|
414 |
* filter: search key
|
415 |
* pages: the number of pages
|
416 |
* prev: the previous page
|
417 |
* next: the current page
|
418 |
"""
|
419 |
invitations = Invitation.objects.order_by('id')
|
420 |
|
421 |
filter = request.GET.get('filter', '') |
422 |
if filter: |
423 |
if filter.startswith('-'): |
424 |
invitations = invitations.exclude(username__icontains=filter[1:]) |
425 |
else:
|
426 |
invitations = invitations.filter(username__icontains=filter)
|
427 |
|
428 |
try:
|
429 |
page = int(request.GET.get('page', 1)) |
430 |
except ValueError: |
431 |
page = 1
|
432 |
offset = max(0, page - 1) * ADMIN_PAGE_LIMIT |
433 |
limit = offset + ADMIN_PAGE_LIMIT |
434 |
|
435 |
npages = int(ceil(1.0 * invitations.count() / ADMIN_PAGE_LIMIT)) |
436 |
prev = page - 1 if page > 1 else None |
437 |
next = page + 1 if page < npages else None |
438 |
kwargs = {'invitations':invitations[offset:limit],
|
439 |
'filter':filter, |
440 |
'pages':range(1, npages + 1), |
441 |
'page':page,
|
442 |
'prev':prev,
|
443 |
'next':next} |
444 |
return render_response(template_name,
|
445 |
context_instance = get_context(request, extra_context,**kwargs)) |
446 |
|
447 |
@requires_admin
|
448 |
def invitations_export(request): |
449 |
"""
|
450 |
Exports the invitation list in csv file.
|
451 |
"""
|
452 |
# Create the HttpResponse object with the appropriate CSV header.
|
453 |
response = HttpResponse(mimetype='text/csv')
|
454 |
response['Content-Disposition'] = 'attachment; filename=invitations.csv' |
455 |
|
456 |
writer = csv.writer(response) |
457 |
writer.writerow(['ID',
|
458 |
'Username',
|
459 |
'Real Name',
|
460 |
'Code',
|
461 |
'Inviter username',
|
462 |
'Inviter Real Name',
|
463 |
'Is_accepted',
|
464 |
'Created',
|
465 |
'Consumed',])
|
466 |
invitations = Invitation.objects.order_by('id')
|
467 |
for inv in invitations: |
468 |
|
469 |
writer.writerow([inv.id, |
470 |
inv.username.encode("utf-8"),
|
471 |
inv.realname.encode("utf-8"),
|
472 |
inv.code, |
473 |
inv.inviter.username.encode("utf-8"),
|
474 |
inv.inviter.realname.encode("utf-8"),
|
475 |
inv.is_accepted, |
476 |
inv.created, |
477 |
inv.consumed]) |
478 |
|
479 |
return response
|
480 |
|
481 |
|
482 |
@requires_admin
|
483 |
def users_export(request): |
484 |
"""
|
485 |
Exports the user list in csv file.
|
486 |
"""
|
487 |
# Create the HttpResponse object with the appropriate CSV header.
|
488 |
response = HttpResponse(mimetype='text/csv')
|
489 |
response['Content-Disposition'] = 'attachment; filename=users.csv' |
490 |
|
491 |
writer = csv.writer(response) |
492 |
writer.writerow(['ID',
|
493 |
'Username',
|
494 |
'Real Name',
|
495 |
'Admin',
|
496 |
'Affiliation',
|
497 |
'Is active?',
|
498 |
'Quota (GiB)',
|
499 |
'Updated',])
|
500 |
users = AstakosUser.objects.order_by('id')
|
501 |
for u in users: |
502 |
writer.writerow([u.id, |
503 |
u.username.encode("utf-8"),
|
504 |
u.realname.encode("utf-8"),
|
505 |
u.is_superuser, |
506 |
u.affiliation.encode("utf-8"),
|
507 |
u.is_active, |
508 |
u.quota, |
509 |
u.updated]) |
510 |
|
511 |
return response
|
512 |
|
513 |
@requires_admin
|
514 |
def users_create(request, template_name='users_create.html', extra_context={}): |
515 |
"""
|
516 |
Creates a user. Upon success redirect to ``users_info`` view.
|
517 |
|
518 |
**Arguments**
|
519 |
|
520 |
``template_name``
|
521 |
A custom template to use. This is optional; if not specified,
|
522 |
this will default to ``users_create.html``.
|
523 |
|
524 |
``extra_context``
|
525 |
An dictionary of variables to add to the template context.
|
526 |
|
527 |
**Templates:**
|
528 |
|
529 |
users_create.html or ``template_name`` keyword argument.
|
530 |
"""
|
531 |
if request.method == 'GET': |
532 |
form = AdminUserCreationForm() |
533 |
elif request.method == 'POST': |
534 |
form = AdminUserCreationForm(request.POST) |
535 |
if form.is_valid():
|
536 |
try:
|
537 |
user = form.save() |
538 |
return users_info(request, user.id)
|
539 |
except ValueError, e: |
540 |
messages.add_message(request, messages.ERROR, e) |
541 |
return render_response(template_name,
|
542 |
form = form, |
543 |
context_instance=get_context(request, extra_context)) |