Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (49.8 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.quotaholder 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
    resource_catalog = {'resources':defaultdict(defaultdict),
733
                        'groups':defaultdict(list)}
734
    if result.is_success:
735
        for r in result.data:
736
            service = r.get('service', '')
737
            name = r.get('name', '')
738
            group = r.get('group', '')
739
            unit = r.get('unit', '')
740
            fullname = '%s%s%s' % (service, RESOURCE_SEPARATOR, name)
741
            resource_catalog['resources'][fullname] = dict(unit=unit)
742
            resource_catalog['groups'][group].append(fullname)
743
        
744
        resource_catalog = dict(resource_catalog)
745
        for k, v in resource_catalog.iteritems():
746
            resource_catalog[k] = dict(v)
747
    else:
748
        messages.error(
749
            request,
750
            'Unable to retrieve system resources: %s' % result.reason
751
    )
752
    
753
    try:
754
        kind = GroupKind.objects.get(name=kind_name)
755
    except:
756
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
757
    
758
    
759

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

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

    
804

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

    
846

    
847
#@require_http_methods(["GET"])
848
@require_http_methods(["GET", "POST"])
849
@signed_terms_required
850
@login_required
851
def group_list(request):
852
    none = request.user.astakos_groups.none()
853
    q = AstakosGroup.objects.raw("""
854
        SELECT auth_group.id,
855
        %s AS groupname,
856
        im_groupkind.name AS kindname,
857
        im_astakosgroup.*,
858
        owner.email AS groupowner,
859
        (SELECT COUNT(*) FROM im_membership
860
            WHERE group_id = im_astakosgroup.group_ptr_id
861
            AND date_joined IS NOT NULL) AS approved_members_num,
862
        (SELECT CASE WHEN(
863
                    SELECT date_joined FROM im_membership
864
                    WHERE group_id = im_astakosgroup.group_ptr_id
865
                    AND person_id = %s) IS NULL
866
                    THEN 0 ELSE 1 END) AS membership_status
867
        FROM im_astakosgroup
868
        INNER JOIN im_membership ON (
869
            im_astakosgroup.group_ptr_id = im_membership.group_id)
870
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
871
        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
872
        LEFT JOIN im_astakosuser_owner ON (
873
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
874
        LEFT JOIN auth_user as owner ON (
875
            im_astakosuser_owner.astakosuser_id = owner.id)
876
        WHERE im_membership.person_id = %s
877
        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
878
    
879
    # Create the template, context, response
880
    template_name = "%s/%s_list.html" % (
881
        q.model._meta.app_label,
882
        q.model._meta.object_name.lower()
883
    )
884
    extra_context = dict(
885
        is_search=False,
886
        q=q,
887
        sorting=request.GET.get('sorting'),
888
        page=request.GET.get('page', 1)
889
    )
890
    return render_response(template_name,
891
                           context_instance=get_context(request, extra_context)
892
    )
893

    
894

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

    
914
    model = q.model
915
    context_processors = None
916
    mimetype = None
917
    try:
918
        obj = q.get()
919
    except AstakosGroup.DoesNotExist:
920
        raise Http404("No %s found matching the query" % (
921
            model._meta.verbose_name))
922

    
923
    update_form = AstakosGroupUpdateForm(instance=obj)
924
    addmembers_form = AddGroupMembersForm()
925
    if request.method == 'POST':
926
        update_data = {}
927
        addmembers_data = {}
928
        for k, v in request.POST.iteritems():
929
            if k in update_form.fields:
930
                update_data[k] = v
931
            if k in addmembers_form.fields:
932
                addmembers_data[k] = v
933
        update_data = update_data or None
934
        addmembers_data = addmembers_data or None
935
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
936
        addmembers_form = AddGroupMembersForm(addmembers_data)
937
        if update_form.is_valid():
938
            update_form.save()
939
        if addmembers_form.is_valid():
940
            try:
941
                map(obj.approve_member, addmembers_form.valid_users)
942
            except AssertionError:
943
                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
944
                messages.error(request, msg)
945
            addmembers_form = AddGroupMembersForm()
946

    
947
    template_name = "%s/%s_detail.html" % (
948
        model._meta.app_label, model._meta.object_name.lower())
949
    t = template_loader.get_template(template_name)
950
    c = RequestContext(request, {
951
        'object': obj,
952
    }, context_processors)
953

    
954
    # validate sorting
955
    sorting = request.GET.get('sorting')
956
    if sorting:
957
        form = MembersSortForm({'sort_by': sorting})
958
        if form.is_valid():
959
            sorting = form.cleaned_data.get('sort_by')
960

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

    
995

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

    
1058

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

    
1101

    
1102
#@require_http_methods(["POST"])
1103
@require_http_methods(["POST", "GET"])
1104
@signed_terms_required
1105
@login_required
1106
def group_join(request, group_id):
1107
    m = Membership(group_id=group_id,
1108
                   person=request.user,
1109
                   date_requested=datetime.now())
1110
    try:
1111
        m.save()
1112
        post_save_redirect = reverse(
1113
            'group_detail',
1114
            kwargs=dict(group_id=group_id))
1115
        return HttpResponseRedirect(post_save_redirect)
1116
    except IntegrityError, e:
1117
        logger.exception(e)
1118
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1119
        messages.error(request, msg)
1120
        return group_search(request)
1121

    
1122

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

    
1144

    
1145
def handle_membership(func):
1146
    @wraps(func)
1147
    def wrapper(request, group_id, user_id):
1148
        try:
1149
            m = Membership.objects.select_related().get(
1150
                group__id=group_id,
1151
                person__id=user_id)
1152
        except Membership.DoesNotExist:
1153
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1154
        else:
1155
            if request.user not in m.group.owner.all():
1156
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1157
            func(request, m)
1158
            return group_detail(request, group_id)
1159
    return wrapper
1160

    
1161

    
1162
#@require_http_methods(["POST"])
1163
@require_http_methods(["POST", "GET"])
1164
@signed_terms_required
1165
@login_required
1166
@handle_membership
1167
def approve_member(request, membership):
1168
    try:
1169
        membership.approve()
1170
        realname = membership.person.realname
1171
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1172
        messages.success(request, msg)
1173
    except AssertionError:
1174
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1175
        messages.error(request, msg)
1176
    except BaseException, e:
1177
        logger.exception(e)
1178
        realname = membership.person.realname
1179
        msg = _(astakos_messages.GENERIC_ERROR)
1180
        messages.error(request, msg)
1181

    
1182

    
1183
@signed_terms_required
1184
@login_required
1185
@handle_membership
1186
def disapprove_member(request, membership):
1187
    try:
1188
        membership.disapprove()
1189
        realname = membership.person.realname
1190
        msg = MEMBER_REMOVED % realname
1191
        messages.success(request, msg)
1192
    except BaseException, e:
1193
        logger.exception(e)
1194
        msg = _(astakos_messages.GENERIC_ERROR)
1195
        messages.error(request, msg)
1196

    
1197

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

    
1221
    def with_class(entry):
1222
        entry['load_class'] = 'red'
1223
        max_value = float(entry['maxValue'])
1224
        curr_value = float(entry['currValue'])
1225
        if max_value > 0 :
1226
           entry['ratio'] = (curr_value / max_value) * 100
1227
        else: 
1228
           entry['ratio'] = 0 
1229
        if entry['ratio'] < 66:
1230
            entry['load_class'] = 'yellow'
1231
        if entry['ratio'] < 33:
1232
            entry['load_class'] = 'green'
1233
        return entry
1234

    
1235
    def pluralize(entry):
1236
        entry['plural'] = engine.plural(entry.get('name'))
1237
        return entry
1238

    
1239
    result = callpoint.get_user_status(request.user.id)
1240
    if result.is_success:
1241
        backenddata = map(with_class, result.data)
1242
        data = map(pluralize, result.data)
1243
    else:
1244
        data = None
1245
        messages.error(request, result.reason)
1246
    return render_response('im/resource_list.html',
1247
                           data=data,
1248
                           resource_presentation=resource_presentation,
1249
                           context_instance=get_context(request))
1250

    
1251

    
1252
def group_create_list(request):
1253
    form = PickResourceForm()
1254
    return render_response(
1255
        template='im/astakosgroup_create_list.html',
1256
        context_instance=get_context(request),)
1257

    
1258

    
1259
#@require_http_methods(["GET"])
1260
@require_http_methods(["POST", "GET"])
1261
@signed_terms_required
1262
@login_required
1263
def billing(request):
1264

    
1265
    today = datetime.today()
1266
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1267
    data['resources'] = map(with_class, data['resources'])
1268
    start = request.POST.get('datefrom', None)
1269
    if start:
1270
        today = datetime.fromtimestamp(int(start))
1271
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1272

    
1273
    start = datetime(today.year, today.month, 1).strftime("%s")
1274
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1275
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1276
                                    int(start) * 1000,
1277
                                    int(end) * 1000))
1278
    data = {}
1279

    
1280
    try:
1281
        status, data = r.result
1282
        data = _clear_billing_data(data)
1283
        if status != 200:
1284
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1285
    except:
1286
        messages.error(request, r.result)
1287

    
1288
    print type(start)
1289

    
1290
    return render_response(
1291
        template='im/billing.html',
1292
        context_instance=get_context(request),
1293
        data=data,
1294
        zerodate=datetime(month=1, year=1970, day=1),
1295
        today=today,
1296
        start=int(start),
1297
        month_last_day=month_last_day)
1298

    
1299

    
1300
def _clear_billing_data(data):
1301

    
1302
    # remove addcredits entries
1303
    def isnotcredit(e):
1304
        return e['serviceName'] != "addcredits"
1305

    
1306
    # separate services
1307
    def servicefilter(service_name):
1308
        service = service_name
1309

    
1310
        def fltr(e):
1311
            return e['serviceName'] == service
1312
        return fltr
1313

    
1314
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1315
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1316
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1317
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1318

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

    
1345
    return render_response(template='im/timeline.html',
1346
                           context_instance=get_context(request),
1347
                           form=form,
1348
                           timeline_header=timeline_header,
1349
                           timeline_body=timeline_body)
1350
    return data