Admin improvements
[pithos] / pithos / im / views.py
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
38 from datetime import datetime
39 from functools import wraps
40 from math import ceil
41 from random import randint
42 from smtplib import SMTPException
43
44 from django.conf import settings
45 from django.core.mail import send_mail
46 from django.http import HttpResponse, HttpResponseRedirect
47 from django.shortcuts import redirect
48 from django.template.loader import render_to_string
49 from django.utils.http import urlencode
50 from django.utils.translation import ugettext as _
51
52 from pithos.im.models import User, Invitation
53 from pithos.im.util import isoformat
54
55
56 def render_response(template, tab=None, status=200, **kwargs):
57     if tab is None:
58         tab = template.partition('_')[0]
59     kwargs.setdefault('tab', tab)
60     html = render_to_string(template, kwargs)
61     return HttpResponse(html, status=status)
62
63
64 def requires_login(func):
65     @wraps(func)
66     def wrapper(request, *args):
67         if not settings.BYPASS_ADMIN_AUTH:
68             if not request.user:
69                 next = urlencode({'next': request.build_absolute_uri()})
70                 login_uri = settings.LOGIN_URL + '?' + next
71                 return HttpResponseRedirect(login_uri)
72         return func(request, *args)
73     return wrapper
74
75
76 def requires_admin(func):
77     @wraps(func)
78     def wrapper(request, *args):
79         if not settings.BYPASS_ADMIN_AUTH:
80             if not request.user:
81                 next = urlencode({'next': request.build_absolute_uri()})
82                 login_uri = settings.LOGIN_URL + '?' + next
83                 return HttpResponseRedirect(login_uri)
84             if not request.user_obj.is_admin:
85                 return HttpResponse('Forbidden', status=403)
86         return func(request, *args)
87     return wrapper
88
89
90 def index(request):
91     # TODO: Get and pass on next variable.
92     return render_response('index.html')
93
94
95 @requires_admin
96 def admin(request):
97     stats = {}
98     stats['users'] = User.objects.count()
99     
100     invitations = Invitation.objects.all()
101     stats['invitations'] = invitations.count()
102     stats['invitations_accepted'] = invitations.filter(is_accepted=True).count()
103     
104     return render_response('admin.html', tab='home', stats=stats)
105
106
107 @requires_admin
108 def users_list(request):
109     users = User.objects.order_by('id')
110     
111     filter = request.GET.get('filter', '')
112     if filter:
113         if filter.startswith('-'):
114             users = users.exclude(uniq__icontains=filter[1:])
115         else:
116             users = users.filter(uniq__icontains=filter)
117     
118     try:
119         page = int(request.GET.get('page', 1))
120     except ValueError:
121         page = 1
122     offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
123     limit = offset + settings.ADMIN_PAGE_LIMIT
124     
125     npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
126     prev = page - 1 if page > 1 else None
127     next = page + 1 if page < npages else None
128     return render_response('users_list.html',
129                             users=users[offset:limit],
130                             filter=filter,
131                             pages=range(1, npages + 1),
132                             page=page,
133                             prev=prev,
134                             next=next)
135
136
137 @requires_admin
138 def users_create(request):
139     if request.method == 'GET':
140         return render_response('users_create.html')
141     if request.method == 'POST':
142         user = User()
143         user.uniq = request.POST.get('uniq')
144         user.realname = request.POST.get('realname')
145         user.is_admin = True if request.POST.get('admin') else False
146         user.affiliation = request.POST.get('affiliation')
147         user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
148         user.renew_token()
149         user.save()
150         return redirect(users_info, user.id)
151
152
153 @requires_admin
154 def users_info(request, user_id):
155     user = User.objects.get(id=user_id)
156     states = [x[0] for x in User.ACCOUNT_STATE]
157     return render_response('users_info.html',
158                             user=user,
159                             states=states)
160
161
162 @requires_admin
163 def users_modify(request, user_id):
164     user = User.objects.get(id=user_id)
165     user.uniq = request.POST.get('uniq')
166     user.realname = request.POST.get('realname')
167     user.is_admin = True if request.POST.get('admin') else False
168     user.affiliation = request.POST.get('affiliation')
169     user.state = request.POST.get('state')
170     user.invitations = int(request.POST.get('invitations') or 0)
171     user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
172     user.auth_token = request.POST.get('auth_token')
173     try:
174         auth_token_expires = request.POST.get('auth_token_expires')
175         d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
176         user.auth_token_expires = d
177     except ValueError:
178         pass
179     user.save()
180     return redirect(users_info, user.id)
181
182
183 @requires_admin
184 def users_delete(request, user_id):
185     user = User.objects.get(id=user_id)
186     user.delete()
187     return redirect(users_list)
188
189
190 def generate_invitation_code():
191     return randint(1, 2L**63 - 1)
192
193
194 def send_invitation(inv):
195     url = settings.INVITATION_LOGIN_TARGET % inv.code
196     subject = _('Invitation to Pithos')
197     message = render_to_string('invitation.txt', {
198                 'invitation': inv,
199                 'url': url})
200     sender = settings.DEFAULT_FROM_EMAIL
201     send_mail(subject, message, sender, [inv.uniq])
202     inv.inviter.invitations = max(0, inv.inviter.invitations - 1)
203     inv.inviter.save()
204     logging.info('Sent invitation %s', inv)
205
206
207 @requires_login
208 def invite(request):
209     status = None
210     message = None
211
212     if request.method == 'POST':
213         if request.user_obj.invitations > 0:
214             code = generate_invitation_code()
215             invitation, created = Invitation.objects.get_or_create(code=code)
216             invitation.inviter=request.user_obj
217             invitation.realname=request.POST.get('realname')
218             invitation.uniq=request.POST.get('uniq')
219             invitation.save()
220             
221             try:
222                 send_invitation(invitation)
223                 status = 'success'
224                 message = _('Invitation sent to %s' % invitation.uniq)
225             except (SMTPException, socket.error) as e:
226                 status = 'error'
227                 message = e.strerror
228         else:
229             status = 'error'
230             message = _('No invitations left')
231
232     if request.GET.get('format') == 'json':
233         rep = {'invitations': request.user_obj.invitations}
234         return HttpResponse(json.dumps(rep))
235     
236     html = render_to_string('invitations.html', {
237             'user': request.user_obj,
238             'status': status,
239             'message': message})
240     return HttpResponse(html)