Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 5ebebb20

History | View | Annotate | Download (49.4 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
43

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

    
61
from django.template.loader import render_to_string
62
from django.views.decorators.http import require_http_methods
63
from astakos.im.activation_backends import get_backend, SimpleBackend
64

    
65
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
66
                               EmailChange, GroupKind, Membership,
67
                               RESOURCE_SEPARATOR)
68
from astakos.im.util import get_context, prepare_response, get_query, restrict_next
69
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
70
                              FeedbackForm, SignApprovalTermsForm,
71
                              EmailChangeForm,
72
                              AstakosGroupCreationForm, AstakosGroupSearchForm,
73
                              AstakosGroupUpdateForm, AddGroupMembersForm,
74
                              MembersSortForm,
75
                              TimelineForm, PickResourceForm,
76
                              AstakosGroupCreationSummaryForm)
77
from astakos.im.functions import (send_feedback, SendMailError,
78
                                  logout as auth_logout,
79
                                  activate as activate_func,
80
                                  send_activation as send_activation_func,
81
                                  send_group_creation_notification,
82
                                  SendNotificationError)
83
from astakos.im.endpoints.qh import timeline_charge
84
from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
85
                                 LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
86
from astakos.im.tasks import request_billing
87
from astakos.im.api.callpoint import AstakosCallpoint
88

    
89
import astakos.im.messages as astakos_messages
90

    
91
logger = logging.getLogger(__name__)
92

    
93
DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
94
                                     'https://', '')"""
95

    
96
callpoint = AstakosCallpoint()
97

    
98
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
99
    """
100
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
101
    keyword argument and returns an ``django.http.HttpResponse`` with the
102
    specified ``status``.
103
    """
104
    if tab is None:
105
        tab = template.partition('_')[0].partition('.html')[0]
106
    kwargs.setdefault('tab', tab)
107
    html = template_loader.render_to_string(
108
        template, kwargs, context_instance=context_instance)
109
    response = HttpResponse(html, status=status)
110
    return response
111

    
112

    
113
def requires_anonymous(func):
114
    """
115
    Decorator checkes whether the request.user is not Anonymous and in that case
116
    redirects to `logout`.
117
    """
118
    @wraps(func)
119
    def wrapper(request, *args):
120
        if not request.user.is_anonymous():
121
            next = urlencode({'next': request.build_absolute_uri()})
122
            logout_uri = reverse(logout) + '?' + next
123
            return HttpResponseRedirect(logout_uri)
124
        return func(request, *args)
125
    return wrapper
126

    
127

    
128
def signed_terms_required(func):
129
    """
130
    Decorator checkes whether the request.user is Anonymous and in that case
131
    redirects to `logout`.
132
    """
133
    @wraps(func)
134
    def wrapper(request, *args, **kwargs):
135
        if request.user.is_authenticated() and not request.user.signed_terms:
136
            params = urlencode({'next': request.build_absolute_uri(),
137
                                'show_form': ''})
138
            terms_uri = reverse('latest_terms') + '?' + params
139
            return HttpResponseRedirect(terms_uri)
140
        return func(request, *args, **kwargs)
141
    return wrapper
142

    
143

    
144
@require_http_methods(["GET", "POST"])
145
@signed_terms_required
146
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
147
    """
148
    If there is logged on user renders the profile page otherwise renders login page.
149

150
    **Arguments**
151

152
    ``login_template_name``
153
        A custom login template to use. This is optional; if not specified,
154
        this will default to ``im/login.html``.
155

156
    ``profile_template_name``
157
        A custom profile template to use. This is optional; if not specified,
158
        this will default to ``im/profile.html``.
159

160
    ``extra_context``
161
        An dictionary of variables to add to the template context.
162

163
    **Template:**
164

165
    im/profile.html or im/login.html or ``template_name`` keyword argument.
166

167
    """
168
    extra_context = extra_context or {}
169
    template_name = login_template_name
170
    if request.user.is_authenticated():
171
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
172
    
173
    return render_response(
174
        template_name,
175
        login_form = LoginForm(request=request),
176
        context_instance = get_context(request, extra_context)
177
    )
178

    
179

    
180
@require_http_methods(["GET", "POST"])
181
@login_required
182
@signed_terms_required
183
@transaction.commit_manually
184
def invite(request, template_name='im/invitations.html', extra_context=None):
185
    """
186
    Allows a user to invite somebody else.
187

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

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

195
    If the user isn't logged in, redirects to settings.LOGIN_URL.
196

197
    **Arguments**
198

199
    ``template_name``
200
        A custom template to use. This is optional; if not specified,
201
        this will default to ``im/invitations.html``.
202

203
    ``extra_context``
204
        An dictionary of variables to add to the template context.
205

206
    **Template:**
207

208
    im/invitations.html or ``template_name`` keyword argument.
209

210
    **Settings:**
211

212
    The view expectes the following settings are defined:
213

214
    * LOGIN_URL: login uri
