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 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)
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 form = AdminProfileForm(request.POST)
229 return redirect(users_info, user.id, template_name, extra_context)
230 return render_response(template_name,
232 context_instance = get_context(request, extra_context))
235 def users_delete(request, user_id):
237 Deletes the specified user
239 If the ``request.user`` is not a superuser redirects to login page.
241 user = AstakosUser.objects.get(id=user_id)
243 return redirect(users_list)
246 def pending_users(request, template_name='pending_users.html', extra_context={}):
248 Displays the list of the pending users.
250 If the ``request.user`` is not a superuser redirects to login page.
255 A custom template to use. This is optional; if not specified,
256 this will default to ``users_list.html``.
259 An dictionary of variables to add to the template context.
263 pending_users.html or ``template_name`` keyword argument.
265 **Template Context:**
267 The template context is extended by:
269 * users: list of pending users fitting in current page
271 * pages: the number of pages
272 * prev: the previous page
273 * next: the current page
277 * ADMIN_PAGE_LIMIT: Show these many users per page in admin interface
279 users = AstakosUser.objects.order_by('id')
281 users = users.filter(is_active = False)
283 filter = request.GET.get('filter', '')
285 if filter.startswith('-'):
286 users = users.exclude(username__icontains=filter[1:])
288 users = users.filter(username__icontains=filter)
291 page = int(request.GET.get('page', 1))
294 offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
295 limit = offset + settings.ADMIN_PAGE_LIMIT
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],
302 'pages':range(1, npages + 1),
306 return render_response(template_name,
307 context_instance = get_context(request, extra_context,**kwargs))
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 = 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)