1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
40 from datetime import datetime
41 from functools import wraps
43 from random import randint
44 from smtplib import SMTPException
45 from hashlib import new as newhasher
46 from urllib import quote
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 Site
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
68 def requires_admin(func):
70 Decorator checkes whether the request.user is a superuser and if not
71 redirects to login page.
74 def wrapper(request, *args):
75 if not settings.BYPASS_ADMIN_AUTH:
76 if request.user.is_anonymous():
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)
86 def admin(request, template_name='admin.html', extra_context={}):
88 Renders the admin page
90 If the ``request.user`` is not a superuser redirects to login page.
95 A custom template to use. This is optional; if not specified,
96 this will default to ``admin.html``.
99 An dictionary of variables to add to the template context.
103 admin.html or ``template_name`` keyword argument.
105 **Template Context:**
107 The template context is extended by:
109 * tab: the name of the active tab
110 * stats: dictionary containing the number of all and prending users
113 stats['users'] = AstakosUser.objects.count()
114 stats['pending'] = AstakosUser.objects.filter(is_active = False).count()
116 invitations = Invitation.objects.all()
117 stats['invitations'] = invitations.count()
118 stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
120 kwargs = {'tab': 'home', 'stats': stats}
121 context = get_context(request, extra_context,**kwargs)
122 return render_response(template_name, context_instance = context)
125 def users_list(request, template_name='users_list.html', extra_context={}):
127 Displays the list of all users.
129 If the ``request.user`` is not a superuser redirects to login page.
134 A custom template to use. This is optional; if not specified,
135 this will default to ``users_list.html``.
138 An dictionary of variables to add to the template context.
142 users_list.html or ``template_name`` keyword argument.
144 **Template Context:**
146 The template context is extended by:
148 * users: list of users fitting in current page
150 * pages: the number of pages
151 * prev: the previous page
152 * next: the current page
156 * ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
158 users = AstakosUser.objects.order_by('id')
160 filter = request.GET.get('filter', '')
162 if filter.startswith('-'):
163 users = users.exclude(username__icontains=filter[1:])
165 users = users.filter(username__icontains=filter)
168 page = int(request.GET.get('page', 1))
171 offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
172 limit = offset + settings.ADMIN_PAGE_LIMIT
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
178 kwargs = {'users':users[offset:limit],
180 'pages':range(1, npages + 1),
183 context = get_context(request, extra_context,**kwargs)
184 return render_response(template_name, context_instance = context)
187 def users_info(request, user_id, template_name='users_info.html', extra_context={}):
189 Displays the specific user profile.
191 If the ``request.user`` is not a superuser redirects to login page.
196 A custom template to use. This is optional; if not specified,
197 this will default to ``users_info.html``.
200 An dictionary of variables to add to the template context.
204 users_info.html or ``template_name`` keyword argument.
206 **Template Context:**
208 The template context is extended by:
210 * user: the user instance identified by ``user_id`` keyword argument
212 if not 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))
220 def users_modify(request, user_id, template_name='users_info.html', extra_context={}):
222 Update the specific user information. Upon success redirects to ``user_info`` view.
224 If the ``request.user`` is not a superuser redirects to login page.
226 user = AstakosUser.objects.get(id = user_id)
227 form = AdminProfileForm(request.POST, instance=user)
230 return redirect(users_info, user.id, template_name, extra_context)
231 return render_response(template_name,
233 context_instance = get_context(request, extra_context))
236 def users_delete(request, user_id):
238 Deletes the specified user
240 If the ``request.user`` is not a superuser redirects to login page.
242 user = AstakosUser.objects.get(id=user_id)
244 return redirect(users_list)
247 def pending_users(request, template_name='pending_users.html', extra_context={}):
249 Displays the list of the pending users.
251 If the ``request.user`` is not a superuser redirects to login page.
256 A custom template to use. This is optional; if not specified,
257 this will default to ``users_list.html``.
260 An dictionary of variables to add to the template context.
264 pending_users.html or ``template_name`` keyword argument.
266 **Template Context:**
268 The template context is extended by:
270 * users: list of pending users fitting in current page
272 * pages: the number of pages
273 * prev: the previous page
274 * next: the current page
278 * ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
280 users = AstakosUser.objects.order_by('id')
282 users = users.filter(is_active = False)
284 filter = request.GET.get('filter', '')
286 if filter.startswith('-'):
287 users = users.exclude(username__icontains=filter[1:])
289 users = users.filter(username__icontains=filter)
292 page = int(request.GET.get('page', 1))
295 offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
296 limit = offset + settings.ADMIN_PAGE_LIMIT
298 npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
299 prev = page - 1 if page > 1 else None
300 next = page + 1 if page < npages else None
301 kwargs = {'users':users[offset:limit],
303 'pages':range(1, npages + 1),
307 return render_response(template_name,
308 context_instance = get_context(request, extra_context,**kwargs))
310 def _send_greeting(request, user, template_name):
311 subject = _('Welcome to %s' %settings.SERVICE_NAME)
312 site = Site.objects.get_current()
313 baseurl = request.build_absolute_uri('/').rstrip('/')
314 message = render_to_string(template_name, {
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)
325 @transaction.commit_manually
326 def users_activate(request, user_id, template_name='pending_users.html', extra_context={}, email_template_name='welcome_email.txt'):
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``.
331 If the ``request.user`` is not a superuser redirects to login page.
336 A custom template to use. This is optional; if not specified,
337 this will default to ``users_list.html``.
340 An dictionary of variables to add to the template context.
344 pending_users.html or ``template_name`` keyword argument.
345 welcome_email.txt or ``email_template_name`` keyword argument.
347 **Template Context:**
349 The template context is extended by:
351 * users: list of pending users fitting in current page
353 * pages: the number of pages
354 * prev: the previous page
355 * next: the current page
357 user = AstakosUser.objects.get(id=user_id)
358 user.is_active = True
360 status = messages.SUCCESS
362 _send_greeting(request, user, email_template_name)
363 message = _('Greeting sent to %s' % user.email)
365 except (SMTPException, socket.error) as e:
366 status = messages.ERROR
368 message = getattr(e, name) if hasattr(e, name) else e
369 transaction.rollback()
370 messages.add_message(request, status, message)
372 users = AstakosUser.objects.order_by('id')
373 users = users.filter(is_active = False)
376 page = int(request.POST.get('page', 1))
379 offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
380 limit = offset + settings.ADMIN_PAGE_LIMIT
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],
387 'pages':range(1, npages + 1),
391 return render_response(template_name,
392 context_instance = get_context(request, extra_context,**kwargs))
395 def invitations_list(request, template_name='invitations_list.html', extra_context={}):
397 Displays a list with the Invitations.
399 If the ``request.user`` is not a superuser redirects to login page.
404 A custom template to use. This is optional; if not specified,
405 this will default to ``invitations_list.html``.
408 An dictionary of variables to add to the template context.
412 invitations_list.html or ``template_name`` keyword argument.
414 **Template Context:**
416 The template context is extended by:
418 * invitations: list of invitations fitting in current page
420 * pages: the number of pages
421 * prev: the previous page
422 * next: the current page
424 invitations = Invitation.objects.order_by('id')
426 filter = request.GET.get('filter', '')
428 if filter.startswith('-'):
429 invitations = invitations.exclude(username__icontains=filter[1:])
431 invitations = invitations.filter(username__icontains=filter)
434 page = int(request.GET.get('page', 1))
437 offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
438 limit = offset + settings.ADMIN_PAGE_LIMIT
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],
445 'pages':range(1, npages + 1),
449 return render_response(template_name,
450 context_instance = get_context(request, extra_context,**kwargs))
453 def invitations_export(request):
455 Exports the invitation list in csv file.
457 # Create the HttpResponse object with the appropriate CSV header.
458 response = HttpResponse(mimetype='text/csv')
459 response['Content-Disposition'] = 'attachment; filename=invitations.csv'
461 writer = csv.writer(response)
462 writer.writerow(['ID',
471 invitations = Invitation.objects.order_by('id')
472 for inv in invitations:
474 writer.writerow([inv.id,
475 inv.username.encode("utf-8"),
476 inv.realname.encode("utf-8"),
478 inv.inviter.username.encode("utf-8"),
479 inv.inviter.realname.encode("utf-8"),
488 def users_export(request):
490 Exports the user list in csv file.
492 # Create the HttpResponse object with the appropriate CSV header.
493 response = HttpResponse(mimetype='text/csv')
494 response['Content-Disposition'] = 'attachment; filename=users.csv'
496 writer = csv.writer(response)
497 writer.writerow(['ID',
505 users = AstakosUser.objects.order_by('id')
507 writer.writerow([u.id,
508 u.username.encode("utf-8"),
509 u.realname.encode("utf-8"),
511 u.affiliation.encode("utf-8"),
519 def users_create(request, template_name='users_create.html', extra_context={}):
521 Creates a user. Upon success redirect to ``users_info`` view.
526 A custom template to use. This is optional; if not specified,
527 this will default to ``users_create.html``.
530 An dictionary of variables to add to the template context.
534 users_create.html or ``template_name`` keyword argument.
536 if request.method == 'GET':
537 return render_response(template_name,
538 context_instance=get_context(request, extra_context))
539 if request.method == 'POST':
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
549 user.provider = 'local'
551 return redirect(users_info, user.id)