Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ b6852727

History | View | Annotate | Download (50 kB)

1
# Copyright 2011-2012 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 calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

    
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime, timedelta
43
from collections import defaultdict
44

    
45
from django.contrib import messages
46
from django.contrib.auth.decorators import login_required
47
from django.contrib.auth.views import password_change
48
from django.core.urlresolvers import reverse
49
from django.db import transaction
50
from django.db.models import Q
51
from django.db.utils import IntegrityError
52
from django.forms.fields import URLField
53
from django.http import (HttpResponse, HttpResponseBadRequest,
54
                         HttpResponseForbidden, HttpResponseRedirect,
55
                         HttpResponseBadRequest, Http404)
56
from django.shortcuts import redirect
57
from django.template import RequestContext, loader as template_loader
58
from django.utils.http import urlencode
59
from django.utils.translation import ugettext as _
60
from django.views.generic.create_update import (create_object, delete_object,
61
                                                get_model_and_form_class)
62
from django.views.generic.list_detail import object_list, object_detail
63
from django.http import HttpResponseBadRequest
64
from django.core.xheaders import populate_xheaders
65

    
66
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
67
                               Resource, EmailChange, GroupKind, Membership,
68
                               AstakosGroupQuota, RESOURCE_SEPARATOR)
69
from django.views.decorators.http import require_http_methods
70
from django.db.models.query import QuerySet
71

    
72
from astakos.im.activation_backends import get_backend, SimpleBackend
73
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
74
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
75
                              FeedbackForm, SignApprovalTermsForm,
76
                              ExtendedPasswordChangeForm, EmailChangeForm,
77
                              AstakosGroupCreationForm, AstakosGroupSearchForm,
78
                              AstakosGroupUpdateForm, AddGroupMembersForm,
79
                              AstakosGroupSortForm, MembersSortForm,
80
                              TimelineForm, PickResourceForm,
81
                              AstakosGroupCreationSummaryForm)
82
from astakos.im.functions import (send_feedback, SendMailError,
83
                                  logout as auth_logout,
84
                                  activate as activate_func,
85
                                  switch_account_to_shibboleth,
86
                                  send_group_creation_notification,
87
                                  SendNotificationError)
88
from astakos.im.endpoints.qh import timeline_charge
89
from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, LOGOUT_NEXT,
90
                                 LOGGING_LEVEL, PAGINATE_BY)
91
from astakos.im.tasks import request_billing
92
from astakos.im.api.callpoint import AstakosCallpoint
93

    
94
import astakos.im.messages as astakos_messages
95

    
96
logger = logging.getLogger(__name__)
97

    
98

    
99
DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
100
                                     'https://', '')"""
101

    
102
callpoint = AstakosCallpoint()
103

    
104
def render_response(template, tab=None, status=200, reset_cookie=False,
105
                    context_instance=None, **kwargs):
106
    """
107
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
108
    keyword argument and returns an ``django.http.HttpResponse`` with the
109
    specified ``status``.
110
    """
111
    if tab is None:
112
        tab = template.partition('_')[0].partition('.html')[0]
113
    kwargs.setdefault('tab', tab)
114
    html = template_loader.render_to_string(
115
        template, kwargs, context_instance=context_instance)
116
    response = HttpResponse(html, status=status)
117
    if reset_cookie:
118
        set_cookie(response, context_instance['request'].user)
119
    return response
120

    
121

    
122
def requires_anonymous(func):
123
    """
124
    Decorator checkes whether the request.user is not Anonymous and in that case
125
    redirects to `logout`.
126
    """
127
    @wraps(func)
128
    def wrapper(request, *args):
129
        if not request.user.is_anonymous():
130
            next = urlencode({'next': request.build_absolute_uri()})
131
            logout_uri = reverse(logout) + '?' + next
132
            return HttpResponseRedirect(logout_uri)
133
        return func(request, *args)
134
    return wrapper
135

    
136

    
137
def signed_terms_required(func):
138
    """
139
    Decorator checkes whether the request.user is Anonymous and in that case
140
    redirects to `logout`.
141
    """
142
    @wraps(func)
143
    def wrapper(request, *args, **kwargs):
144
        if request.user.is_authenticated() and not request.user.signed_terms:
145
            params = urlencode({'next': request.build_absolute_uri(),
146
                                'show_form': ''})
147
            terms_uri = reverse('latest_terms') + '?' + params
148
            return HttpResponseRedirect(terms_uri)
149
        return func(request, *args, **kwargs)
150
    return wrapper
151

    
152

    
153
@require_http_methods(["GET", "POST"])
154
@signed_terms_required
155
def index(request, login_template_name='im/login.html', extra_context=None):
156
    """
157
    If there is logged on user renders the profile page otherwise renders login page.
158

159
    **Arguments**
160

161
    ``login_template_name``
162
        A custom login template to use. This is optional; if not specified,
163
        this will default to ``im/login.html``.
164

165
    ``profile_template_name``
166
        A custom profile template to use. This is optional; if not specified,
167
        this will default to ``im/profile.html``.
168

169
    ``extra_context``
170
        An dictionary of variables to add to the template context.
171

172
    **Template:**
173

174
    im/profile.html or im/login.html or ``template_name`` keyword argument.
175

176
    """
177
    template_name = login_template_name
178
    if request.user.is_authenticated():
179
        return HttpResponseRedirect(reverse('edit_profile'))
180
    return render_response(template_name,
181
                           login_form=LoginForm(request=request),
182
                           context_instance=get_context(request, extra_context))
183

    
184

    
185
@require_http_methods(["GET", "POST"])
186
@login_required
187
@signed_terms_required
188
@transaction.commit_manually
189
def invite(request, template_name='im/invitations.html', extra_context=None):
190
    """
191
    Allows a user to invite somebody else.
192