215
    """
216
    extra_context = extra_context or {}
217
    status = None
218
    message = None
219
    form = InvitationForm()
220

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

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

    
258

    
259
@require_http_methods(["GET", "POST"])
260
@login_required
261
@signed_terms_required
262
def edit_profile(request, template_name='im/profile.html', extra_context=None):
263
    """
264
    Allows a user to edit his/her profile.
265

266
    In case of GET request renders a form for displaying the user information.
267
    In case of POST updates the user informantion and redirects to ``next``
268
    url parameter if exists.
269

270
    If the user isn't logged in, redirects to settings.LOGIN_URL.
271

272
    **Arguments**
273

274
    ``template_name``
275
        A custom template to use. This is optional; if not specified,
276
        this will default to ``im/profile.html``.
277

278
    ``extra_context``
279
        An dictionary of variables to add to the template context.
280

281
    **Template:**
282

283
    im/profile.html or ``template_name`` keyword argument.
284

285
    **Settings:**
286

287
    The view expectes the following settings are defined:
288

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

    
330

    
331
@transaction.commit_manually
332
@require_http_methods(["GET", "POST"])
333
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
334
    """
335
    Allows a user to create a local account.
336

337
    In case of GET request renders a form for entering the user information.
338
    In case of POST handles the signup.
339

340
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
341
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
342
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
343
    (see activation_backends);
344

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

348
    On unsuccessful creation, renders ``template_name`` with an error message.
349

350
    **Arguments**
351

352
    ``template_name``
353
        A custom template to render. This is optional;
354
        if not specified, this will default to ``im/signup.html``.
355

356
    ``on_success``
357
        A custom template to render in case of success. This is optional;
358
        if not specified, this will default to ``im/signup_complete.html``.
359

360
    ``extra_context``
361
        An dictionary of variables to add to the template context.
362

363
    **Template:**
364

365
    im/signup.html or ``template_name`` keyword argument.
366
    im/signup_complete.html or ``on_success`` keyword argument.
367
    """
368
    extra_context = extra_context or {}
369
    if request.user.is_authenticated():
370
        return HttpResponseRedirect(reverse('edit_profile'))
371

    
372
    provider = get_query(request).get('provider', 'local')
373
    id = get_query(request).get('id')
374
    try:
375
        instance = AstakosUser.objects.get(id=id) if id else None
376
    except AstakosUser.DoesNotExist:
377
        instance = None
378

    
379
    try:
380
        if not backend:
381
            backend = get_backend(request)
382
        form = backend.get_signup_form(provider, instance)
383
    except Exception, e:
384
        form = SimpleBackend(request).get_signup_form(provider)
385
        messages.error(request, e)
386
    if request.method == 'POST':
387
        if form.is_valid():
388
            user = form.save(commit=False)
389
            try:
390
                result = backend.handle_activation(user)
391
                status = messages.SUCCESS
392
                message = result.message
393
                user.save()
394
                if 'additional_email' in form.cleaned_data:
395
                    additional_email = form.cleaned_data['additional_email']
396
                    if additional_email != user.email:
397
                        user.additionalmail_set.create(email=additional_email)
398
                        msg = 'Additional email: %s saved for user %s.' % (
399
                            additional_email,
400
                            user.email
401
                        )
402
                        logger._log(LOGGING_LEVEL, msg, [])
403
                if user and user.is_active:
404
                    next = request.POST.get('next', '')
405
                    response = prepare_response(request, user, next=next)
406
                    transaction.commit()
407
                    return response
408
                messages.add_message(request, status, message)
409
                return render_response(
410
                    on_success,
411
                    context_instance=get_context(
412
                        request,
413
                        extra_context
414
                    )
415
                )
416
            except SendMailError, e:
417
                logger.exception(e)
418
                status = messages.ERROR
419
                message = e.message
420
                messages.error(request, message)
421
                transaction.rollback()
422
            except BaseException, e:
423
                logger.exception(e)
424
                message = _(astakos_messages.GENERIC_ERROR)
425
                messages.error(request, message)
426
                logger.exception(e)
427
                transaction.rollback()
428
    return render_response(template_name,
429
                           signup_form=form,
430
                           provider=provider,
431
                           context_instance=get_context(request, extra_context))
432

    
433

    
434
@require_http_methods(["GET", "POST"])
435
@login_required
436
@signed_terms_required
437
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
438
    """
439
    Allows a user to send feedback.
440

441
    In case of GET request renders a form for providing the feedback information.
442
    In case of POST sends an email to support team.
443

444
    If the user isn't logged in, redirects to settings.LOGIN_URL.
445

446
    **Arguments**
447

448
    ``template_name``
449
        A custom template to use. This is optional; if not specified,
450
        this will default to ``im/feedback.html``.
451

452
    ``extra_context``
453
        An dictionary of variables to add to the template context.
454

455
    **Template:**
456

457
    im/signup.html or ``template_name`` keyword argument.
458

459
    **Settings:**
460

461
    * LOGIN_URL: login uri
462
    """
463
    extra_context = extra_context or {}
464
    if request.method == 'GET':
465
        form = FeedbackForm()
466
    if request.method == 'POST':
