Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 3cbd5e47

History | View | Annotate | Download (50.3 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

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

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

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

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

    
94
import astakos.im.messages as astakos_messages
95

    
96
logger = logging.getLogger(__name__)
97

    
98

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

    
102
callpoint = AstakosCallpoint()
103

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

    
121

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

    
136

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

    
152

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

159
    **Arguments**
160

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

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

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

172
    **Template:**
173

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

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

    
184

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

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

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

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

202
    **Arguments**
203

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

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

211
    **Template:**
212

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

215
    **Settings:**
216

217
    The view expectes the following settings are defined:
218

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

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

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

    
263

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

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

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

277
    **Arguments**
278

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

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

286
    **Template:**
287

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

290
    **Settings:**
291

292
    The view expectes the following settings are defined:
293

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

    
325

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

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

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

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

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

345
    **Arguments**
346

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

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

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

358
    **Template:**
359

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

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

    
412

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

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

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

425
    **Arguments**
426

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

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

434
    **Template:**
435

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

438
    **Settings:**
439

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

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

    
464

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

    
493

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

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

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

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

    
567

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

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

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

    
610

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

    
618

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

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

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

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

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

    
805
    # Create the template, context, response
806
    template_name = "%s/%s_form.html" % (
807
        model._meta.app_label,
808
        model._meta.object_name.lower()
809
    )
810
    t = template_loader.get_template(template_name)
811
    c = RequestContext(request, {
812
        'form': form,
813
        'kind': kind,
814
        'resource_catalog':resource_catalog,
815
    }, context_processors)
816
    return HttpResponse(t.render(c))
817

    
818

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

    
860

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

    
900
       
901
       
902
    # Create the template, context, response
903
    template_name = "%s/%s_list.html" % (
904
        q.model._meta.app_label,
905
        q.model._meta.object_name.lower()
906
    )
907
    extra_context = dict(
908
        is_search=False,
909
        q=q,
910
        sorting=request.GET.get('sorting'),
911
        page=request.GET.get('page', 1)
912
    )
913
    return render_response(template_name,
914
                           context_instance=get_context(request, extra_context)
915
    )
916

    
917

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

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

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

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

    
977
    # validate sorting
978
    sorting = request.GET.get('sorting')
979
    if sorting:
980
        form = MembersSortForm({'sort_by': sorting})
981
        if form.is_valid():
982
            sorting = form.cleaned_data.get('sort_by')
983
    
984
 
985
    
986
    result = callpoint.list_resources()
987
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
988
    resource_catalog.update_from_result(result)
989

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

    
1015

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

    
1065
    else:
1066
        queryset = AstakosGroup.objects.none()
1067
    return object_list(
1068
        request,
1069
        queryset,
1070
        paginate_by=PAGINATE_BY,
1071
        page=request.GET.get('page') or 1,
1072
        template_name='im/astakosgroup_list.html',
1073
        extra_context=dict(form=form,
1074
                           is_search=True,
1075
                           q=q,
1076
                           sorting=sorting))
1077

    
1078

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

    
1121

    
1122
#@require_http_methods(["POST"])
1123
@require_http_methods(["POST", "GET"])
1124
@signed_terms_required
1125
@login_required
1126
def group_join(request, group_id):
1127
    m = Membership(group_id=group_id,
1128
                   person=request.user,
1129
                   date_requested=datetime.now())
1130
    try:
1131
        m.save()
1132
        post_save_redirect = reverse(
1133
            'group_detail',
1134
            kwargs=dict(group_id=group_id))
1135
        return HttpResponseRedirect(post_save_redirect)
1136
    except IntegrityError, e:
1137
        logger.exception(e)
1138
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1139
        messages.error(request, msg)
1140
        return group_search(request)
1141

    
1142

    
1143
@require_http_methods(["POST"])
1144
@signed_terms_required
1145
@login_required
1146
def group_leave(request, group_id):
1147
    try:
1148
        m = Membership.objects.select_related().get(
1149
            group__id=group_id,
1150
            person=request.user)
1151
    except Membership.DoesNotExist:
1152
        return HttpResponseBadRequest(_(astakos_messages.NOT_A_MEMBER))
1153
    if request.user in m.group.owner.all():
1154
        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1155
    return delete_object(
1156
        request,
1157
        model=Membership,
1158
        object_id=m.id,
1159
        template_name='im/astakosgroup_list.html',
1160
        post_delete_redirect=reverse(
1161
            'group_detail',
1162
            kwargs=dict(group_id=group_id)))
1163

    
1164

    
1165
def handle_membership(func):
1166
    @wraps(func)
1167
    def wrapper(request, group_id, user_id):
1168
        try:
1169
            m = Membership.objects.select_related().get(
1170
                group__id=group_id,
1171
                person__id=user_id)
1172
        except Membership.DoesNotExist:
1173
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1174
        else:
1175
            if request.user not in m.group.owner.all():
1176
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1177
            func(request, m)
1178
            return group_detail(request, group_id)
1179
    return wrapper
1180

    
1181

    
1182
#@require_http_methods(["POST"])
1183
@require_http_methods(["POST", "GET"])
1184
@signed_terms_required
1185
@login_required
1186
@handle_membership
1187
def approve_member(request, membership):
1188
    try:
1189
        membership.approve()
1190
        realname = membership.person.realname
1191
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1192
        messages.success(request, msg)
1193
    except AssertionError:
1194
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1195
        messages.error(request, msg)
1196
    except BaseException, e:
1197
        logger.exception(e)
1198
        realname = membership.person.realname
1199
        msg = _(astakos_messages.GENERIC_ERROR)
1200
        messages.error(request, msg)
1201

    
1202

    
1203
@signed_terms_required
1204
@login_required
1205
@handle_membership
1206
def disapprove_member(request, membership):
1207
    try:
1208
        membership.disapprove()
1209
        realname = membership.person.realname
1210
        msg = MEMBER_REMOVED % realname
1211
        messages.success(request, msg)
1212
    except BaseException, e:
1213
        logger.exception(e)
1214
        msg = _(astakos_messages.GENERIC_ERROR)
1215
        messages.error(request, msg)
1216

    
1217

    
1218
#@require_http_methods(["GET"])
1219
@require_http_methods(["POST", "GET"])
1220
@signed_terms_required
1221
@login_required
1222
def resource_list(request):
1223
#     if request.method == 'POST':
1224
#         form = PickResourceForm(request.POST)
1225
#         if form.is_valid():
1226
#             r = form.cleaned_data.get('resource')
1227
#             if r:
1228
#                 groups = request.user.membership_set.only('group').filter(
1229
#                     date_joined__isnull=False)
1230
#                 groups = [g.group_id for g in groups]
1231
#                 q = AstakosGroupQuota.objects.select_related().filter(
1232
#                     resource=r, group__in=groups)
1233
#     else:
1234
#         form = PickResourceForm()
1235
#         q = AstakosGroupQuota.objects.none()
1236
#
1237
#     return object_list(request, q,
1238
#                        template_name='im/astakosuserquota_list.html',
1239
#                        extra_context={'form': form, 'data':data})
1240

    
1241
    def with_class(entry):
1242
        entry['load_class'] = 'red'
1243
        max_value = float(entry['maxValue'])
1244
        curr_value = float(entry['currValue'])
1245
        if max_value > 0 :
1246
           entry['ratio'] = (curr_value / max_value) * 100
1247
        else: 
1248
           entry['ratio'] = 0 
1249
        if entry['ratio'] < 66:
1250
            entry['load_class'] = 'yellow'
1251
        if entry['ratio'] < 33:
1252
            entry['load_class'] = 'green'
1253
        return entry
1254

    
1255
    def pluralize(entry):
1256
        entry['plural'] = engine.plural(entry.get('name'))
1257
        return entry
1258

    
1259
    result = callpoint.get_user_status(request.user.id)
1260
    if result.is_success:
1261
        backenddata = map(with_class, result.data)
1262
        data = map(pluralize, result.data)
1263
    else:
1264
        data = None
1265
        messages.error(request, result.reason)
1266
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1267
    resource_catalog.update_from_result_report(result)
1268
    
1269

    
1270
    
1271
    return render_response('im/resource_list.html',
1272
                           data=data,
1273
                           context_instance=get_context(request),
1274
                                       resource_catalog=resource_catalog,
1275
                           result=result)
1276

    
1277

    
1278
def group_create_list(request):
1279
    form = PickResourceForm()
1280
    return render_response(
1281
        template='im/astakosgroup_create_list.html',
1282
        context_instance=get_context(request),)
1283

    
1284

    
1285
#@require_http_methods(["GET"])
1286
@require_http_methods(["POST", "GET"])
1287
@signed_terms_required
1288
@login_required
1289
def billing(request):
1290

    
1291
    today = datetime.today()
1292
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1293
    data['resources'] = map(with_class, data['resources'])
1294
    start = request.POST.get('datefrom', None)
1295
    if start:
1296
        today = datetime.fromtimestamp(int(start))
1297
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1298

    
1299
    start = datetime(today.year, today.month, 1).strftime("%s")
1300
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1301
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1302
                                    int(start) * 1000,
1303
                                    int(end) * 1000))