193
    In case of GET request renders a form for providing the invitee information.
194
    In case of POST checks whether the user has not run out of invitations and then
195
    sends an invitation email to singup to the service.
196

197
    The view uses commit_manually decorator in order to ensure the number of the
198
    user invitations is going to be updated only if the email has been successfully sent.
199

200
    If the user isn't logged in, redirects to settings.LOGIN_URL.
201

202
    **Arguments**
203

204
    ``template_name``
205
        A custom template to use. This is optional; if not specified,
206
        this will default to ``im/invitations.html``.
207

208
    ``extra_context``
209
        An dictionary of variables to add to the template context.
210

211
    **Template:**
212

213
    im/invitations.html or ``template_name`` keyword argument.
214

215
    **Settings:**
216

217
    The view expectes the following settings are defined:
218

219
    * LOGIN_URL: login uri
220
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
221
    """
222
    status = None
223
    message = None
224
    form = InvitationForm()
225

    
226
    inviter = request.user
227
    if request.method == 'POST':
228
        form = InvitationForm(request.POST)
229
        if inviter.invitations > 0:
230
            if form.is_valid():
231
                try:
232
                    email = form.cleaned_data.get('username')
233
                    realname = form.cleaned_data.get('realname')
234
                    inviter.invite(email, realname)
235
                    message = _(astakos_messages.INVITATION_SENT) % locals()
236
                    messages.success(request, message)
237
                except SendMailError, e:
238
                    message = e.message
239
                    messages.error(request, message)
240
                    transaction.rollback()
241
                except BaseException, e:
242
                    message = _(astakos_messages.GENERIC_ERROR)
243
                    messages.error(request, message)
244
                    logger.exception(e)
245
                    transaction.rollback()
246
                else:
247
                    transaction.commit()
248
        else:
249
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
250
            messages.error(request, message)
251

    
252
    sent = [{'email': inv.username,
253
             'realname': inv.realname,
254
             'is_consumed': inv.is_consumed}
255
            for inv in request.user.invitations_sent.all()]
256
    kwargs = {'inviter': inviter,
257
              'sent': sent}
258
    context = get_context(request, extra_context, **kwargs)
259
    return render_response(template_name,
260
                           invitation_form=form,
261
                           context_instance=context)
262

    
263

    
264
@require_http_methods(["GET", "POST"])
265
@login_required
266
@signed_terms_required
267
def edit_profile(request, template_name='im/profile.html', extra_context=None):
268
    """
269
    Allows a user to edit his/her profile.
270

271
    In case of GET request renders a form for displaying the user information.
272
    In case of POST updates the user informantion and redirects to ``next``
273
    url parameter if exists.
274

275
    If the user isn't logged in, redirects to settings.LOGIN_URL.
276

277
    **Arguments**
278

279
    ``template_name``
280
        A custom template to use. This is optional; if not specified,
281
        this will default to ``im/profile.html``.
282

283
    ``extra_context``
284
        An dictionary of variables to add to the template context.
285

286
    **Template:**
287

288
    im/profile.html or ``template_name`` keyword argument.
289

290
    **Settings:**
291

292
    The view expectes the following settings are defined:
293

294
    * LOGIN_URL: login uri
295
    """
296
    extra_context = extra_context or {}
297
    form = ProfileForm(instance=request.user)
298
    extra_context['next'] = request.GET.get('next')
299
    reset_cookie = False
300
    if request.method == 'POST':
301
        form = ProfileForm(request.POST, instance=request.user)
302
        if form.is_valid():
303
            try:
304
                prev_token = request.user.auth_token
305
                user = form.save()
306
                reset_cookie = user.auth_token != prev_token
307
                form = ProfileForm(instance=user)
308
                next = request.POST.get('next')
309
                if next:
310
                    return redirect(next)
311
                msg = _(astakos_messages.PROFILE_UPDATED)
312
                messages.success(request, msg)
313
            except ValueError, ve:
314
                messages.success(request, ve)
315
    elif request.method == "GET":
316
        if not request.user.is_verified:
317
            request.user.is_verified = True
318
            request.user.save()
319
    return render_response(template_name,
320
                           reset_cookie=reset_cookie,
321
                           profile_form=form,
322
                           context_instance=get_context(request,
323
                                                        extra_context))
324

    
325

    
326
@transaction.commit_manually
327
@require_http_methods(["GET", "POST"])
328
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
329
    """
330
    Allows a user to create a local account.
331

332
    In case of GET request renders a form for entering the user information.
333
    In case of POST handles the signup.
334

335
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
336
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
337
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
338
    (see activation_backends);
339

340
    Upon successful user creation, if ``next`` url parameter is present the user is redirected there
341
    otherwise renders the same page with a success message.
342

343
    On unsuccessful creation, renders ``template_name`` with an error message.
344

345
    **Arguments**
346

347
    ``template_name``
348
        A custom template to render. This is optional;
349
        if not specified, this will default to ``im/signup.html``.
350

351
    ``on_success``
352
        A custom template to render in case of success. This is optional;
353
        if not specified, this will default to ``im/signup_complete.html``.
354

355
    ``extra_context``
356
        An dictionary of variables to add to the template context.
357

358
    **Template:**
359

360
    im/signup.html or ``template_name`` keyword argument.
361
    im/signup_complete.html or ``on_success`` keyword argument.
