Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 6abd262c

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

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

    
93
import astakos.im.messages as astakos_messages
94

    
95
logger = logging.getLogger(__name__)
96

    
97

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

    
101
callpoint = AstakosCallpoint()
102

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

    
120

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

    
135

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

    
151

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

158
    **Arguments**
159

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

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

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

171
    **Template:**
172

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

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

    
183

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

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

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

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

201
    **Arguments**
202

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

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

210
    **Template:**
211

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

214
    **Settings:**
215

216
    The view expectes the following settings are defined:
217

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

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

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

    
262

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

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

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

276
    **Arguments**
277

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

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

285
    **Template:**
286

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

289
    **Settings:**
290

291
    The view expectes the following settings are defined:
292

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

    
324

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

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

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

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

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

344
    **Arguments**
345

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

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

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

357
    **Template:**
358

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

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

    
411

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

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

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

424
    **Arguments**
425

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

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

433
    **Template:**
434

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

437
    **Settings:**
438

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

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

    
463

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

    
492

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

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

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

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

    
566

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

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

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

    
609

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

    
617

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

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

    
669

    
670

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

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

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

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

    
803

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

    
845

    
846
#@require_http_methods(["GET"])
847
@require_http_methods(["GET", "POST"])
848
@signed_terms_required
849
@login_required
850
def group_list(request):
851
    none = request.user.astakos_groups.none()