467
        if not request.user:
468
            return HttpResponse('Unauthorized', status=401)
469

    
470
        form = FeedbackForm(request.POST)
471
        if form.is_valid():
472
            msg = form.cleaned_data['feedback_msg']
473
            data = form.cleaned_data['feedback_data']
474
            try:
475
                send_feedback(msg, data, request.user, email_template_name)
476
            except SendMailError, e:
477
                messages.error(request, message)
478
            else:
479
                message = _(astakos_messages.FEEDBACK_SENT)
480
                messages.success(request, message)
481
    return render_response(template_name,
482
                           feedback_form=form,
483
                           context_instance=get_context(request, extra_context))
484

    
485

    
486
@require_http_methods(["GET"])
487
@signed_terms_required
488
def logout(request, template='registration/logged_out.html', extra_context=None):
489
    """
490
    Wraps `django.contrib.auth.logout`.
491
    """
492
    extra_context = extra_context or {}
493
    response = HttpResponse()
494
    if request.user.is_authenticated():
495
        email = request.user.email
496
        auth_logout(request)
497
    next = restrict_next(
498
        request.GET.get('next'),
499
        domain=COOKIE_DOMAIN
500
    )
501
    if next:
502
        response['Location'] = next
503
        response.status_code = 302
504
    elif LOGOUT_NEXT:
505
        response['Location'] = LOGOUT_NEXT
506
        response.status_code = 301
507
    else:
508
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
509
        context = get_context(request, extra_context)
510
        response.write(render_to_string(template, context_instance=context))
511
    return response