362
    """
363
    if request.user.is_authenticated():
364
        return HttpResponseRedirect(reverse('edit_profile'))
365

    
366
    provider = get_query(request).get('provider', 'local')
367
    try:
368
        if not backend:
369
            backend = get_backend(request)
370
        form = backend.get_signup_form(provider)
371
    except Exception, e:
372
        form = SimpleBackend(request).get_signup_form(provider)
373
        messages.error(request, e)
374
    if request.method == 'POST':
375
        if form.is_valid():
376
            user = form.save(commit=False)
377
            try:
378
                result = backend.handle_activation(user)
379
                status = messages.SUCCESS
380
                message = result.message
381
                user.save()
382
                if 'additional_email' in form.cleaned_data:
383
                    additional_email = form.cleaned_data['additional_email']
384
                    if additional_email != user.email:
385
                        user.additionalmail_set.create(email=additional_email)
386
                        msg = 'Additional email: %s saved for user %s.' % (
387
                            additional_email, user.email)
388
                        logger.log(LOGGING_LEVEL, msg)
389
                if user and user.is_active:
390
                    next = request.POST.get('next', '')
391
                    response = prepare_response(request, user, next=next)
392
                    transaction.commit()
393
                    return response
394
                messages.add_message(request, status, message)
395
                transaction.commit()
396
                return render_response(on_success,
397
                                       context_instance=get_context(request, extra_context))
398
            except SendMailError, e:
399
                message = e.message
400
                messages.error(request, message)
401
                transaction.rollback()
402
            except BaseException, e:
403
                message = _(astakos_messages.GENERIC_ERROR)
404
                messages.error(request, message)
405
                logger.exception(e)
406
                transaction.rollback()
407
    return render_response(template_name,
408
                           signup_form=form,
409
                           provider=provider,
410
                           context_instance=get_context(request, extra_context))
411

    
412

    
413
@require_http_methods(["GET", "POST"])
414
@login_required
415
@signed_terms_required
416
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
417
    """
418
    Allows a user to send feedback.
419

420
    In case of GET request renders a form for providing the feedback information.
421
    In case of POST sends an email to support team.
422

423
    If the user isn't logged in, redirects to settings.LOGIN_URL.
424

425
    **Arguments**
426

427
    ``template_name``
428
        A custom template to use. This is optional; if not specified,
429
        this will default to ``im/feedback.html``.
430

431
    ``extra_context``
432
        An dictionary of variables to add to the template context.
433

434
    **Template:**
435

436
    im/signup.html or ``template_name`` keyword argument.
437

438
    **Settings:**
439

440
    * LOGIN_URL: login uri
441
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
442
    """
443
    if request.method == 'GET':
444
        form = FeedbackForm()
445
    if request.method == 'POST':
446
        if not request.user:
447
            return HttpResponse('Unauthorized', status=401)
448

    
449
        form = FeedbackForm(request.POST)
450
        if form.is_valid():
451
            msg = form.cleaned_data['feedback_msg']
452
            data = form.cleaned_data['feedback_data']
453
            try:
454
                send_feedback(msg, data, request.user, email_template_name)
455
            except SendMailError, e:
456
                messages.error(request, message)
457
            else:
458
                message = _(astakos_messages.FEEDBACK_SENT)
459
                messages.success(request, message)
460
    return render_response(template_name,
461
                           feedback_form=form,
462
                           context_instance=get_context(request, extra_context))
463

    
464

    
465
@require_http_methods(["GET", "POST"])
466
@signed_terms_required
467
def logout(request, template='registration/logged_out.html', extra_context=None):
468
    """
469
    Wraps `django.contrib.auth.logout` and delete the cookie.
470
    """
471
    response = HttpResponse()
472
    if request.user.is_authenticated():
473
        email = request.user.email
474
        auth_logout(request)
475
        response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
476
        msg = 'Cookie deleted for %s' % email
477
        logger.log(LOGGING_LEVEL, msg)
478
    next = request.GET.get('next')
479
    if next:
480
        response['Location'] = next
481
        response.status_code = 302
482
        return response
483
    elif LOGOUT_NEXT:
484
        response['Location'] = LOGOUT_NEXT
485
        response.status_code = 301
486
        return response
487
    messages.success(request, _(astakos_messages.LOGOUT_SUCCESS))
488
    context = get_context(request, extra_context)
489
    response.write(
490
        template_loader.render_to_string(template, context_instance=context))
491
    return response
492

    
493

    
494
@require_http_methods(["GET", "POST"])
495
@transaction.commit_manually
496
def activate(request, greeting_email_template_name='im/welcome_email.txt',
497
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
498
    """
499
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
500
    and renews the user token.
501

502
    The view uses commit_manually decorator in order to ensure the user state will be updated
503
    only if the email will be send successfully.