852
    q = AstakosGroup.objects.raw("""
853
        SELECT auth_group.id,
854
        %s AS groupname,
855
        im_groupkind.name AS kindname,
856
        im_astakosgroup.*,
857
        owner.email AS groupowner,
858
        (SELECT COUNT(*) FROM im_membership
859
            WHERE group_id = im_astakosgroup.group_ptr_id
860
            AND date_joined IS NOT NULL) AS approved_members_num,
861
        (SELECT CASE WHEN(
862
                    SELECT date_joined FROM im_membership
863
                    WHERE group_id = im_astakosgroup.group_ptr_id
864
                    AND person_id = %s) IS NULL
865
                    THEN 0 ELSE 1 END) AS membership_status
866
        FROM im_astakosgroup
867
        INNER JOIN im_membership ON (
868
            im_astakosgroup.group_ptr_id = im_membership.group_id)
869
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
870
        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
871
        LEFT JOIN im_astakosuser_owner ON (
872
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
873
        LEFT JOIN auth_user as owner ON (
874
            im_astakosuser_owner.astakosuser_id = owner.id)
875
        WHERE im_membership.person_id = %s
876
        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
877
    q = list(q)
878
#    d = defaultdict(list)
879
#    
880
#    for g in q:
881
#        if request.user.email == g.groupowner:
882
#            d['own'].append(g)
883
#        else:
884
#            d['other'].append(g)
885
#    
886
#    for g in q:
887
#        d['all'].append(g)
888
#        
889
#        
890
#        
891
#    
892
#    # validate sorting
893
#    fields = ('own', 'other', 'all')
894
#    for f in fields:
895
#        v = globals()['%s_sorting' % f] = request.GET.get('%s_sorting' % f)
896
#        if v:
897
#            form = AstakosGroupSortForm({'sort_by': v})
898
#            if not form.is_valid():
899
#                globals()['%s_sorting' % f] = form.cleaned_data.get('sort_by')
900
    return object_list(request, queryset=none,
901
                       extra_context={'is_search': False,
902
                                      'q': q,
903
#                                      'mine': d['own'],
904
#                                      'other': d['other'],
905
#                                      'own_sorting': own_sorting,
906
#                                      'other_sorting': other_sorting,
907
                                      'sorting': request.GET.get('sorting'),
908
#                                      'own_page': request.GET.get('own_page', 1),
909
#                                      'other_page': request.GET.get('other_page', 1),
910
#                                      'all_page': request.GET.get('all_page', 1)
911
                                      })
912

    
913

    
914
@require_http_methods(["GET", "POST"])
915
@signed_terms_required
916
@login_required
917
def group_detail(request, group_id):
918
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
919
    q = q.extra(select={
920
        'is_member': """SELECT CASE WHEN EXISTS(
921
                            SELECT id FROM im_membership
922
                            WHERE group_id = im_astakosgroup.group_ptr_id
923
                            AND person_id = %s)
924
                        THEN 1 ELSE 0 END""" % request.user.id,
925
        'is_owner': """SELECT CASE WHEN EXISTS(
926
                        SELECT id FROM im_astakosuser_owner
927
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
928
                        AND astakosuser_id = %s)
929
                        THEN 1 ELSE 0 END""" % request.user.id,
930
        'kindname': """SELECT name FROM im_groupkind
931
                       WHERE id = im_astakosgroup.kind_id"""})
932

    
933
    model = q.model
934
    context_processors = None
935
    mimetype = None
936
    try:
937
        obj = q.get()
938
    except AstakosGroup.DoesNotExist:
939
        raise Http404("No %s found matching the query" % (
940
            model._meta.verbose_name))
941

    
942
    update_form = AstakosGroupUpdateForm(instance=obj)
943
    addmembers_form = AddGroupMembersForm()
944
    if request.method == 'POST':
945
        update_data = {}
946
        addmembers_data = {}
947
        for k, v in request.POST.iteritems():
948
            if k in update_form.fields:
949
                update_data[k] = v
950
            if k in addmembers_form.fields:
951
                addmembers_data[k] = v
952
        update_data = update_data or None
953
        addmembers_data = addmembers_data or None
954
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
955
        addmembers_form = AddGroupMembersForm(addmembers_data)
956
        if update_form.is_valid():
957
            update_form.save()
958
        if addmembers_form.is_valid():
959
            try:
960
                map(obj.approve_member, addmembers_form.valid_users)
961
            except AssertionError:
962
                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
963
                messages.error(request, msg)
964
            addmembers_form = AddGroupMembersForm()
965

    
966
    template_name = "%s/%s_detail.html" % (
967
        model._meta.app_label, model._meta.object_name.lower())
968
    t = template_loader.get_template(template_name)
969
    c = RequestContext(request, {
970
        'object': obj,
971
    }, context_processors)
972

    
973
    # validate sorting
974
    sorting = request.GET.get('sorting')
975
    if sorting:
976
        form = MembersSortForm({'sort_by': sorting})
977
        if form.is_valid():
978
            sorting = form.cleaned_data.get('sort_by')
979

    
980
    extra_context = {'update_form': update_form,
981
                     'addmembers_form': addmembers_form,
982
                     'page': request.GET.get('page', 1),
983
                     'sorting': sorting}
984
    for key, value in extra_context.items():
985
        if callable(value):
986
            c[key] = value()
987
        else:
988
            c[key] = value
989
    response = HttpResponse(t.render(c), mimetype=mimetype)
990
    populate_xheaders(
991
        request, response, model, getattr(obj, obj._meta.pk.name))
992
    return response
993

    
994

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

    
1050

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

    
1088

    
1089
#@require_http_methods(["POST"])
1090
@require_http_methods(["POST", "GET"])
1091
@signed_terms_required
1092
@login_required
1093
def group_join(request, group_id):
1094
    m = Membership(group_id=group_id,
1095
                   person=request.user,
1096
                   date_requested=datetime.now())
1097
    try:
1098
        m.save()
1099
        post_save_redirect = reverse(
1100
            'group_detail',
1101
            kwargs=dict(group_id=group_id))
1102
        return HttpResponseRedirect(post_save_redirect)
1103
    except IntegrityError, e:
1104
        logger.exception(e)
1105
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1106
        messages.error(request, msg)
1107
        return group_search(request)
1108

    
1109

    
1110
@require_http_methods(["POST"])
1111
@signed_terms_required
1112
@login_required
1113
def group_leave(request, group_id):
1114
    try:
1115
        m = Membership.objects.select_related().get(
1116
            group__id=group_id,
1117
            person=request.user)
1118
    except Membership.DoesNotExist:
1119
        return HttpResponseBadRequest(_(astakos_messages.NOT_A_MEMBER))
1120
    if request.user in m.group.owner.all():
1121
        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1122
    return delete_object(
1123
        request,
1124
        model=Membership,
1125
        object_id=m.id,
1126
        template_name='im/astakosgroup_list.html',
1127
        post_delete_redirect=reverse(
1128
            'group_detail',
1129
            kwargs=dict(group_id=group_id)))
1130

    
1131

    
1132
def handle_membership(func):
1133
    @wraps(func)
1134
    def wrapper(request, group_id, user_id):
1135
        try:
1136
            m = Membership.objects.select_related().get(
1137
                group__id=group_id,
1138
                person__id=user_id)
1139
        except Membership.DoesNotExist:
1140
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1141
        else:
1142
            if request.user not in m.group.owner.all():
1143
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1144
            func(request, m)
1145
            return group_detail(request, group_id)
1146
    return wrapper
1147

    
1148

    
1149
#@require_http_methods(["POST"])
1150
@require_http_methods(["POST", "GET"])
1151
@signed_terms_required
1152
@login_required
1153
@handle_membership
1154
def approve_member(request, membership):
1155
    try:
1156
        membership.approve()
1157
        realname = membership.person.realname
1158
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1159
        messages.success(request, msg)
1160
    except AssertionError:
1161
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1162
        messages.error(request, msg)
1163
    except BaseException, e:
1164
        logger.exception(e)
1165
        realname = membership.person.realname
1166
        msg = _(astakos_messages.GENERIC_ERROR)
1167
        messages.error(request, msg)
1168

    
1169

    
1170
@signed_terms_required
1171
@login_required
1172
@handle_membership
1173
def disapprove_member(request, membership):
1174
    try:
1175
        membership.disapprove()
1176
        realname = membership.person.realname
1177
        msg = MEMBER_REMOVED % realname
1178
        messages.success(request, msg)
1179
    except BaseException, e:
1180
        logger.exception(e)
1181
        msg = _(astakos_messages.GENERIC_ERROR)
1182
        messages.error(request, msg)
1183

    
1184

    
1185
#@require_http_methods(["GET"])
1186
@require_http_methods(["POST", "GET"])
1187
@signed_terms_required
1188
@login_required
1189
def resource_list(request):
1190
#     if request.method == 'POST':
1191
#         form = PickResourceForm(request.POST)
1192
#         if form.is_valid():
1193
#             r = form.cleaned_data.get('resource')
1194
#             if r:
1195
#                 groups = request.user.membership_set.only('group').filter(
1196
#                     date_joined__isnull=False)
1197
#                 groups = [g.group_id for g in groups]
1198
#                 q = AstakosGroupQuota.objects.select_related().filter(
1199
#                     resource=r, group__in=groups)
1200
#     else:
1201
#         form = PickResourceForm()
1202
#         q = AstakosGroupQuota.objects.none()
1203
#
1204
#     return object_list(request, q,
1205
#                        template_name='im/astakosuserquota_list.html',
1206
#                        extra_context={'form': form, 'data':data})
1207

    
1208
    def with_class(entry):
1209
        entry['load_class'] = 'red'
1210
        max_value = float(entry['maxValue'])
1211
        curr_value = float(entry['currValue'])
1212
        if max_value > 0 :
1213
           entry['ratio'] = (curr_value / max_value) * 100
1214
        else: 
1215
           entry['ratio'] = 0 
1216
        if entry['ratio'] < 66:
1217
            entry['load_class'] = 'yellow'
1218
        if entry['ratio'] < 33:
1219
            entry['load_class'] = 'green'
1220
        return entry
1221

    
1222
    def pluralize(entry):
1223
        entry['plural'] = engine.plural(entry.get('name'))
1224
        return entry
1225

    
1226
    result = callpoint.get_user_status(request.user.id)
1227
    if result.is_success:
1228
        backenddata = map(with_class, result.data)
1229
        data = map(pluralize, result.data)
1230
    else:
1231
        data = None
1232
        messages.error(request, result.reason)
1233
    return render_response('im/resource_list.html',
1234
                           data=data,
1235
                           resource_presentation=resource_presentation,
1236
                           context_instance=get_context(request))
1237

    
1238

    
1239
def group_create_list(request):
1240
    form = PickResourceForm()
1241
    return render_response(
1242
        template='im/astakosgroup_create_list.html',
1243
        context_instance=get_context(request),)
1244

    
1245

    
1246
#@require_http_methods(["GET"])
1247
@require_http_methods(["POST", "GET"])
1248
@signed_terms_required
1249
@login_required
1250
def billing(request):
1251

    
1252
    today = datetime.today()
1253
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1254
    data['resources'] = map(with_class, data['resources'])
1255
    start = request.POST.get('datefrom', None)
1256
    if start:
1257
        today = datetime.fromtimestamp(int(start))
1258
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1259

    
1260
    start = datetime(today.year, today.month, 1).strftime("%s")
1261
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1262
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1263
                                    int(start) * 1000,
1264
                                    int(end) * 1000))
1265
    data = {}
1266

    
1267
    try:
1268
        status, data = r.result
1269
        data = _clear_billing_data(data)
1270
        if status != 200:
1271
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1272
    except:
1273
        messages.error(request, r.result)
1274

    
1275
    print type(start)
1276

    
1277
    return render_response(
1278
        template='im/billing.html',
1279
        context_instance=get_context(request),
1280
        data=data,
1281
        zerodate=datetime(month=1, year=1970, day=1),
1282
        today=today,
1283
        start=int(start),
1284
        month_last_day=month_last_day)
1285

    
1286

    
1287
def _clear_billing_data(data):
1288

    
1289
    # remove addcredits entries
1290
    def isnotcredit(e):
1291
        return e['serviceName'] != "addcredits"
1292

    
1293
    # separate services
1294
    def servicefilter(service_name):
1295
        service = service_name
1296

    
1297
        def fltr(e):
1298
            return e['serviceName'] == service
1299
        return fltr
1300

    
1301
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1302
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1303
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1304
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1305

    
1306
    return data
1307
     
1308
     
1309
#@require_http_methods(["GET"])
1310
@require_http_methods(["POST", "GET"])
1311
@signed_terms_required
1312
@login_required
1313
def timeline(request):
1314
#    data = {'entity':request.user.email}
1315
    timeline_body = ()
1316
    timeline_header = ()
1317
#    form = TimelineForm(data)
1318
    form = TimelineForm()
1319
    if request.method == 'POST':
1320
        data = request.POST
1321
        form = TimelineForm(data)
1322
        if form.is_valid():
1323
            data = form.cleaned_data
1324
            timeline_header = ('entity', 'resource',
1325
                               'event name', 'event date',
1326
                               'incremental cost', 'total cost')
1327
            timeline_body = timeline_charge(
1328
                data['entity'], data['resource'],
1329
                data['start_date'], data['end_date'],
1330
                data['details'], data['operation'])
1331

    
1332
    return render_response(template='im/timeline.html',
1333
                           context_instance=get_context(request),
1334
                           form=form,
1335
                           timeline_header=timeline_header,
1336
                           timeline_body=timeline_body)
1337
    return data