Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 59728d4e

History | View | Annotate | Download (50.5 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, PAGINATE_BY_ALL)
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
            return render_response(
785
                template='im/astakosgroup_form_summary.html',
786
                context_instance=get_context(request),
787
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
788
                policies = resource_catalog.get_policies(form.policies()),
789
                resource_catalog= resource_catalog,
790
            )
791
         
792
    else:
793
        now = datetime.now()
794
        data = {
795
            'kind': kind,
796
        }
797
        for group, resources in resource_catalog.get_groups_resources():
798
            data['is_selected_%s' % group] = False
799
            for resource in resources:
800
                data['%s_uplimit' % resource] = ''
801
        
802
        form = form_class(data)
803

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

    
817

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

    
859

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

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

    
916

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

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

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

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

    
976
    # validate sorting
977
    sorting = request.GET.get('sorting')
978
    if sorting:
979
        form = MembersSortForm({'sort_by': sorting})
980
        if form.is_valid():
981
            sorting = form.cleaned_data.get('sort_by')
982
    
983
    else:
984
        form = MembersSortForm({'sort_by': 'person_first_name'})
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
        else:
1065
            queryset = queryset.order_by("groupname")
1066

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

    
1080

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

    
1126

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

    
1147

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

    
1169

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

    
1186

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

    
1207

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

    
1222

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

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

    
1260
    def pluralize(entry):
1261
        entry['plural'] = engine.plural(entry.get('name'))
1262
        return entry
1263

    
1264
    result = callpoint.get_user_status(request.user.id)
1265
    if result.is_success:
1266
        backenddata = map(with_class, result.data)
1267
        data = map(pluralize, result.data)
1268
    else:
1269
        data = None
1270
        messages.error(request, result.reason)
1271
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1272
    resource_catalog.update_from_result_report(result)
1273
    
1274

    
1275
    
1276
    return render_response('im/resource_list.html',
1277
                           data=data,
1278
                           context_instance=get_context(request),
1279
                                       resource_catalog=resource_catalog,
1280
                           result=result)
1281

    
1282

    
1283
def group_create_list(request):
1284
    form = PickResourceForm()
1285
    return render_response(
1286
        template='im/astakosgroup_create_list.html',
1287
        context_instance=get_context(request),)
1288

    
1289

    
1290
#@require_http_methods(["GET"])
1291
@require_http_methods(["POST", "GET"])
1292
@signed_terms_required
1293
@login_required
1294
def billing(request):
1295

    
1296
    today = datetime.today()
1297
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1298
    data['resources'] = map(with_class, data['resources'])
1299
    start = request.POST.get('datefrom', None)
1300
    if start:
1301
        today = datetime.fromtimestamp(int(start))
1302
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1303

    
1304
    start = datetime(today.year, today.month, 1).strftime("%s")
1305
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1306
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1307
                                    int(start) * 1000,
1308
                                    int(end) * 1000))
1309
    data = {}
1310

    
1311
    try:
1312
        status, data = r.result
1313
        data = _clear_billing_data(data)
1314
        if status != 200:
1315
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1316
    except:
1317
        messages.error(request, r.result)
1318

    
1319
    print type(start)
1320

    
1321
    return render_response(
1322
        template='im/billing.html',
1323
        context_instance=get_context(request),
1324
        data=data,
1325
        zerodate=datetime(month=1, year=1970, day=1),
1326
        today=today,
1327
        start=int(start),
1328
        month_last_day=month_last_day)
1329

    
1330

    
1331
def _clear_billing_data(data):
1332

    
1333
    # remove addcredits entries
1334
    def isnotcredit(e):
1335
        return e['serviceName'] != "addcredits"
1336

    
1337
    # separate services
1338
    def servicefilter(service_name):
1339
        service = service_name
1340

    
1341
        def fltr(e):
1342
            return e['serviceName'] == service
1343
        return fltr
1344

    
1345
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1346
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1347
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1348
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1349

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

    
1376
    return render_response(template='im/timeline.html',
1377
                           context_instance=get_context(request),
1378
                           form=form,
1379
                           timeline_header=timeline_header,
1380
                           timeline_body=timeline_body)
1381
    return data