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