Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (50.4 kB)

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

    
34
import logging
35
import calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

    
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime, 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
            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
 
984
    
985
    result = callpoint.list_resources()
986
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
987
    resource_catalog.update_from_result(result)
988

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

    
1014

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

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

    
1079

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

    
1125

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

    
1146

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

    
1168

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

    
1185

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

    
1206

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

    
1221

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

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

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

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

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

    
1281

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

    
1288

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

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

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

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

    
1318
    print type(start)
1319

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

    
1329

    
1330
def _clear_billing_data(data):
1331

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

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

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

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

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

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