504
    """
505
    token = request.GET.get('auth')
506
    next = request.GET.get('next')
507
    try:
508
        user = AstakosUser.objects.get(auth_token=token)
509
    except AstakosUser.DoesNotExist:
510
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
511

    
512
    if user.is_active:
513
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
514
        messages.error(request, message)
515
        return index(request)
516

    
517
    try:
518
        local_user = AstakosUser.objects.get(
519
            ~Q(id=user.id),
520
            email=user.email,
521
            is_active=True
522
        )
523
    except AstakosUser.DoesNotExist:
524
        try:
525
            activate_func(
526
                user,
527
                greeting_email_template_name,
528
                helpdesk_email_template_name,
529
                verify_email=True
530
            )
531
            response = prepare_response(request, user, next, renew=True)
532
            transaction.commit()
533
            return response
534
        except SendMailError, e:
535
            message = e.message
536
            messages.error(request, message)
537
            transaction.rollback()
538
            return index(request)
539
        except BaseException, e:
540
            message = _(astakos_messages.GENERIC_ERROR)
541
            messages.error(request, message)
542
            logger.exception(e)
543
            transaction.rollback()
544
            return index(request)
545
    else:
546
        try:
547
            user = switch_account_to_shibboleth(
548
                user,
549
                local_user,
550
                greeting_email_template_name
551
            )
552
            response = prepare_response(request, user, next, renew=True)
553
            transaction.commit()
554
            return response
555
        except SendMailError, e:
556
            message = e.message
557
            messages.error(request, message)
558
            transaction.rollback()
559
            return index(request)
560
        except BaseException, e:
561
            message = _(astakos_messages.GENERIC_ERROR)
562
            messages.error(request, message)
563
            logger.exception(e)
564
            transaction.rollback()
565
            return index(request)
566

    
567

    
568
@require_http_methods(["GET", "POST"])
569
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
570
    term = None
571
    terms = None
572
    if not term_id:
573
        try:
574
            term = ApprovalTerms.objects.order_by('-id')[0]
575
        except IndexError:
576
            pass
577
    else:
578
        try:
579
            term = ApprovalTerms.objects.get(id=term_id)
580
        except ApprovalTerms.DoesNotExist, e:
581
            pass
582

    
583
    if not term:
584
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
585
        return HttpResponseRedirect(reverse('index'))
586
    f = open(term.location, 'r')
587
    terms = f.read()
588

    
589
    if request.method == 'POST':
590
        next = request.POST.get('next')
591
        if not next:
592
            next = reverse('index')
593
        form = SignApprovalTermsForm(request.POST, instance=request.user)
594
        if not form.is_valid():
595
            return render_response(template_name,
596
                                   terms=terms,
597
                                   approval_terms_form=form,
598
                                   context_instance=get_context(request, extra_context))
599
        user = form.save()
600
        return HttpResponseRedirect(next)
601
    else:
602
        form = None
603
        if request.user.is_authenticated() and not request.user.signed_terms:
604
            form = SignApprovalTermsForm(instance=request.user)
605
        return render_response(template_name,
606
                               terms=terms,
607
                               approval_terms_form=form,
608
                               context_instance=get_context(request, extra_context))
609

    
610

    
611
@require_http_methods(["GET", "POST"])
612
@signed_terms_required
613
def change_password(request):
614
    return password_change(request,
615
                           post_change_redirect=reverse('edit_profile'),
616
                           password_change_form=ExtendedPasswordChangeForm)
617

    
618

    
619
@require_http_methods(["GET", "POST"])
620
@signed_terms_required
621
@login_required
622
@transaction.commit_manually
623
def change_email(request, activation_key=None,
624
                 email_template_name='registration/email_change_email.txt',
625
                 form_template_name='registration/email_change_form.html',
626
                 confirm_template_name='registration/email_change_done.html',
627
                 extra_context=None):
628
    if activation_key:
629
        try:
630
            user = EmailChange.objects.change_email(activation_key)
631
            if request.user.is_authenticated() and request.user == user:
632
                msg = _(astakos_messages.EMAIL_CHANGED)
633
                messages.success(request, msg)
634
                auth_logout(request)
635
                response = prepare_response(request, user)
636
                transaction.commit()
637
                return response
638
        except ValueError, e:
639
            messages.error(request, e)
640
        return render_response(confirm_template_name,
641
                               modified_user=user if 'user' in locals(
642
                               ) else None,
643
                               context_instance=get_context(request,
644
                                                            extra_context))
645

    
646
    if not request.user.is_authenticated():
647
        path = quote(request.get_full_path())
648
        url = request.build_absolute_uri(reverse('index'))
649
        return HttpResponseRedirect(url + '?next=' + path)
650
    form = EmailChangeForm(request.POST or None)
651
    if request.method == 'POST' and form.is_valid():
652
        try:
653
            ec = form.save(email_template_name, request)
654
        except SendMailError, e:
655
            msg = e
656
            messages.error(request, msg)
657
            transaction.rollback()
658
        except IntegrityError, e:
659
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
660
            messages.error(request, msg)
661
        else:
662
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
663
            messages.success(request, msg)
664
            transaction.commit()
665
    return render_response(form_template_name,
666
                           form=form,
667
                           context_instance=get_context(request,
668
                                                        extra_context))
669

    
670

    
671

    
672
resource_presentation = {
673
       'compute': {
674
            'help_text':'group compute help text',
675
            'is_abbreviation':False,
676
            'report_desc':''
677
        },
678
        'storage': {
679
            'help_text':'group storage help text',
680
            'is_abbreviation':False,
681
            'report_desc':''
682
        },
683
        'pithos+.diskspace': {
684
            'help_text':'resource pithos+.diskspace help text',
685
            'is_abbreviation':False,
686
            'report_desc':'Pithos+ Diskspace',
687
            'placeholder':'eg. 10GB'
688
        },
689
        'cyclades.vm': {
690
            'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
691
            'is_abbreviation':True,
692
            'report_desc':'Virtual Machines',
693
            'placeholder':'eg. 2'
694
        },
695
        'cyclades.disksize': {
696
            'help_text':'resource cyclades.disksize help text',
697
            'is_abbreviation':False,
698
            'report_desc':'Disksize',
699
            'placeholder':'eg. 5GB, 2GB etc'
700
        },
701
        'cyclades.disk': {
702
            'help_text':'resource cyclades.disk help text',
703
            'is_abbreviation':False,
704
            'report_desc':'Disk',
705
            'placeholder':'eg. 5GB, 2GB etc'
706
        },
707
        'cyclades.ram': {
708
            'help_text':'resource cyclades.ram help text',
709
            'is_abbreviation':True,
710
            'report_desc':'RAM',
711
            'placeholder':'eg. 4GB'
712
        },
713
        'cyclades.cpu': {
714
            'help_text':'resource cyclades.cpu help text',
715
            'is_abbreviation':True,
716
            'report_desc':'CPUs',
717
            'placeholder':'eg. 1'
718
        },
719
        'cyclades.network.private': {
720
            'help_text':'resource cyclades.network.private help text',
721
            'is_abbreviation':False,
722
            'report_desc':'Network',
723
            'placeholder':'eg. 1'
724
        }
725
    }
726

    
727
@require_http_methods(["GET", "POST"])
728
@signed_terms_required
729
@login_required
730
def group_add(request, kind_name='default'):
731
    result = callpoint.list_resources()
732
    print '###', result
733
    resource_catalog = {'resources':defaultdict(defaultdict),
734
                        'groups':defaultdict(list)}
735
    if result.is_success:
736
        for r in result.data:
737
            service = r.get('service', '')
738
            name = r.get('name', '')
739
            group = r.get('group', '')
740
            unit = r.get('unit', '')
741
            fullname = '%s%s%s' % (service, RESOURCE_SEPARATOR, name)
742
            resource_catalog['resources'][fullname] = dict(unit=unit)
743
            resource_catalog['groups'][group].append(fullname)
744
        
745
        resource_catalog = dict(resource_catalog)
746
        for k, v in resource_catalog.iteritems():
747
            resource_catalog[k] = dict(v)
748
    else:
749
        messages.error(
750
            request,
751
            'Unable to retrieve system resources: %s' % result.reason
752
    )
753
    
754
    try:
755
        kind = GroupKind.objects.get(name=kind_name)
756
    except:
757
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
758
    
759
    
760

    
761
    post_save_redirect = '/im/group/%(id)s/'
762
    context_processors = None
763
    model, form_class = get_model_and_form_class(
764
        model=None,
765
        form_class=AstakosGroupCreationForm
766
    )
767
    
768
    resources = resource_catalog['resources']
769
    
770
 
771
    if request.method == 'POST':
772
        form = form_class(request.POST, request.FILES)
773
        if form.is_valid():
774
            return render_response(
775
                template='im/astakosgroup_form_summary.html',
776
                context_instance=get_context(request),
777
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
778
                policies = form.policies(),
779
                resource_presentation=resource_presentation,
780
                resource_catalog= resource_catalog,
781
                resources = resources,
782
            
783
            )
784
    else:
785
        now = datetime.now()
786
        data = {
787
            'kind': kind,
788
        }
789
        for group, resources in resource_catalog['groups'].iteritems():
790
            data['is_selected_%s' % group] = False
791
            for resource in resources:
792
                data['%s_uplimit' % resource] = ''
793
        
794
        form = form_class(data)
795

    
796
    # Create the template, context, response
797
    template_name = "%s/%s_form.html" % (
798
        model._meta.app_label,
799
        model._meta.object_name.lower()
800
    )
801
    t = template_loader.get_template(template_name)
802
    c = RequestContext(request, {
803
        'form': form,
804
        'kind': kind,
805
        'resource_catalog':resource_catalog,
806
        'resource_presentation':resource_presentation,
807
    }, context_processors)
808
    return HttpResponse(t.render(c))
809

    
810

    
811
#@require_http_methods(["POST"])
812
@require_http_methods(["GET", "POST"])
813
@signed_terms_required
814
@login_required
815
def group_add_complete(request):
816
    model = AstakosGroup
817
    form = AstakosGroupCreationSummaryForm(request.POST)
818
    if form.is_valid():
819
        d = form.cleaned_data
820
        d['owners'] = [request.user]
821
        result = callpoint.create_groups((d,)).next()
822
        if result.is_success:
823
            new_object = result.data[0]
824
            msg = _(astakos_messages.OBJECT_CREATED) %\
825
                {"verbose_name": model._meta.verbose_name}
826
            messages.success(request, msg, fail_silently=True)
827
            
828
            # send notification
829
            try:
830
                send_group_creation_notification(
831
                    template_name='im/group_creation_notification.txt',
832
                    dictionary={
833
                        'group': new_object,
834
                        'owner': request.user,
835
                        'policies': d.get('policies', [])
836
                    }
837
                )
838
            except SendNotificationError, e:
839
                messages.error(request, e, fail_silently=True)
840
            post_save_redirect = '/im/group/%(id)s/'
841
            return HttpResponseRedirect(post_save_redirect % new_object)
842
        else:
843
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) %\
844
                {"verbose_name": model._meta.verbose_name,
845
                 "reason":result.reason}
846
            messages.error(request, msg, fail_silently=True)
847
    return render_response(
848
        template='im/astakosgroup_form_summary.html',
849
        context_instance=get_context(request),
850
        form=form)
851

    
852

    
853
#@require_http_methods(["GET"])
854
@require_http_methods(["GET", "POST"])
855
@signed_terms_required
856
@login_required
857
def group_list(request):
858
    none = request.user.astakos_groups.none()
859
    sorting = request.GET.get('sorting')
860
    q = AstakosGroup.objects.raw("""