512

    
513

    
514
@require_http_methods(["GET", "POST"])
515
@transaction.commit_manually
516
def activate(request, greeting_email_template_name='im/welcome_email.txt',
517
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
518
    """
519
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
520
    and renews the user token.
521

522
    The view uses commit_manually decorator in order to ensure the user state will be updated
523
    only if the email will be send successfully.
524
    """
525
    token = request.GET.get('auth')
526
    next = request.GET.get('next')
527
    try:
528
        user = AstakosUser.objects.get(auth_token=token)
529
    except AstakosUser.DoesNotExist:
530
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
531

    
532
    if user.is_active:
533
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
534
        messages.error(request, message)
535
        return index(request)
536

    
537
    try:
538
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
539
        response = prepare_response(request, user, next, renew=True)
540
        transaction.commit()
541
        return response
542
    except SendMailError, e:
543
        message = e.message
544
        messages.add_message(request, messages.ERROR, message)
545
        transaction.rollback()
546
        return index(request)
547
    except BaseException, e:
548
        status = messages.ERROR
549
        message = _(astakos_messages.GENERIC_ERROR)
550
        messages.add_message(request, messages.ERROR, message)
551
        logger.exception(e)
552
        transaction.rollback()
553
        return index(request)
554

    
555

    
556
@require_http_methods(["GET", "POST"])
557
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
558
    extra_context = extra_context or {}
559
    term = None
560
    terms = None
561
    if not term_id:
562
        try:
563
            term = ApprovalTerms.objects.order_by('-id')[0]
564
        except IndexError:
565
            pass
566
    else:
567
        try:
568
            term = ApprovalTerms.objects.get(id=term_id)
569
        except ApprovalTerms.DoesNotExist, e:
570
            pass
571

    
572
    if not term:
573
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
574
        return HttpResponseRedirect(reverse('index'))
575
    f = open(term.location, 'r')
576
    terms = f.read()
577

    
578
    if request.method == 'POST':
579
        next = restrict_next(
580
            request.POST.get('next'),
581
            domain=COOKIE_DOMAIN
582
        )
583
        if not next:
584
            next = reverse('index')
585
        form = SignApprovalTermsForm(request.POST, instance=request.user)
586
        if not form.is_valid():
587
            return render_response(template_name,
588
                                   terms=terms,
589
                                   approval_terms_form=form,
590
                                   context_instance=get_context(request, extra_context))
591
        user = form.save()
592
        return HttpResponseRedirect(next)
593
    else:
594
        form = None
595
        if request.user.is_authenticated() and not request.user.signed_terms:
596
            form = SignApprovalTermsForm(instance=request.user)
597
        return render_response(template_name,
598
                               terms=terms,
599
                               approval_terms_form=form,
600
                               context_instance=get_context(request, extra_context))
601

    
602

    
603
@require_http_methods(["GET", "POST"])
604
@login_required
605
@signed_terms_required
606
@transaction.commit_manually
607
def change_email(request, activation_key=None,
608
                 email_template_name='registration/email_change_email.txt',
609
                 form_template_name='registration/email_change_form.html',
610
                 confirm_template_name='registration/email_change_done.html',
611
                 extra_context=None):
612
    extra_context = extra_context or {}
613
    if activation_key:
614
        try:
615
            user = EmailChange.objects.change_email(activation_key)
616
            if request.user.is_authenticated() and request.user == user:
617
                msg = _(astakos_messages.EMAIL_CHANGED)
618
                messages.success(request, msg)
619
                auth_logout(request)
620
                response = prepare_response(request, user)
621
                transaction.commit()
622
                return response
623
        except ValueError, e:
624
            messages.error(request, e)
625
        return render_response(confirm_template_name,
626
                               modified_user=user if 'user' in locals(
627
                               ) else None,
628
                               context_instance=get_context(request,
629
                                                            extra_context))
630

    
631
    if not request.user.is_authenticated():
632
        path = quote(request.get_full_path())
633
        url = request.build_absolute_uri(reverse('index'))
634
        return HttpResponseRedirect(url + '?next=' + path)
635
    form = EmailChangeForm(request.POST or None)
636
    if request.method == 'POST' and form.is_valid():
637
        try:
638
            ec = form.save(email_template_name, request)
639
        except SendMailError, e:
640
            msg = e
641
            messages.error(request, msg)
642
            transaction.rollback()
643
        except IntegrityError, e:
644
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
645
            messages.error(request, msg)
646
        else:
647
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
648
            messages.success(request, msg)
649
            transaction.commit()
650
    return render_response(
651
        form_template_name,
652
        form=form,
653
        context_instance=get_context(request, extra_context)
654
    )
655

    
656

    
657
def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
658
    extra_context = extra_context or {}
659
    try:
660
        u = AstakosUser.objects.get(id=user_id)
661
    except AstakosUser.DoesNotExist:
662
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
663
    else:
664
        try:
665
            send_activation_func(u)
666
            msg = _(astakos_messages.ACTIVATION_SENT)
667
            messages.success(request, msg)
668
        except SendMailError, e:
669
            messages.error(request, e)
670
    return render_response(
671
        template_name,
672
        login_form = LoginForm(request=request),
673
        context_instance = get_context(
674
            request,
675
            extra_context
676
        )
677
    )
678

    
679
class ResourcePresentation():
680
    
681
    def __init__(self, data):
682
        self.data = data
683
        
684
    def update_from_result(self, result):
685
        if result.is_success:
686
            for r in result.data:
687
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
688
                if not rname in self.data['resources']:
689
                    self.data['resources'][rname] = {}
690
                    
691
                self.data['resources'][rname].update(r)
692
                self.data['resources'][rname]['id'] = rname
693
                group = r.get('group')
694
                if not group in self.data['groups']:
695
                    self.data['groups'][group] = {}
696
                    
697
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
698
    
699
    def test(self, quota_dict):
700
        for k, v in quota_dict.iteritems():
701
            rname = k
702
            value = v
703
            if not rname in self.data['resources']:
704
                self.data['resources'][rname] = {}
705
                    
706
 
707
            self.data['resources'][rname]['value'] = value
708
            
709
    
710
    def update_from_result_report(self, result):
711
        if result.is_success:
712
            for r in result.data:
713
                rname = r.get('name')
714
                if not rname in self.data['resources']:
715
                    self.data['resources'][rname] = {}
716
                    
717
                self.data['resources'][rname].update(r)
718
                self.data['resources'][rname]['id'] = rname
719
                group = r.get('group')
720
                if not group in self.data['groups']:
721
                    self.data['groups'][group] = {}
722
                    
723
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
724
                
725
    def get_group_resources(self, group):
726
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
727
    
728
    def get_groups_resources(self):
729
        for g in self.data['groups']:
730
            yield g, self.get_group_resources(g)
731
    
732
    def get_quota(self, group_quotas):
733
        for r, v in group_quotas.iteritems():
734
            rname = str(r)
735
            quota = self.data['resources'].get(rname)
736
            quota['value'] = v
737
            yield quota
738
    
739
    
740
    def get_policies(self, policies_data):
741
        for policy in policies_data:
742
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
743
            policy.update(self.data['resources'].get(rname))
744
            yield policy
745
        
746
    def __repr__(self):
747
        return self.data.__repr__()
748
                
749
    def __iter__(self, *args, **kwargs):
750
        return self.data.__iter__(*args, **kwargs)
751
    
752
    def __getitem__(self, *args, **kwargs):
753
        return self.data.__getitem__(*args, **kwargs)
754
    
755
    def get(self, *args, **kwargs):
756
        return self.data.get(*args, **kwargs)
757
        
758
        
759

    
760
@require_http_methods(["GET", "POST"])
761
@signed_terms_required
762
@login_required
763
def group_add(request, kind_name='default'):
764
    
765
    result = callpoint.list_resources()
766
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
767
    resource_catalog.update_from_result(result)
768
    
769
    if not result.is_success:
770
        messages.error(
771
            request,
772
            'Unable to retrieve system resources: %s' % result.reason
773
    )
774
    
775
    try:
776
        kind = GroupKind.objects.get(name=kind_name)
777
    except:
778
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
779
    
780
    
781

    
782
    post_save_redirect = '/im/group/%(id)s/'
783
    context_processors = None
784
    model, form_class = get_model_and_form_class(
785
        model=None,
786
        form_class=AstakosGroupCreationForm
787
    )
788
    
789
    if request.method == 'POST':
790
        form = form_class(request.POST, request.FILES)
791
        if form.is_valid():
792
            return render_response(
793
                template='im/astakosgroup_form_summary.html',
794
                context_instance=get_context(request),
795
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
796
                policies = resource_catalog.get_policies(form.policies()),
797
                resource_catalog= resource_catalog,
798
            )
799
         
800
    else:
801
        now = datetime.now()
802
        data = {
803
            'kind': kind,
804
        }
805
        for group, resources in resource_catalog.get_groups_resources():
806
            data['is_selected_%s' % group] = False
807
            for resource in resources:
808
                data['%s_uplimit' % resource] = ''
809
        
810
        form = form_class(data)
811

    
812
    # Create the template, context, response
813
    template_name = "%s/%s_form.html" % (
814
        model._meta.app_label,
815
        model._meta.object_name.lower()
816
    )
817
    t = template_loader.get_template(template_name)
818
    c = RequestContext(request, {
819
        'form': form,
820
        'kind': kind,
821
        'resource_catalog':resource_catalog,
822
    }, context_processors)
823
    return HttpResponse(t.render(c))
824

    
825

    
826
#@require_http_methods(["POST"])
827
@require_http_methods(["GET", "POST"])
828
@signed_terms_required
829
@login_required
830
def group_add_complete(request):
831
    model = AstakosGroup
832
    form = AstakosGroupCreationSummaryForm(request.POST)
833
    if form.is_valid():
834
        d = form.cleaned_data
835
        d['owners'] = [request.user]
836
        result = callpoint.create_groups((d,)).next()
837
        if result.is_success:
838
            new_object = result.data[0]
839
            msg = _(astakos_messages.OBJECT_CREATED) %\
840
                {"verbose_name": model._meta.verbose_name}
841
            messages.success(request, msg, fail_silently=True)
842
            
843
            # send notification
844
            try:
845
                send_group_creation_notification(
846
                    template_name='im/group_creation_notification.txt',
847
                    dictionary={
848
                        'group': new_object,
849
                        'owner': request.user,
850
                        'policies': d.get('policies', [])
851
                    }
852
                )
853
            except SendNotificationError, e:
854
                messages.error(request, e, fail_silently=True)
855
            post_save_redirect = '/im/group/%(id)s/'
856
            return HttpResponseRedirect(post_save_redirect % new_object)
857
        else:
858
            d = {"verbose_name": model._meta.verbose_name,
859
                 "reason":result.reason}
860
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d 
861
            messages.error(request, msg, fail_silently=True)
862
    return render_response(
863
        template='im/astakosgroup_form_summary.html',
864
        context_instance=get_context(request),
865
        form=form)
866

    
867

    
868
#@require_http_methods(["GET"])
869
@require_http_methods(["GET", "POST"])
870
@signed_terms_required
871
@login_required
872
def group_list(request):
873
    none = request.user.astakos_groups.none()
874
    sorting = request.GET.get('sorting')
875
    query = """
876
        SELECT auth_group.id,
877
        %s AS groupname,
878
        im_groupkind.name AS kindname,
879
        im_astakosgroup.*,
880
        owner.email AS groupowner,
881
        (SELECT COUNT(*) FROM im_membership
882
            WHERE group_id = im_astakosgroup.group_ptr_id
883
            AND date_joined IS NOT NULL) AS approved_members_num,
884
        (SELECT CASE WHEN(
885
                    SELECT date_joined FROM im_membership
886
                    WHERE group_id = im_astakosgroup.group_ptr_id
887
                    AND person_id = %s) IS NULL
888
                    THEN 0 ELSE 1 END) AS membership_status
889
        FROM im_astakosgroup
890
        INNER JOIN im_membership ON (
891
            im_astakosgroup.group_ptr_id = im_membership.group_id)
892
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
893
        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
894
        LEFT JOIN im_astakosuser_owner ON (
895
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
896
        LEFT JOIN auth_user as owner ON (
897
            im_astakosuser_owner.astakosuser_id = owner.id)
898
        WHERE im_membership.person_id = %s 
899
        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id)
900
       
901
    if sorting:
902
        query = query+" ORDER BY %s ASC" %sorting    
903
    else:
904
        query = query+" ORDER BY groupname ASC"     
905
    q = AstakosGroup.objects.raw(query)
906

    
907
       
908
       
909
    # Create the template, context, response
910
    template_name = "%s/%s_list.html" % (
911
        q.model._meta.app_label,
912
        q.model._meta.object_name.lower()
913
    )
914
    extra_context = dict(
915
        is_search=False,
916
        q=q,
917
        sorting=request.GET.get('sorting'),
918
        page=request.GET.get('page', 1)
919
    )
920
    return render_response(template_name,
921
                           context_instance=get_context(request, extra_context)
922
    )
923

    
924

    
925
@require_http_methods(["GET", "POST"])
926
@signed_terms_required
927
@login_required
928
def group_detail(request, group_id):
929
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
930
    q = q.extra(select={
931
        'is_member': """SELECT CASE WHEN EXISTS(
932
                            SELECT id FROM im_membership
933
                            WHERE group_id = im_astakosgroup.group_ptr_id
934
                            AND person_id = %s)
935
                        THEN 1 ELSE 0 END""" % request.user.id,
936
        'is_owner': """SELECT CASE WHEN EXISTS(
937
                        SELECT id FROM im_astakosuser_owner
938
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
939
                        AND astakosuser_id = %s)
940
                        THEN 1 ELSE 0 END""" % request.user.id,
941
        'kindname': """SELECT name FROM im_groupkind
942
                       WHERE id = im_astakosgroup.kind_id"""})
943

    
944
    model = q.model
945
    context_processors = None
946
    mimetype = None
947
    try:
948
        obj = q.get()
949
    except AstakosGroup.DoesNotExist:
950
        raise Http404("No %s found matching the query" % (
951
            model._meta.verbose_name))
952

    
953
    update_form = AstakosGroupUpdateForm(instance=obj)
954
    addmembers_form = AddGroupMembersForm()
955
    if request.method == 'POST':
956
        update_data = {}
957
        addmembers_data = {}
958
        for k, v in request.POST.iteritems():
959
            if k in update_form.fields:
960
                update_data[k] = v
961
            if k in addmembers_form.fields:
962
                addmembers_data[k] = v
963
        update_data = update_data or None
964
        addmembers_data = addmembers_data or None
965
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
966
        addmembers_form = AddGroupMembersForm(addmembers_data)
967
        if update_form.is_valid():
968
            update_form.save()
969
        if addmembers_form.is_valid():
970
            try:
971
                map(obj.approve_member, addmembers_form.valid_users)
972
            except AssertionError:
973
                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
974
                messages.error(request, msg)
975
            addmembers_form = AddGroupMembersForm()
976

    
977
    template_name = "%s/%s_detail.html" % (
978
        model._meta.app_label, model._meta.object_name.lower())
979
    t = template_loader.get_template(template_name)
980
    c = RequestContext(request, {
981
        'object': obj,
982
    }, context_processors)
983

    
984
    # validate sorting
985
    sorting = request.GET.get('sorting')
986
    if sorting:
987
        form = MembersSortForm({'sort_by': sorting})
988
        if form.is_valid():
989
            sorting = form.cleaned_data.get('sort_by')
990
    
991
    else:
992
        form = MembersSortForm({'sort_by': 'person_first_name'})
993
    
994
    result = callpoint.list_resources()
995
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
996
    resource_catalog.update_from_result(result)
997

    
998
    
999
    if not result.is_success:
1000
        messages.error(
1001
            request,
1002
            'Unable to retrieve system resources: %s' % result.reason
1003
    )
1004
    
1005
    extra_context = {'update_form': update_form,
1006
                     'addmembers_form': addmembers_form,
1007
                     'page': request.GET.get('page', 1),
1008
                     'sorting': sorting,
1009
                     'resource_catalog':resource_catalog,
1010
                     'quota':resource_catalog.get_quota(obj.quota)}
1011
    for key, value in extra_context.items():
1012
        if callable(value):
1013
            c[key] = value()
1014
        else:
1015
            c[key] = value
1016
    response = HttpResponse(t.render(c), mimetype=mimetype)
1017
    populate_xheaders(
1018
        request, response, model, getattr(obj, obj._meta.pk.name))
1019
    return response
1020

    
1021

    
1022
@require_http_methods(["GET", "POST"])
1023
@signed_terms_required
1024
@login_required
1025
def group_search(request, extra_context=None, **kwargs):
1026
    q = request.GET.get('q')
1027
    sorting = request.GET.get('sorting')
1028
    if request.method == 'GET':
1029
        form = AstakosGroupSearchForm({'q': q} if q else None)
1030
    else:
1031
        form = AstakosGroupSearchForm(get_query(request))
1032
        if form.is_valid():
1033
            q = form.cleaned_data['q'].strip()
1034
    if q:
1035
        queryset = AstakosGroup.objects.select_related()
1036
        queryset = queryset.filter(name__contains=q)
1037
        queryset = queryset.filter(approval_date__isnull=False)
1038
        queryset = queryset.extra(select={
1039
                                  'groupname': DB_REPLACE_GROUP_SCHEME,
1040
                                  'kindname': "im_groupkind.name",
1041
                                  'approved_members_num': """
1042
                    SELECT COUNT(*) FROM im_membership
1043
                    WHERE group_id = im_astakosgroup.group_ptr_id
1044
                    AND date_joined IS NOT NULL""",
1045
                                  'membership_approval_date': """
1046
                    SELECT date_joined FROM im_membership
1047
                    WHERE group_id = im_astakosgroup.group_ptr_id
1048
                    AND person_id = %s""" % request.user.id,
1049
                                  'is_member': """
1050
                    SELECT CASE WHEN EXISTS(
1051
                    SELECT date_joined FROM im_membership
1052
                    WHERE group_id = im_astakosgroup.group_ptr_id
1053
                    AND person_id = %s)
1054
                    THEN 1 ELSE 0 END""" % request.user.id,
1055
                                  'is_owner': """
1056
                    SELECT CASE WHEN EXISTS(
1057
                    SELECT id FROM im_astakosuser_owner
1058
                    WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1059
                    AND astakosuser_id = %s)
1060
                    THEN 1 ELSE 0 END""" % request.user.id,
1061
                    'is_owner': """SELECT CASE WHEN EXISTS(
1062
                        SELECT id FROM im_astakosuser_owner
1063
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1064
                        AND astakosuser_id = %s)
1065
                        THEN 1 ELSE 0 END""" % request.user.id, 
1066
                    })
1067
        if sorting:
1068
            # TODO check sorting value
1069
            queryset = queryset.order_by(sorting)
1070
        else:
1071
            queryset = queryset.order_by("groupname")
1072

    
1073
    else:
1074
        queryset = AstakosGroup.objects.none()
1075
    return object_list(
1076
        request,
1077
        queryset,
1078
        paginate_by=PAGINATE_BY_ALL,
1079
        page=request.GET.get('page') or 1,
1080
        template_name='im/astakosgroup_list.html',
1081
        extra_context=dict(form=form,
1082
                           is_search=True,
1083
                           q=q,
1084
                           sorting=sorting))
1085

    
1086

    
1087
@require_http_methods(["GET", "POST"])
1088
@signed_terms_required
1089
@login_required
1090
def group_all(request, extra_context=None, **kwargs):
1091
    q = AstakosGroup.objects.select_related()
1092
    q = q.filter(approval_date__isnull=False)
1093
    q = q.extra(select={
1094
                'groupname': DB_REPLACE_GROUP_SCHEME,
1095
                'kindname': "im_groupkind.name",
1096
                'approved_members_num': """
1097
                    SELECT COUNT(*) FROM im_membership
1098
                    WHERE group_id = im_astakosgroup.group_ptr_id
1099
                    AND date_joined IS NOT NULL""",
1100
                'membership_approval_date': """
1101
                    SELECT date_joined FROM im_membership
1102
                    WHERE group_id = im_astakosgroup.group_ptr_id
1103
                    AND person_id = %s""" % request.user.id,
1104
                'is_member': """
1105
                    SELECT CASE WHEN EXISTS(
1106
                    SELECT date_joined FROM im_membership
1107
                    WHERE group_id = im_astakosgroup.group_ptr_id
1108
                    AND person_id = %s)
1109
                    THEN 1 ELSE 0 END""" % request.user.id,
1110
                 'is_owner': """SELECT CASE WHEN EXISTS(
1111
                        SELECT id FROM im_astakosuser_owner
1112
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1113
                        AND astakosuser_id = %s)
1114
                        THEN 1 ELSE 0 END""" % request.user.id,   })
1115
    sorting = request.GET.get('sorting')
1116
    if sorting:
1117
        # TODO check sorting value
1118
        q = q.order_by(sorting)
1119
    else:
1120
        q = q.order_by("groupname")
1121
        
1122
    return object_list(
1123
        request,
1124
        q,
1125
        paginate_by=PAGINATE_BY_ALL,
1126
        page=request.GET.get('page') or 1,
1127
        template_name='im/astakosgroup_list.html',
1128
        extra_context=dict(form=AstakosGroupSearchForm(),
1129
                           is_search=True,
1130
                           sorting=sorting))
1131

    
1132

    
1133
#@require_http_methods(["POST"])
1134
@require_http_methods(["POST", "GET"])
1135
@signed_terms_required
1136
@login_required
1137
def group_join(request, group_id):
1138
    m = Membership(group_id=group_id,
1139
                   person=request.user,
1140
                   date_requested=datetime.now())
1141
    try:
1142
        m.save()
1143
        post_save_redirect = reverse(
1144
            'group_detail',
1145
            kwargs=dict(group_id=group_id))
1146
        return HttpResponseRedirect(post_save_redirect)
1147
    except IntegrityError, e:
1148
        logger.exception(e)
1149
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1150
        messages.error(request, msg)
1151
        return group_search(request)
1152

    
1153

    
1154
@require_http_methods(["POST"])
1155
@signed_terms_required
1156
@login_required
1157
def group_leave(request, group_id):
1158
    try:
1159
        m = Membership.objects.select_related().get(
1160
            group__id=group_id,
1161
            person=request.user)
1162
    except Membership.DoesNotExist:
1163
        return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1164
    if request.user in m.group.owner.all():
1165
        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1166
    return delete_object(
1167
        request,
1168
        model=Membership,
1169
        object_id=m.id,
1170
        template_name='im/astakosgroup_list.html',
1171
        post_delete_redirect=reverse(
1172
            'group_detail',
1173
            kwargs=dict(group_id=group_id)))
1174

    
1175

    
1176
def handle_membership(func):
1177
    @wraps(func)
1178
    def wrapper(request, group_id, user_id):
1179
        try:
1180
            m = Membership.objects.select_related().get(
1181
                group__id=group_id,
1182
                person__id=user_id)
1183
        except Membership.DoesNotExist:
1184
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1185
        else:
1186
            if request.user not in m.group.owner.all():
1187
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1188
            func(request, m)
1189
            return group_detail(request, group_id)
1190
    return wrapper
1191

    
1192

    
1193
#@require_http_methods(["POST"])
1194
@require_http_methods(["POST", "GET"])
1195
@signed_terms_required
1196
@login_required
1197
@handle_membership
1198
def approve_member(request, membership):
1199
    try:
1200
        membership.approve()
1201
        realname = membership.person.realname
1202
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1203
        messages.success(request, msg)
1204
    except AssertionError:
1205
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1206
        messages.error(request, msg)
1207
    except BaseException, e:
1208
        logger.exception(e)
1209
        realname = membership.person.realname
1210
        msg = _(astakos_messages.GENERIC_ERROR)
1211
        messages.error(request, msg)
1212

    
1213

    
1214
@signed_terms_required
1215
@login_required
1216
@handle_membership
1217
def disapprove_member(request, membership):
1218
    try:
1219
        membership.disapprove()
1220
        realname = membership.person.realname
1221
        msg = astakos_messages.MEMBER_REMOVED % realname
1222
        messages.success(request, msg)
1223
    except BaseException, e:
1224
        logger.exception(e)
1225
        msg = _(astakos_messages.GENERIC_ERROR)
1226
        messages.error(request, msg)
1227

    
1228

    
1229
#@require_http_methods(["GET"])
1230
@require_http_methods(["POST", "GET"])
1231
@signed_terms_required
1232
@login_required
1233
def resource_list(request):
1234
    def with_class(entry):
1235
        entry['load_class'] = 'red'
1236
        max_value = float(entry['maxValue'])
1237
        curr_value = float(entry['currValue'])
1238
        if max_value > 0 :
1239
            entry['ratio'] = (curr_value / max_value) * 100
1240
        else:
1241
            entry['ratio'] = 0 
1242
        if entry['ratio'] < 66:
1243
            entry['load_class'] = 'yellow'
1244
        if entry['ratio'] < 33:
1245
            entry['load_class'] = 'green'
1246
        return entry
1247

    
1248
    def pluralize(entry):
1249
        entry['plural'] = engine.plural(entry.get('name'))
1250
        return entry
1251

    
1252
    result = callpoint.get_user_status(request.user.id)
1253
    if result.is_success:
1254
        backenddata = map(with_class, result.data)
1255
        data = map(pluralize, result.data)
1256
    else:
1257
        data = None
1258
        messages.error(request, result.reason)
1259
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1260
    resource_catalog.update_from_result_report(result)
1261
    
1262

    
1263
    
1264
    return render_response('im/resource_list.html',
1265
                           data=data,
1266
                           context_instance=get_context(request),
1267
                           resource_catalog=resource_catalog,
1268
                           result=result)
1269

    
1270

    
1271
def group_create_list(request):
1272
    form = PickResourceForm()
1273
    return render_response(
1274
        template='im/astakosgroup_create_list.html',
1275
        context_instance=get_context(request),)
1276

    
1277

    
1278
#@require_http_methods(["GET"])
1279
@require_http_methods(["POST", "GET"])
1280
@signed_terms_required
1281
@login_required
1282
def billing(request):
1283

    
1284
    today = datetime.today()
1285
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1286
    start = request.POST.get('datefrom', None)
1287
    if start:
1288
        today = datetime.fromtimestamp(int(start))
1289
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1290

    
1291
    start = datetime(today.year, today.month, 1).strftime("%s")
1292
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1293
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1294
                                    int(start) * 1000,
1295
                                    int(end) * 1000))
1296
    data = {}
1297

    
1298
    try:
1299
        status, data = r.result
1300
        data = _clear_billing_data(data)
1301
        if status != 200:
1302
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1303
    except:
1304
        messages.error(request, r.result)
1305

    
1306
    return render_response(
1307
        template='im/billing.html',
1308
        context_instance=get_context(request),
1309
        data=data,
1310
        zerodate=datetime(month=1, year=1970, day=1),
1311
        today=today,
1312
        start=int(start),
1313
        month_last_day=month_last_day)
1314

    
1315

    
1316
def _clear_billing_data(data):
1317

    
1318
    # remove addcredits entries
1319
    def isnotcredit(e):
1320
        return e['serviceName'] != "addcredits"
1321

    
1322
    # separate services
1323
    def servicefilter(service_name):
1324
        service = service_name
1325

    
1326
        def fltr(e):
1327
            return e['serviceName'] == service
1328
        return fltr
1329

    
1330
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1331
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1332
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1333
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1334

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

    
1361
    return render_response(template='im/timeline.html',
1362
                           context_instance=get_context(request),
1363
                           form=form,
1364
                           timeline_header=timeline_header,
1365
                           timeline_body=timeline_body)
1366
    return data
1367

    
1368

    
1369
def how_it_works(request):
1370
    return render_response(
1371
        template='im/how_it_works.html',
1372
        context_instance=get_context(request),)