Statistics
| Branch: | Tag: | Revision:

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)