861
        SELECT auth_group.id,
862
        %s AS groupname,
863
        im_groupkind.name AS kindname,
864
        im_astakosgroup.*,
865
        owner.email AS groupowner,
866
        (SELECT COUNT(*) FROM im_membership
867
            WHERE group_id = im_astakosgroup.group_ptr_id
868
            AND date_joined IS NOT NULL) AS approved_members_num,
869
        (SELECT CASE WHEN(
870
                    SELECT date_joined FROM im_membership
871
                    WHERE group_id = im_astakosgroup.group_ptr_id
872
                    AND person_id = %s) IS NULL
873
                    THEN 0 ELSE 1 END) AS membership_status
874
        FROM im_astakosgroup
875
        INNER JOIN im_membership ON (
876
            im_astakosgroup.group_ptr_id = im_membership.group_id)
877
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
878
        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
879
        LEFT JOIN im_astakosuser_owner ON (
880
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
881
        LEFT JOIN auth_user as owner ON (
882
            im_astakosuser_owner.astakosuser_id = owner.id)
883
        WHERE im_membership.person_id = %s
884
        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
885

    
886
            
887
    # Create the template, context, response
888
    template_name = "%s/%s_list.html" % (
889
        q.model._meta.app_label,
890
        q.model._meta.object_name.lower()
891
    )
892
    extra_context = dict(
893
        is_search=False,
894
        q=q,
895
        sorting=request.GET.get('sorting'),
896
        page=request.GET.get('page', 1)
897
    )
898
    return render_response(template_name,
899
                           context_instance=get_context(request, extra_context)
900
    )
901

    
902

    
903
@require_http_methods(["GET", "POST"])
904
@signed_terms_required
905
@login_required
906
def group_detail(request, group_id):
907
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
908
    q = q.extra(select={
909
        'is_member': """SELECT CASE WHEN EXISTS(
910
                            SELECT id FROM im_membership
911
                            WHERE group_id = im_astakosgroup.group_ptr_id
912
                            AND person_id = %s)
913
                        THEN 1 ELSE 0 END""" % request.user.id,
914
        'is_owner': """SELECT CASE WHEN EXISTS(
915
                        SELECT id FROM im_astakosuser_owner
916
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
917
                        AND astakosuser_id = %s)
918
                        THEN 1 ELSE 0 END""" % request.user.id,
919
        'kindname': """SELECT name FROM im_groupkind
920
                       WHERE id = im_astakosgroup.kind_id"""})
921

    
922
    model = q.model
923
    context_processors = None
924
    mimetype = None
925
    try:
926
        obj = q.get()
927
    except AstakosGroup.DoesNotExist:
928
        raise Http404("No %s found matching the query" % (
929
            model._meta.verbose_name))
930

    
931
    update_form = AstakosGroupUpdateForm(instance=obj)
932
    addmembers_form = AddGroupMembersForm()
933
    if request.method == 'POST':
934
        update_data = {}
935
        addmembers_data = {}
936
        for k, v in request.POST.iteritems():
937
            if k in update_form.fields:
938
                update_data[k] = v
939
            if k in addmembers_form.fields:
940
                addmembers_data[k] = v
941
        update_data = update_data or None
942
        addmembers_data = addmembers_data or None
943
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
944
        addmembers_form = AddGroupMembersForm(addmembers_data)
945
        if update_form.is_valid():
946
            update_form.save()
947
        if addmembers_form.is_valid():
948
            try:
949
                map(obj.approve_member, addmembers_form.valid_users)
950
            except AssertionError:
951
                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
952
                messages.error(request, msg)
953
            addmembers_form = AddGroupMembersForm()
954

    
955
    template_name = "%s/%s_detail.html" % (
956
        model._meta.app_label, model._meta.object_name.lower())
957
    t = template_loader.get_template(template_name)
958
    c = RequestContext(request, {
959
        'object': obj,
960
    }, context_processors)
961

    
962
    # validate sorting
963
    sorting = request.GET.get('sorting')
964
    if sorting:
965
        form = MembersSortForm({'sort_by': sorting})
966
        if form.is_valid():
967
            sorting = form.cleaned_data.get('sort_by')
968

    
969
    result = callpoint.list_resources()
970
    resource_catalog = {'resources':defaultdict(defaultdict),
971
                        'groups':defaultdict(list)}
972
    if result.is_success:
973
        for r in result.data:
974
            service = r.get('service', '')
975
            name = r.get('name', '')
976
            group = r.get('group', '')
977
            unit = r.get('unit', '')
978
            fullname = '%s%s%s' % (service, RESOURCE_SEPARATOR, name)
979
            resource_catalog['resources'][fullname] = dict(unit=unit, name=name)
980
            resource_catalog['groups'][group].append(fullname)
981
        
982
        resource_catalog = dict(resource_catalog)
983
        for k, v in resource_catalog.iteritems():
984
            resource_catalog[k] = dict(v)
985
    
986
    print '####', resource_catalog, obj.quota
987
    extra_context = {'update_form': update_form,
988
                     'addmembers_form': addmembers_form,
989
                     'page': request.GET.get('page', 1),
990
                     'sorting': sorting,
991
                     'resource_catalog':resource_catalog,
992
                     'resource_presentation':resource_presentation,}
993
    for key, value in extra_context.items():
994
        if callable(value):
995
            c[key] = value()
996
        else:
997
            c[key] = value
998
    response = HttpResponse(t.render(c), mimetype=mimetype)
999
    populate_xheaders(
1000
        request, response, model, getattr(obj, obj._meta.pk.name))
1001
    return response
1002

    
1003

    
1004
@require_http_methods(["GET", "POST"])
1005
@signed_terms_required
1006
@login_required
1007
def group_search(request, extra_context=None, **kwargs):
1008
    print '###', request
1009
    q = request.GET.get('q')
1010
    sorting = request.GET.get('sorting')
1011
    if request.method == 'GET':
1012
        form = AstakosGroupSearchForm({'q': q} if q else None)
1013
    else:
1014
        form = AstakosGroupSearchForm(get_query(request))
1015
        if form.is_valid():
1016
            q = form.cleaned_data['q'].strip()
1017
    if q:
1018
        queryset = AstakosGroup.objects.select_related()
1019
        queryset = queryset.filter(name__contains=q)
1020
        queryset = queryset.filter(approval_date__isnull=False)
1021
        queryset = queryset.extra(select={
1022
                                  'groupname': DB_REPLACE_GROUP_SCHEME,
1023
                                  'kindname': "im_groupkind.name",
1024
                                  'approved_members_num': """
1025
                    SELECT COUNT(*) FROM im_membership
1026
                    WHERE group_id = im_astakosgroup.group_ptr_id
1027
                    AND date_joined IS NOT NULL""",
1028
                                  'membership_approval_date': """
1029
                    SELECT date_joined FROM im_membership
1030
                    WHERE group_id = im_astakosgroup.group_ptr_id
1031
                    AND person_id = %s""" % request.user.id,
1032
                                  'is_member': """
1033
                    SELECT CASE WHEN EXISTS(
1034
                    SELECT date_joined FROM im_membership
1035
                    WHERE group_id = im_astakosgroup.group_ptr_id
1036
                    AND person_id = %s)
1037
                    THEN 1 ELSE 0 END""" % request.user.id,
1038
                                  'is_owner': """
1039
                    SELECT CASE WHEN EXISTS(
1040
                    SELECT id FROM im_astakosuser_owner
1041
                    WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1042
                    AND astakosuser_id = %s)
1043
                    THEN 1 ELSE 0 END""" % request.user.id,
1044
                    'is_owner': """SELECT CASE WHEN EXISTS(
1045
                        SELECT id FROM im_astakosuser_owner
1046
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1047
                        AND astakosuser_id = %s)
1048
                        THEN 1 ELSE 0 END""" % request.user.id, 
1049
                    })
1050
        if sorting:
1051
            # TODO check sorting value
1052
            queryset = queryset.order_by(sorting)
1053
    else:
1054
        queryset = AstakosGroup.objects.none()
1055
    return object_list(
1056
        request,
1057
        queryset,
1058
        paginate_by=PAGINATE_BY,
1059
        page=request.GET.get('page') or 1,
1060
        template_name='im/astakosgroup_list.html',
1061
        extra_context=dict(form=form,
1062
                           is_search=True,
1063
                           q=q,
1064
                           sorting=sorting))
1065

    
1066

    
1067
@require_http_methods(["GET", "POST"])
1068
@signed_terms_required
1069
@login_required
1070
def group_all(request, extra_context=None, **kwargs):
1071
    q = AstakosGroup.objects.select_related()
1072
    q = q.filter(approval_date__isnull=False)
1073
    q = q.extra(select={
1074
                'groupname': DB_REPLACE_GROUP_SCHEME,
1075
                'kindname': "im_groupkind.name",
1076
                'approved_members_num': """
1077
                    SELECT COUNT(*) FROM im_membership
1078
                    WHERE group_id = im_astakosgroup.group_ptr_id
1079
                    AND date_joined IS NOT NULL""",
1080
                'membership_approval_date': """
1081
                    SELECT date_joined FROM im_membership
1082
                    WHERE group_id = im_astakosgroup.group_ptr_id
1083
                    AND person_id = %s""" % request.user.id,
1084
                'is_member': """
1085
                    SELECT CASE WHEN EXISTS(
1086
                    SELECT date_joined FROM im_membership
1087
                    WHERE group_id = im_astakosgroup.group_ptr_id
1088
                    AND person_id = %s)
1089
                    THEN 1 ELSE 0 END""" % request.user.id,
1090
                 'is_owner': """SELECT CASE WHEN EXISTS(
1091
                        SELECT id FROM im_astakosuser_owner
1092
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1093
                        AND astakosuser_id = %s)
1094
                        THEN 1 ELSE 0 END""" % request.user.id,   })
1095
    sorting = request.GET.get('sorting')
1096
    if sorting:
1097
        # TODO check sorting value
1098
        q = q.order_by(sorting)
1099
    return object_list(
1100
        request,
1101
        q,
1102
        paginate_by=PAGINATE_BY,
1103
        page=request.GET.get('page') or 1,
1104
        template_name='im/astakosgroup_list.html',
1105
        extra_context=dict(form=AstakosGroupSearchForm(),
1106
                           is_search=True,
1107
                           sorting=sorting))
1108

    
1109

    
1110
#@require_http_methods(["POST"])
1111
@require_http_methods(["POST", "GET"])
1112
@signed_terms_required
1113
@login_required
1114
def group_join(request, group_id):
1115
    m = Membership(group_id=group_id,
1116
                   person=request.user,
1117
                   date_requested=datetime.now())
1118
    try:
1119
        m.save()
1120
        post_save_redirect = reverse(
1121
            'group_detail',
1122
            kwargs=dict(group_id=group_id))
1123
        return HttpResponseRedirect(post_save_redirect)
1124
    except IntegrityError, e:
1125
        logger.exception(e)
1126
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1127
        messages.error(request, msg)
1128
        return group_search(request)
1129

    
1130

    
1131
@require_http_methods(["POST"])
1132
@signed_terms_required
1133
@login_required
1134
def group_leave(request, group_id):
1135
    try:
1136
        m = Membership.objects.select_related().get(
1137
            group__id=group_id,
1138
            person=request.user)
1139
    except Membership.DoesNotExist:
1140
        return HttpResponseBadRequest(_(astakos_messages.NOT_A_MEMBER))
1141
    if request.user in m.group.owner.all():
1142
        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1143
    return delete_object(
1144
        request,
1145
        model=Membership,
1146
        object_id=m.id,
1147
        template_name='im/astakosgroup_list.html',
1148
        post_delete_redirect=reverse(
1149
            'group_detail',
1150
            kwargs=dict(group_id=group_id)))
1151

    
1152

    
1153
def handle_membership(func):
1154
    @wraps(func)
1155
    def wrapper(request, group_id, user_id):
1156
        try:
1157
            m = Membership.objects.select_related().get(
1158
                group__id=group_id,
1159
                person__id=user_id)
1160
        except Membership.DoesNotExist:
1161
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1162
        else:
1163
            if request.user not in m.group.owner.all():
1164
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1165
            func(request, m)
1166
            return group_detail(request, group_id)
1167
    return wrapper
1168

    
1169

    
1170
#@require_http_methods(["POST"])
1171
@require_http_methods(["POST", "GET"])
1172
@signed_terms_required
1173
@login_required
1174
@handle_membership
1175
def approve_member(request, membership):
1176
    try:
1177
        membership.approve()
1178
        realname = membership.person.realname
1179
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1180
        messages.success(request, msg)
1181
    except AssertionError:
1182
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1183
        messages.error(request, msg)
1184
    except BaseException, e:
1185
        logger.exception(e)
1186
        realname = membership.person.realname
1187
        msg = _(astakos_messages.GENERIC_ERROR)
1188
        messages.error(request, msg)
1189

    
1190

    
1191
@signed_terms_required
1192
@login_required
1193
@handle_membership
1194
def disapprove_member(request, membership):
1195
    try:
1196
        membership.disapprove()
1197
        realname = membership.person.realname
1198
        msg = MEMBER_REMOVED % realname
1199
        messages.success(request, msg)
1200
    except BaseException, e:
1201
        logger.exception(e)
1202
        msg = _(astakos_messages.GENERIC_ERROR)
1203
        messages.error(request, msg)
1204

    
1205

    
1206
#@require_http_methods(["GET"])
1207
@require_http_methods(["POST", "GET"])
1208
@signed_terms_required
1209
@login_required
1210
def resource_list(request):
1211
#     if request.method == 'POST':
1212
#         form = PickResourceForm(request.POST)
1213
#         if form.is_valid():
1214
#             r = form.cleaned_data.get('resource')
1215
#             if r:
1216
#                 groups = request.user.membership_set.only('group').filter(
1217
#                     date_joined__isnull=False)
1218
#                 groups = [g.group_id for g in groups]
1219
#                 q = AstakosGroupQuota.objects.select_related().filter(
1220
#                     resource=r, group__in=groups)
1221
#     else:
1222
#         form = PickResourceForm()
1223
#         q = AstakosGroupQuota.objects.none()
1224
#
1225
#     return object_list(request, q,
1226
#                        template_name='im/astakosuserquota_list.html',
1227
#                        extra_context={'form': form, 'data':data})
1228

    
1229
    def with_class(entry):
1230
        entry['load_class'] = 'red'
1231
        max_value = float(entry['maxValue'])
1232
        curr_value = float(entry['currValue'])
1233
        if max_value > 0 :
1234
           entry['ratio'] = (curr_value / max_value) * 100
1235
        else: 
1236
           entry['ratio'] = 0 
1237
        if entry['ratio'] < 66:
1238
            entry['load_class'] = 'yellow'
1239
        if entry['ratio'] < 33:
1240
            entry['load_class'] = 'green'
1241
        return entry
1242

    
1243
    def pluralize(entry):
1244
        entry['plural'] = engine.plural(entry.get('name'))
1245
        return entry
1246

    
1247
    result = callpoint.get_user_status(request.user.id)
1248
    if result.is_success:
1249
        backenddata = map(with_class, result.data)
1250
        data = map(pluralize, result.data)
1251
    else:
1252
        data = None
1253
        messages.error(request, result.reason)
1254
    return render_response('im/resource_list.html',
1255
                           data=data,
1256
                           resource_presentation=resource_presentation,
1257
                           context_instance=get_context(request))
1258

    
1259

    
1260
def group_create_list(request):
1261
    form = PickResourceForm()
1262
    return render_response(
1263
        template='im/astakosgroup_create_list.html',
1264
        context_instance=get_context(request),)
1265

    
1266

    
1267
#@require_http_methods(["GET"])
1268
@require_http_methods(["POST", "GET"])
1269
@signed_terms_required
1270
@login_required
1271
def billing(request):
1272

    
1273
    today = datetime.today()
1274
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1275
    data['resources'] = map(with_class, data['resources'])
1276
    start = request.POST.get('datefrom', None)
1277
    if start:
1278
        today = datetime.fromtimestamp(int(start))
1279
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1280

    
1281
    start = datetime(today.year, today.month, 1).strftime("%s")
1282
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1283
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1284
                                    int(start) * 1000,
1285
                                    int(end) * 1000))