1304
    data = {}
1305

    
1306
    try:
1307
        status, data = r.result
1308
        data = _clear_billing_data(data)
1309
        if status != 200:
1310
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1311
    except:
1312
        messages.error(request, r.result)
1313

    
1314
    print type(start)
1315

    
1316
    return render_response(
1317
        template='im/billing.html',
1318
        context_instance=get_context(request),
1319
        data=data,
1320
        zerodate=datetime(month=1, year=1970, day=1),
1321
        today=today,
1322
        start=int(start),
1323
        month_last_day=month_last_day)
1324

    
1325

    
1326
def _clear_billing_data(data):
1327

    
1328
    # remove addcredits entries
1329
    def isnotcredit(e):
1330
        return e['serviceName'] != "addcredits"
1331

    
1332
    # separate services
1333
    def servicefilter(service_name):
1334
        service = service_name
1335

    
1336
        def fltr(e):
1337
            return e['serviceName'] == service
1338
        return fltr
1339

    
1340
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1341
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1342
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1343
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1344

    
1345
    return data
1346
     
1347
     
1348
#@require_http_methods(["GET"])
1349
@require_http_methods(["POST", "GET"])
1350
@signed_terms_required
1351
@login_required
1352
def timeline(request):
1353
#    data = {'entity':request.user.email}
1354
    timeline_body = ()
1355
    timeline_header = ()
1356
#    form = TimelineForm(data)
1357
    form = TimelineForm()
1358
    if request.method == 'POST':
1359
        data = request.POST
1360
        form = TimelineForm(data)
1361
        if form.is_valid():
1362
            data = form.cleaned_data
1363
            timeline_header = ('entity', 'resource',
1364
                               'event name', 'event date',
1365
                               'incremental cost', 'total cost')
1366
            timeline_body = timeline_charge(
1367
                data['entity'], data['resource'],
1368
                data['start_date'], data['end_date'],
1369
                data['details'], data['operation'])
1370

    
1371
    return render_response(template='im/timeline.html',
1372
                           context_instance=get_context(request),
1373
                           form=form,
1374
                           timeline_header=timeline_header,
1375
                           timeline_body=timeline_body)
1376
    return data