Statistics
| Branch: | Tag: | Revision:

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))