1286
    data = {}
1287

    
1288
    try:
1289
        status, data = r.result
1290
        data = _clear_billing_data(data)
1291
        if status != 200:
1292
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1293
    except:
1294
        messages.error(request, r.result)
1295

    
1296
    print type(start)
1297

    
1298
    return render_response(
1299
        template='im/billing.html',
1300
        context_instance=get_context(request),
1301
        data=data,
1302
        zerodate=datetime(month=1, year=1970, day=1),
1303
        today=today,
1304
        start=int(start),
1305
        month_last_day=month_last_day)
1306

    
1307

    
1308
def _clear_billing_data(data):
1309

    
1310
    # remove addcredits entries
1311
    def isnotcredit(e):
1312
        return e['serviceName'] != "addcredits"
1313

    
1314
    # separate services
1315
    def servicefilter(service_name):
1316
        service = service_name
1317

    
1318
        def fltr(e):
1319
            return e['serviceName'] == service
1320
        return fltr
1321

    
1322
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1323
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1324
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1325
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1326

    
1327
    return data
1328
     
1329
     
1330
#@require_http_methods(["GET"])
1331
@require_http_methods(["POST", "GET"])
1332
@signed_terms_required
1333
@login_required
1334
def timeline(request):
1335
#    data = {'entity':request.user.email}
1336
    timeline_body = ()
1337
    timeline_header = ()
1338
#    form = TimelineForm(data)
1339
    form = TimelineForm()
1340
    if request.method == 'POST':
1341
        data = request.POST
1342
        form = TimelineForm(data)
1343
        if form.is_valid():
1344
            data = form.cleaned_data
1345
            timeline_header = ('entity', 'resource',
1346
                               'event name', 'event date',
1347
                               'incremental cost', 'total cost')
1348
            timeline_body = timeline_charge(
1349
                data['entity'], data['resource'],
1350
                data['start_date'], data['end_date'],
1351
                data['details'], data['operation'])
1352

    
1353
    return render_response(template='im/timeline.html',
1354
                           context_instance=get_context(request),
1355
                           form=form,
1356
                           timeline_header=timeline_header,
1357
                           timeline_body=timeline_body)
1358
    return data