Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 666e1351

History | View | Annotate | Download (50.7 kB)

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

    
34
import logging
35
import calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

    
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime
43

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

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

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

    
90
import astakos.im.messages as astakos_messages
91
from astakos.im import settings
92
from astakos.im import auth_providers
93

    
94
logger = logging.getLogger(__name__)
95

    
96
callpoint = AstakosCallpoint()
97

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

    
112
def requires_auth_provider(provider_id, **perms):
113
    """
114
    """
115
    def decorator(func, *args, **kwargs):
116
        @wraps(func)
117
        def wrapper(request, *args, **kwargs):
118
            provider = auth_providers.get_provider(provider_id)
119

    
120
            if not provider or not provider.is_active():
121
                raise PermissionDenied
122

    
123
            if provider:
124
                for pkey, value in perms.iteritems():
125
                    attr = 'is_available_for_%s' % pkey.lower()
126
                    if getattr(provider, attr)() != value:
127
                        raise PermissionDenied
128
            return func(request, *args)
129
        return wrapper
130
    return decorator
131

    
132

    
133
def requires_anonymous(func):
134
    """
135
    Decorator checkes whether the request.user is not Anonymous and in that case
136
    redirects to `logout`.
137
    """
138
    @wraps(func)
139
    def wrapper(request, *args):
140
        if not request.user.is_anonymous():
141
            next = urlencode({'next': request.build_absolute_uri()})
142
            logout_uri = reverse(logout) + '?' + next
143
            return HttpResponseRedirect(logout_uri)
144
        return func(request, *args)
145
    return wrapper
146

    
147

    
148
def signed_terms_required(func):
149
    """
150
    Decorator checkes whether the request.user is Anonymous and in that case
151
    redirects to `logout`.
152
    """
153
    @wraps(func)
154
    def wrapper(request, *args, **kwargs):
155
        if request.user.is_authenticated() and not request.user.signed_terms:
156
            params = urlencode({'next': request.build_absolute_uri(),
157
                                'show_form': ''})
158
            terms_uri = reverse('latest_terms') + '?' + params
159
            return HttpResponseRedirect(terms_uri)
160
        return func(request, *args, **kwargs)
161
    return wrapper
162

    
163

    
164
@require_http_methods(["GET", "POST"])
165
@signed_terms_required
166
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
167
    """
168
    If there is logged on user renders the profile page otherwise renders login page.
169

170
    **Arguments**
171

172
    ``login_template_name``
173
        A custom login template to use. This is optional; if not specified,
174
        this will default to ``im/login.html``.
175

176
    ``profile_template_name``
177
        A custom profile template to use. This is optional; if not specified,
178
        this will default to ``im/profile.html``.
179

180
    ``extra_context``
181
        An dictionary of variables to add to the template context.
182

183
    **Template:**
184

185
    im/profile.html or im/login.html or ``template_name`` keyword argument.
186

187
    """
188
    extra_context = extra_context or {}
189
    template_name = login_template_name
190
    if request.user.is_authenticated():
191
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
192

    
193
    return render_response(
194
        template_name,
195
        login_form = LoginForm(request=request),
196
        context_instance = get_context(request, extra_context)
197
    )
198

    
199

    
200
@require_http_methods(["GET", "POST"])
201
@login_required
202
@signed_terms_required
203
@transaction.commit_manually
204
def invite(request, template_name='im/invitations.html', extra_context=None):
205
    """
206
    Allows a user to invite somebody else.
207

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

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

215
    If the user isn't logged in, redirects to settings.LOGIN_URL.
216

217
    **Arguments**
218

219
    ``template_name``
220
        A custom template to use. This is optional; if not specified,
221
        this will default to ``im/invitations.html``.
222

223
    ``extra_context``
224
        An dictionary of variables to add to the template context.
225

226
    **Template:**
227

228
    im/invitations.html or ``template_name`` keyword argument.
229

230
    **Settings:**
231

232
    The view expectes the following settings are defined:
233

234
    * LOGIN_URL: login uri
235
    """
236
    extra_context = extra_context or {}
237
    status = None
238
    message = None
239
    form = InvitationForm()
240

    
241
    inviter = request.user
242
    if request.method == 'POST':
243
        form = InvitationForm(request.POST)
244
        if inviter.invitations > 0:
245
            if form.is_valid():
246
                try:
247
                    email = form.cleaned_data.get('username')
248
                    realname = form.cleaned_data.get('realname')
249
                    inviter.invite(email, realname)
250
                    message = _(astakos_messages.INVITATION_SENT) % locals()
251
                    messages.success(request, message)
252
                except SendMailError, e:
253
                    message = e.message
254
                    messages.error(request, message)
255
                    transaction.rollback()
256
                except BaseException, e:
257
                    message = _(astakos_messages.GENERIC_ERROR)
258
                    messages.error(request, message)
259
                    logger.exception(e)
260
                    transaction.rollback()
261
                else:
262
                    transaction.commit()
263
        else:
264
            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
265
            messages.error(request, message)
266

    
267
    sent = [{'email': inv.username,
268
             'realname': inv.realname,
269
             'is_consumed': inv.is_consumed}
270
            for inv in request.user.invitations_sent.all()]
271
    kwargs = {'inviter': inviter,
272
              'sent': sent}
273
    context = get_context(request, extra_context, **kwargs)
274
    return render_response(template_name,
275
                           invitation_form=form,
276
                           context_instance=context)
277

    
278

    
279
@require_http_methods(["GET", "POST"])
280
@login_required
281
@signed_terms_required
282
def edit_profile(request, template_name='im/profile.html', extra_context=None):
283
    """
284
    Allows a user to edit his/her profile.
285

286
    In case of GET request renders a form for displaying the user information.
287
    In case of POST updates the user informantion and redirects to ``next``
288
    url parameter if exists.
289

290
    If the user isn't logged in, redirects to settings.LOGIN_URL.
291

292
    **Arguments**
293

294
    ``template_name``
295
        A custom template to use. This is optional; if not specified,
296
        this will default to ``im/profile.html``.
297

298
    ``extra_context``
299
        An dictionary of variables to add to the template context.
300

301
    **Template:**
302

303
    im/profile.html or ``template_name`` keyword argument.
304

305
    **Settings:**
306

307
    The view expectes the following settings are defined:
308

309
    * LOGIN_URL: login uri
310
    """
311
    extra_context = extra_context or {}
312
    form = ProfileForm(
313
        instance=request.user,
314
        session_key=request.session.session_key
315
    )
316
    extra_context['next'] = request.GET.get('next')
317
    if request.method == 'POST':
318
        form = ProfileForm(
319
            request.POST,
320
            instance=request.user,
321
            session_key=request.session.session_key
322
        )
323
        if form.is_valid():
324
            try:
325
                prev_token = request.user.auth_token
326
                user = form.save()
327
                form = ProfileForm(
328
                    instance=user,
329
                    session_key=request.session.session_key
330
                )
331
                next = restrict_next(
332
                    request.POST.get('next'),
333
                    domain=COOKIE_DOMAIN
334
                )
335
                if next:
336
                    return redirect(next)
337
                msg = _(astakos_messages.PROFILE_UPDATED)
338
                messages.success(request, msg)
339
            except ValueError, ve:
340
                messages.success(request, ve)
341
    elif request.method == "GET":
342
        request.user.is_verified = True
343
        request.user.save()
344

    
345
    # existing providers
346
    user_providers = request.user.get_active_auth_providers()
347

    
348
    # providers that user can add
349
    user_available_providers = request.user.get_available_auth_providers()
350

    
351
    return render_response(template_name,
352
                           profile_form = form,
353
                           user_providers = user_providers,
354
                           user_available_providers = user_available_providers,
355
                           context_instance = get_context(request,
356
                                                          extra_context))
357

    
358

    
359
@transaction.commit_manually
360
@require_http_methods(["GET", "POST"])
361
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
362
    """
363
    Allows a user to create a local account.
364

365
    In case of GET request renders a form for entering the user information.
366
    In case of POST handles the signup.
367

368
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
369
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
370
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
371
    (see activation_backends);
372

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

376
    On unsuccessful creation, renders ``template_name`` with an error message.
377

378
    **Arguments**
379

380
    ``template_name``
381
        A custom template to render. This is optional;
382
        if not specified, this will default to ``im/signup.html``.
383

384
    ``on_success``
385
        A custom template to render in case of success. This is optional;
386
        if not specified, this will default to ``im/signup_complete.html``.
387

388
    ``extra_context``
389
        An dictionary of variables to add to the template context.
390

391
    **Template:**
392

393
    im/signup.html or ``template_name`` keyword argument.
394
    im/signup_complete.html or ``on_success`` keyword argument.
395
    """
396
    extra_context = extra_context or {}
397
    if request.user.is_authenticated():
398
        return HttpResponseRedirect(reverse('edit_profile'))
399

    
400
    provider = get_query(request).get('provider', 'local')
401
    if not auth_providers.get_provider(provider).is_available_for_create():
402
        raise PermissionDenied
403

    
404
    id = get_query(request).get('id')
405
    try:
406
        instance = AstakosUser.objects.get(id=id) if id else None
407
    except AstakosUser.DoesNotExist:
408
        instance = None
409

    
410
    try:
411
        if not backend:
412
            backend = get_backend(request)
413
        form = backend.get_signup_form(provider, instance)
414
    except Exception, e:
415
        form = SimpleBackend(request).get_signup_form(provider)
416
        messages.error(request, e)
417
    if request.method == 'POST':
418
        if form.is_valid():
419
            user = form.save(commit=False)
420
            try:
421
                result = backend.handle_activation(user)
422
                status = messages.SUCCESS
423
                message = result.message
424

    
425
                form.store_user(user, request)
426

    
427
                if 'additional_email' in form.cleaned_data:
428
                    additional_email = form.cleaned_data['additional_email']
429
                    if additional_email != user.email:
430
                        user.additionalmail_set.create(email=additional_email)
431
                        msg = 'Additional email: %s saved for user %s.' % (
432
                            additional_email,
433
                            user.email
434
                        )
435
                        logger._log(LOGGING_LEVEL, msg, [])
436
                if user and user.is_active:
437
                    next = request.POST.get('next', '')
438
                    response = prepare_response(request, user, next=next)
439
                    transaction.commit()
440
                    return response
441
                messages.add_message(request, status, message)
442
                transaction.commit()
443
                return render_response(
444
                    on_success,
445
                    context_instance=get_context(
446
                        request,
447
                        extra_context
448
                    )
449
                )
450
            except SendMailError, e:
451
                logger.exception(e)
452
                status = messages.ERROR
453
                message = e.message
454
                messages.error(request, message)
455
                transaction.rollback()
456
            except BaseException, e:
457
                logger.exception(e)
458
                message = _(astakos_messages.GENERIC_ERROR)
459
                messages.error(request, message)
460
                logger.exception(e)
461
                transaction.rollback()
462
    return render_response(template_name,
463
                           signup_form=form,
464
                           provider=provider,
465
                           context_instance=get_context(request, extra_context))
466

    
467

    
468
@require_http_methods(["GET", "POST"])
469
@login_required
470
@signed_terms_required
471
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
472
    """
473
    Allows a user to send feedback.
474

475
    In case of GET request renders a form for providing the feedback information.
476
    In case of POST sends an email to support team.
477

478
    If the user isn't logged in, redirects to settings.LOGIN_URL.
479

480
    **Arguments**
481

482
    ``template_name``
483
        A custom template to use. This is optional; if not specified,
484
        this will default to ``im/feedback.html``.
485

486
    ``extra_context``
487
        An dictionary of variables to add to the template context.
488

489
    **Template:**
490

491
    im/signup.html or ``template_name`` keyword argument.
492

493
    **Settings:**
494

495
    * LOGIN_URL: login uri
496
    """
497
    extra_context = extra_context or {}
498
    if request.method == 'GET':
499
        form = FeedbackForm()
500
    if request.method == 'POST':
501
        if not request.user:
502
            return HttpResponse('Unauthorized', status=401)
503

    
504
        form = FeedbackForm(request.POST)
505
        if form.is_valid():
506
            msg = form.cleaned_data['feedback_msg']
507
            data = form.cleaned_data['feedback_data']
508
            try:
509
                send_feedback(msg, data, request.user, email_template_name)
510
            except SendMailError, e:
511
                messages.error(request, message)
512
            else:
513
                message = _(astakos_messages.FEEDBACK_SENT)
514
                messages.success(request, message)
515
    return render_response(template_name,
516
                           feedback_form=form,
517
                           context_instance=get_context(request, extra_context))
518

    
519

    
520
@require_http_methods(["GET"])
521
@signed_terms_required
522
def logout(request, template='registration/logged_out.html', extra_context=None):
523
    """
524
    Wraps `django.contrib.auth.logout`.
525
    """
526
    extra_context = extra_context or {}
527
    response = HttpResponse()
528
    if request.user.is_authenticated():
529
        email = request.user.email
530
        auth_logout(request)
531
    next = restrict_next(
532
        request.GET.get('next'),
533
        domain=COOKIE_DOMAIN
534
    )
535
    if next:
536
        response['Location'] = next
537
        response.status_code = 302
538
    elif LOGOUT_NEXT:
539
        response['Location'] = LOGOUT_NEXT
540
        response.status_code = 301
541
    else:
542
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
543
        context = get_context(request, extra_context)
544
        response.write(render_to_string(template, context_instance=context))
545
    return response
546

    
547

    
548
@require_http_methods(["GET", "POST"])
549
@transaction.commit_manually
550
def activate(request, greeting_email_template_name='im/welcome_email.txt',
551
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
552
    """
553
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
554
    and renews the user token.
555

556
    The view uses commit_manually decorator in order to ensure the user state will be updated
557
    only if the email will be send successfully.
558
    """
559
    token = request.GET.get('auth')
560
    next = request.GET.get('next')
561
    try:
562
        user = AstakosUser.objects.get(auth_token=token)
563
    except AstakosUser.DoesNotExist:
564
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
565

    
566
    if user.is_active:
567
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
568
        messages.error(request, message)
569
        return index(request)
570

    
571
    try:
572
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
573
        response = prepare_response(request, user, next, renew=True)
574
        transaction.commit()
575
        return response
576
    except SendMailError, e:
577
        message = e.message
578
        messages.add_message(request, messages.ERROR, message)
579
        transaction.rollback()
580
        return index(request)
581
    except BaseException, e:
582
        status = messages.ERROR
583
        message = _(astakos_messages.GENERIC_ERROR)
584
        messages.add_message(request, messages.ERROR, message)
585
        logger.exception(e)
586
        transaction.rollback()
587
        return index(request)
588

    
589

    
590
@require_http_methods(["GET", "POST"])
591
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
592
    extra_context = extra_context or {}
593
    term = None
594
    terms = None
595
    if not term_id:
596
        try:
597
            term = ApprovalTerms.objects.order_by('-id')[0]
598
        except IndexError:
599
            pass
600
    else:
601
        try:
602
            term = ApprovalTerms.objects.get(id=term_id)
603
        except ApprovalTerms.DoesNotExist, e:
604
            pass
605

    
606
    if not term:
607
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
608
        return HttpResponseRedirect(reverse('index'))
609
    f = open(term.location, 'r')
610
    terms = f.read()
611

    
612
    if request.method == 'POST':
613
        next = restrict_next(
614
            request.POST.get('next'),
615
            domain=COOKIE_DOMAIN
616
        )
617
        if not next:
618
            next = reverse('index')
619
        form = SignApprovalTermsForm(request.POST, instance=request.user)
620
        if not form.is_valid():
621
            return render_response(template_name,
622
                                   terms=terms,
623
                                   approval_terms_form=form,
624
                                   context_instance=get_context(request, extra_context))
625
        user = form.save()
626
        return HttpResponseRedirect(next)
627
    else:
628
        form = None
629
        if request.user.is_authenticated() and not request.user.signed_terms:
630
            form = SignApprovalTermsForm(instance=request.user)
631
        return render_response(template_name,
632
                               terms=terms,
633
                               approval_terms_form=form,
634
                               context_instance=get_context(request, extra_context))
635

    
636

    
637
@require_http_methods(["GET", "POST"])
638
@login_required
639
@signed_terms_required
640
@transaction.commit_manually
641
def change_email(request, activation_key=None,
642
                 email_template_name='registration/email_change_email.txt',
643
                 form_template_name='registration/email_change_form.html',
644
                 confirm_template_name='registration/email_change_done.html',
645
                 extra_context=None):
646
    extra_context = extra_context or {}
647
    if activation_key:
648
        try:
649
            user = EmailChange.objects.change_email(activation_key)
650
            if request.user.is_authenticated() and request.user == user:
651
                msg = _(astakos_messages.EMAIL_CHANGED)
652
                messages.success(request, msg)
653
                auth_logout(request)
654
                response = prepare_response(request, user)
655
                transaction.commit()
656
                return response
657
        except ValueError, e:
658
            messages.error(request, e)
659
        return render_response(confirm_template_name,
660
                               modified_user=user if 'user' in locals(
661
                               ) else None,
662
                               context_instance=get_context(request,
663
                                                            extra_context))
664

    
665
    if not request.user.is_authenticated():
666
        path = quote(request.get_full_path())
667
        url = request.build_absolute_uri(reverse('index'))
668
        return HttpResponseRedirect(url + '?next=' + path)
669
    form = EmailChangeForm(request.POST or None)
670
    if request.method == 'POST' and form.is_valid():
671
        try:
672
            ec = form.save(email_template_name, request)
673
        except SendMailError, e:
674
            msg = e
675
            messages.error(request, msg)
676
            transaction.rollback()
677
        except IntegrityError, e:
678
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
679
            messages.error(request, msg)
680
        else:
681
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
682
            messages.success(request, msg)
683
            transaction.commit()
684
    return render_response(
685
        form_template_name,
686
        form=form,
687
        context_instance=get_context(request, extra_context)
688
    )
689

    
690

    
691
def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
692

    
693
    if settings.MODERATION_ENABLED:
694
        raise PermissionDenied
695

    
696
    extra_context = extra_context or {}
697
    try:
698
        u = AstakosUser.objects.get(id=user_id)
699
    except AstakosUser.DoesNotExist:
700
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
701
    else:
702
        try:
703
            send_activation_func(u)
704
            msg = _(astakos_messages.ACTIVATION_SENT)
705
            messages.success(request, msg)
706
        except SendMailError, e:
707
            messages.error(request, e)
708
    return render_response(
709
        template_name,
710
        login_form = LoginForm(request=request),
711
        context_instance = get_context(
712
            request,
713
            extra_context
714
        )
715
    )
716

    
717
class ResourcePresentation():
718

    
719
    def __init__(self, data):
720
        self.data = data
721

    
722
    def update_from_result(self, result):
723
        if result.is_success:
724
            for r in result.data:
725
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
726
                if not rname in self.data['resources']:
727
                    self.data['resources'][rname] = {}
728

    
729
                self.data['resources'][rname].update(r)
730
                self.data['resources'][rname]['id'] = rname
731
                group = r.get('group')
732
                if not group in self.data['groups']:
733
                    self.data['groups'][group] = {}
734

    
735
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
736

    
737
    def test(self, quota_dict):
738
        for k, v in quota_dict.iteritems():
739
            rname = k
740
            value = v
741
            if not rname in self.data['resources']:
742
                self.data['resources'][rname] = {}
743

    
744

    
745
            self.data['resources'][rname]['value'] = value
746

    
747

    
748
    def update_from_result_report(self, result):
749
        if result.is_success:
750
            for r in result.data:
751
                rname = r.get('name')
752
                if not rname in self.data['resources']:
753
                    self.data['resources'][rname] = {}
754

    
755
                self.data['resources'][rname].update(r)
756
                self.data['resources'][rname]['id'] = rname
757
                group = r.get('group')
758
                if not group in self.data['groups']:
759
                    self.data['groups'][group] = {}
760

    
761
                self.data['groups'][r.get('group')].update({'name': r.get('group')})
762

    
763
    def get_group_resources(self, group):
764
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
765

    
766
    def get_groups_resources(self):
767
        for g in self.data['groups']:
768
            yield g, self.get_group_resources(g)
769

    
770
    def get_quota(self, group_quotas):
771
        for r, v in group_quotas.iteritems():
772
            rname = str(r)
773
            quota = self.data['resources'].get(rname)
774
            quota['value'] = v
775
            yield quota
776

    
777

    
778
    def get_policies(self, policies_data):
779
        for policy in policies_data:
780
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
781
            policy.update(self.data['resources'].get(rname))
782
            yield policy
783

    
784
    def __repr__(self):
785
        return self.data.__repr__()
786

    
787
    def __iter__(self, *args, **kwargs):
788
        return self.data.__iter__(*args, **kwargs)
789

    
790
    def __getitem__(self, *args, **kwargs):
791
        return self.data.__getitem__(*args, **kwargs)
792

    
793
    def get(self, *args, **kwargs):
794
        return self.data.get(*args, **kwargs)
795

    
796

    
797

    
798
@require_http_methods(["GET", "POST"])
799
@signed_terms_required
800
@login_required
801
def group_add(request, kind_name='default'):
802

    
803
    result = callpoint.list_resources()
804
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
805
    resource_catalog.update_from_result(result)
806

    
807
    if not result.is_success:
808
        messages.error(
809
            request,
810
            'Unable to retrieve system resources: %s' % result.reason
811
    )
812

    
813
    try:
814
        kind = GroupKind.objects.get(name=kind_name)
815
    except:
816
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
817

    
818

    
819

    
820
    post_save_redirect = '/im/group/%(id)s/'
821
    context_processors = None
822
    model, form_class = get_model_and_form_class(
823
        model=None,
824
        form_class=AstakosGroupCreationForm
825
    )
826

    
827
    if request.method == 'POST':
828
        form = form_class(request.POST, request.FILES)
829
        if form.is_valid():
830
            policies = form.policies()
831
            return render_response(
832
                template='im/astakosgroup_form_summary.html',
833
                context_instance=get_context(request),
834
                form=AstakosGroupCreationSummaryForm(form.cleaned_data),
835
                policies=resource_catalog.get_policies(policies)
836
            )
837
    else:
838
        now = datetime.now()
839
        data = {
840
            'kind': kind,
841
        }
842
        for group, resources in resource_catalog.get_groups_resources():
843
            data['is_selected_%s' % group] = False
844
            for resource in resources:
845
                data['%s_uplimit' % resource] = ''
846

    
847
        form = form_class(data)
848

    
849
    # Create the template, context, response
850
    template_name = "%s/%s_form.html" % (
851
        model._meta.app_label,
852
        model._meta.object_name.lower()
853
    )
854
    t = template_loader.get_template(template_name)
855
    c = RequestContext(request, {
856
        'form': form,
857
        'kind': kind,
858
        'resource_catalog':resource_catalog,
859
    }, context_processors)
860
    return HttpResponse(t.render(c))
861

    
862

    
863
#@require_http_methods(["POST"])
864
@require_http_methods(["GET", "POST"])
865
@signed_terms_required
866
@login_required
867
def group_add_complete(request):
868
    model = AstakosGroup
869
    form = AstakosGroupCreationSummaryForm(request.POST)
870
    if form.is_valid():
871
        d = form.cleaned_data
872
        d['owners'] = [request.user]
873
        result = callpoint.create_groups((d,)).next()
874
        if result.is_success:
875
            new_object = result.data[0]
876
            msg = _(astakos_messages.OBJECT_CREATED) %\
877
                {"verbose_name": model._meta.verbose_name}
878
            messages.success(request, msg, fail_silently=True)
879

    
880
            # send notification
881
            try:
882
                send_group_creation_notification(
883
                    template_name='im/group_creation_notification.txt',
884
                    dictionary={
885
                        'group': new_object,
886
                        'owner': request.user,
887
                        'policies': d.get('policies', [])
888
                    }
889
                )
890
            except SendNotificationError, e:
891
                messages.error(request, e, fail_silently=True)
892
            post_save_redirect = '/im/group/%(id)s/'
893
            return HttpResponseRedirect(post_save_redirect % new_object)
894
        else:
895
            d = {"verbose_name": model._meta.verbose_name,
896
                 "reason":result.reason}
897
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
898
            messages.error(request, msg, fail_silently=True)
899
    return render_response(
900
        template='im/astakosgroup_form_summary.html',
901
        context_instance=get_context(request),
902
        form=form,
903
        policies=form.cleaned_data.get('policies')
904
    )
905

    
906

    
907
#@require_http_methods(["GET"])
908
@require_http_methods(["GET", "POST"])
909
@signed_terms_required
910
@login_required
911
def group_list(request):
912
    none = request.user.astakos_groups.none()
913
    query = """
914
        SELECT auth_group.id,
915
        auth_group.name AS groupname,
916
        im_groupkind.name AS kindname,
917
        im_astakosgroup.*,
918
        owner.email AS groupowner,
919
        (SELECT COUNT(*) FROM im_membership
920
            WHERE group_id = im_astakosgroup.group_ptr_id
921
            AND date_joined IS NOT NULL) AS approved_members_num,
922
        (SELECT CASE WHEN(
923
                    SELECT date_joined FROM im_membership
924
                    WHERE group_id = im_astakosgroup.group_ptr_id
925
                    AND person_id = %(userid)s) IS NULL
926
                    THEN 0 ELSE 1 END) AS membership_status
927
        FROM im_astakosgroup
928
        INNER JOIN im_membership ON (
929
            im_astakosgroup.group_ptr_id = im_membership.group_id)
930
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
931
        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
932
        LEFT JOIN im_astakosuser_owner ON (
933
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
934
        LEFT JOIN auth_user as owner ON (
935
            im_astakosuser_owner.astakosuser_id = owner.id)
936
        WHERE im_membership.person_id = %(userid)s
937
        """
938
    params = {'userid':request.user.id}
939

    
940
    # validate sorting
941
    sorting = 'groupname'
942
    sort_form = AstakosGroupSortForm(request.GET)
943
    if sort_form.is_valid():
944
        sorting = sort_form.cleaned_data.get('sorting')
945
    query = query+" ORDER BY %s ASC" %sorting
946
    
947
    q = AstakosGroup.objects.raw(query, params=params)
948
    
949
    # Create the template, context, response
950
    template_name = "%s/%s_list.html" % (
951
        q.model._meta.app_label,
952
        q.model._meta.object_name.lower()
953
    )
954
    extra_context = dict(
955
        is_search=False,
956
        q=q,
957
        sorting=sorting,
958
        page=request.GET.get('page', 1)
959
    )
960
    return render_response(template_name,
961
                           context_instance=get_context(request, extra_context)
962
    )
963

    
964

    
965
@require_http_methods(["GET", "POST"])
966
@signed_terms_required
967
@login_required
968
def group_detail(request, group_id):
969
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
970
    q = q.extra(select={
971
        'is_member': """SELECT CASE WHEN EXISTS(
972
                            SELECT id FROM im_membership
973
                            WHERE group_id = im_astakosgroup.group_ptr_id
974
                            AND person_id = %s)
975
                        THEN 1 ELSE 0 END""" % request.user.id,
976
        'is_owner': """SELECT CASE WHEN EXISTS(
977
                        SELECT id FROM im_astakosuser_owner
978
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
979
                        AND astakosuser_id = %s)
980
                        THEN 1 ELSE 0 END""" % request.user.id,
981
        'kindname': """SELECT name FROM im_groupkind
982
                       WHERE id = im_astakosgroup.kind_id"""})
983

    
984
    model = q.model
985
    context_processors = None
986
    mimetype = None
987
    try:
988
        obj = q.get()
989
    except AstakosGroup.DoesNotExist:
990
        raise Http404("No %s found matching the query" % (
991
            model._meta.verbose_name))
992

    
993
    update_form = AstakosGroupUpdateForm(instance=obj)
994
    addmembers_form = AddGroupMembersForm()
995
    if request.method == 'POST':
996
        update_data = {}
997
        addmembers_data = {}
998
        for k, v in request.POST.iteritems():
999
            if k in update_form.fields:
1000
                update_data[k] = v
1001
            if k in addmembers_form.fields:
1002
                addmembers_data[k] = v
1003
        update_data = update_data or None
1004
        addmembers_data = addmembers_data or None
1005
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
1006
        addmembers_form = AddGroupMembersForm(addmembers_data)
1007
        if update_form.is_valid():
1008
            update_form.save()
1009
        if addmembers_form.is_valid():
1010
            try:
1011
                map(obj.approve_member, addmembers_form.valid_users)
1012
            except AssertionError:
1013
                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1014
                messages.error(request, msg)
1015
            addmembers_form = AddGroupMembersForm()
1016

    
1017
    template_name = "%s/%s_detail.html" % (
1018
        model._meta.app_label, model._meta.object_name.lower())
1019
    t = template_loader.get_template(template_name)
1020
    c = RequestContext(request, {
1021
        'object': obj,
1022
    }, context_processors)
1023

    
1024
    # validate sorting
1025
    sorting = 'person__email'
1026
    form = MembersSortForm(request.GET)
1027
    if form.is_valid():
1028
        sorting = form.cleaned_data.get('sorting')
1029
    
1030
    result = callpoint.list_resources()
1031
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1032
    resource_catalog.update_from_result(result)
1033

    
1034

    
1035
    if not result.is_success:
1036
        messages.error(
1037
            request,
1038
            'Unable to retrieve system resources: %s' % result.reason
1039
    )
1040

    
1041
    extra_context = {'update_form': update_form,
1042
                     'addmembers_form': addmembers_form,
1043
                     'page': request.GET.get('page', 1),
1044
                     'sorting': sorting,
1045
                     'resource_catalog':resource_catalog,
1046
                     'quota':resource_catalog.get_quota(obj.quota)}
1047
    for key, value in extra_context.items():
1048
        if callable(value):
1049
            c[key] = value()
1050
        else:
1051
            c[key] = value
1052
    response = HttpResponse(t.render(c), mimetype=mimetype)
1053
    populate_xheaders(
1054
        request, response, model, getattr(obj, obj._meta.pk.name))
1055
    return response
1056

    
1057

    
1058
@require_http_methods(["GET", "POST"])
1059
@signed_terms_required
1060
@login_required
1061
def group_search(request, extra_context=None, **kwargs):
1062
    q = request.GET.get('q')
1063
    if request.method == 'GET':
1064
        form = AstakosGroupSearchForm({'q': q} if q else None)
1065
    else:
1066
        form = AstakosGroupSearchForm(get_query(request))
1067
        if form.is_valid():
1068
            q = form.cleaned_data['q'].strip()
1069
    
1070
    sorting = 'groupname'
1071
    if q:
1072
        queryset = AstakosGroup.objects.select_related()
1073
        queryset = queryset.filter(name__contains=q)
1074
        queryset = queryset.filter(approval_date__isnull=False)
1075
        queryset = queryset.extra(select={
1076
                                  'groupname': "auth_group.name",
1077
                                  'kindname': "im_groupkind.name",
1078
                                  'approved_members_num': """
1079
                    SELECT COUNT(*) FROM im_membership
1080
                    WHERE group_id = im_astakosgroup.group_ptr_id
1081
                    AND date_joined IS NOT NULL""",
1082
                                  'membership_approval_date': """
1083
                    SELECT date_joined FROM im_membership
1084
                    WHERE group_id = im_astakosgroup.group_ptr_id
1085
                    AND person_id = %s""" % request.user.id,
1086
                                  'is_member': """
1087
                    SELECT CASE WHEN EXISTS(
1088
                    SELECT date_joined FROM im_membership
1089
                    WHERE group_id = im_astakosgroup.group_ptr_id
1090
                    AND person_id = %s)
1091
                    THEN 1 ELSE 0 END""" % request.user.id,
1092
                                  'is_owner': """
1093
                    SELECT CASE WHEN EXISTS(
1094
                    SELECT id FROM im_astakosuser_owner
1095
                    WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1096
                    AND astakosuser_id = %s)
1097
                    THEN 1 ELSE 0 END""" % request.user.id,
1098
                    'is_owner': """SELECT CASE WHEN EXISTS(
1099
                        SELECT id FROM im_astakosuser_owner
1100
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1101
                        AND astakosuser_id = %s)
1102
                        THEN 1 ELSE 0 END""" % request.user.id,
1103
                    })
1104
        
1105
        # validate sorting
1106
        sort_form = AstakosGroupSortForm(request.GET)
1107
        if sort_form.is_valid():
1108
            sorting = sort_form.cleaned_data.get('sorting')
1109
        queryset = queryset.order_by(sorting)
1110

    
1111
    else:
1112
        queryset = AstakosGroup.objects.none()
1113
    return object_list(
1114
        request,
1115
        queryset,
1116
        paginate_by=PAGINATE_BY_ALL,
1117
        page=request.GET.get('page') or 1,
1118
        template_name='im/astakosgroup_list.html',
1119
        extra_context=dict(form=form,
1120
                           is_search=True,
1121
                           q=q,
1122
                           sorting=sorting))
1123

    
1124

    
1125
@require_http_methods(["GET", "POST"])
1126
@signed_terms_required
1127
@login_required
1128
def group_all(request, extra_context=None, **kwargs):
1129
    q = AstakosGroup.objects.select_related()
1130
    q = q.filter(approval_date__isnull=False)
1131
    q = q.extra(select={
1132
                'groupname': "auth_group.name",
1133
                'kindname': "im_groupkind.name",
1134
                'approved_members_num': """
1135
                    SELECT COUNT(*) FROM im_membership
1136
                    WHERE group_id = im_astakosgroup.group_ptr_id
1137
                    AND date_joined IS NOT NULL""",
1138
                'membership_approval_date': """
1139
                    SELECT date_joined FROM im_membership
1140
                    WHERE group_id = im_astakosgroup.group_ptr_id
1141
                    AND person_id = %s""" % request.user.id,
1142
                'is_member': """
1143
                    SELECT CASE WHEN EXISTS(
1144
                    SELECT date_joined FROM im_membership
1145
                    WHERE group_id = im_astakosgroup.group_ptr_id
1146
                    AND person_id = %s)
1147
                    THEN 1 ELSE 0 END""" % request.user.id,
1148
                 'is_owner': """SELECT CASE WHEN EXISTS(
1149
                        SELECT id FROM im_astakosuser_owner
1150
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1151
                        AND astakosuser_id = %s)
1152
                        THEN 1 ELSE 0 END""" % request.user.id,   })
1153
    
1154
    # validate sorting
1155
    sorting = 'groupname'
1156
    print '>>>', sorting, request.GET
1157
    sort_form = AstakosGroupSortForm(request.GET)
1158
    if sort_form.is_valid():
1159
        sorting = sort_form.cleaned_data.get('sorting')
1160
    print '<<<', sorting
1161
    q = q.order_by(sorting)
1162
    
1163
    return object_list(
1164
        request,
1165
        q,
1166
        paginate_by=PAGINATE_BY_ALL,
1167
        page=request.GET.get('page') or 1,
1168
        template_name='im/astakosgroup_list.html',
1169
        extra_context=dict(form=AstakosGroupSearchForm(),
1170
                           is_search=True,
1171
                           sorting=sorting))
1172

    
1173

    
1174
#@require_http_methods(["POST"])
1175
@require_http_methods(["POST", "GET"])
1176
@signed_terms_required
1177
@login_required
1178
def group_join(request, group_id):
1179
    m = Membership(group_id=group_id,
1180
                   person=request.user,
1181
                   date_requested=datetime.now())
1182
    try:
1183
        m.save()
1184
        post_save_redirect = reverse(
1185
            'group_detail',
1186
            kwargs=dict(group_id=group_id))
1187
        return HttpResponseRedirect(post_save_redirect)
1188
    except IntegrityError, e:
1189
        logger.exception(e)
1190
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1191
        messages.error(request, msg)
1192
        return group_search(request)
1193

    
1194

    
1195
@require_http_methods(["POST"])
1196
@signed_terms_required
1197
@login_required
1198
def group_leave(request, group_id):
1199
    try:
1200
        m = Membership.objects.select_related().get(
1201
            group__id=group_id,
1202
            person=request.user)
1203
    except Membership.DoesNotExist:
1204
        return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1205
    if request.user in m.group.owner.all():
1206
        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1207
    return delete_object(
1208
        request,
1209
        model=Membership,
1210
        object_id=m.id,
1211
        template_name='im/astakosgroup_list.html',
1212
        post_delete_redirect=reverse(
1213
            'group_detail',
1214
            kwargs=dict(group_id=group_id)))
1215

    
1216

    
1217
def handle_membership(func):
1218
    @wraps(func)
1219
    def wrapper(request, group_id, user_id):
1220
        try:
1221
            m = Membership.objects.select_related().get(
1222
                group__id=group_id,
1223
                person__id=user_id)
1224
        except Membership.DoesNotExist:
1225
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1226
        else:
1227
            if request.user not in m.group.owner.all():
1228
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1229
            func(request, m)
1230
            return group_detail(request, group_id)
1231
    return wrapper
1232

    
1233

    
1234
#@require_http_methods(["POST"])
1235
@require_http_methods(["POST", "GET"])
1236
@signed_terms_required
1237
@login_required
1238
@handle_membership
1239
def approve_member(request, membership):
1240
    try:
1241
        membership.approve()
1242
        realname = membership.person.realname
1243
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1244
        messages.success(request, msg)
1245
    except AssertionError:
1246
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1247
        messages.error(request, msg)
1248
    except BaseException, e:
1249
        logger.exception(e)
1250
        realname = membership.person.realname
1251
        msg = _(astakos_messages.GENERIC_ERROR)
1252
        messages.error(request, msg)
1253

    
1254

    
1255
@signed_terms_required
1256
@login_required
1257
@handle_membership
1258
def disapprove_member(request, membership):
1259
    try:
1260
        membership.disapprove()
1261
        realname = membership.person.realname
1262
        msg = astakos_messages.MEMBER_REMOVED % locals()
1263
        messages.success(request, msg)
1264
    except BaseException, e:
1265
        logger.exception(e)
1266
        msg = _(astakos_messages.GENERIC_ERROR)
1267
        messages.error(request, msg)
1268

    
1269

    
1270
#@require_http_methods(["GET"])
1271
@require_http_methods(["POST", "GET"])
1272
@signed_terms_required
1273
@login_required
1274
def resource_list(request):
1275
    def with_class(entry):
1276
        entry['load_class'] = 'red'
1277
        max_value = float(entry['maxValue'])
1278
        curr_value = float(entry['currValue'])
1279
        if max_value > 0 :
1280
            entry['ratio'] = (curr_value / max_value) * 100
1281
        else:
1282
            entry['ratio'] = 0
1283
        if entry['ratio'] < 66:
1284
            entry['load_class'] = 'yellow'
1285
        if entry['ratio'] < 33:
1286
            entry['load_class'] = 'green'
1287
        return entry
1288

    
1289
    def pluralize(entry):
1290
        entry['plural'] = engine.plural(entry.get('name'))
1291
        return entry
1292

    
1293
    result = callpoint.get_user_status(request.user.id)
1294
    if result.is_success:
1295
        backenddata = map(with_class, result.data)
1296
        data = map(pluralize, result.data)
1297
    else:
1298
        data = None
1299
        messages.error(request, result.reason)
1300
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1301
    resource_catalog.update_from_result_report(result)
1302

    
1303

    
1304

    
1305
    return render_response('im/resource_list.html',
1306
                           data=data,
1307
                           context_instance=get_context(request),
1308
                           resource_catalog=resource_catalog,
1309
                           result=result)
1310

    
1311

    
1312
def group_create_list(request):
1313
    form = PickResourceForm()
1314
    return render_response(
1315
        template='im/astakosgroup_create_list.html',
1316
        context_instance=get_context(request),)
1317

    
1318

    
1319
#@require_http_methods(["GET"])
1320
@require_http_methods(["POST", "GET"])
1321
@signed_terms_required
1322
@login_required
1323
def billing(request):
1324

    
1325
    today = datetime.today()
1326
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1327
    start = request.POST.get('datefrom', None)
1328
    if start:
1329
        today = datetime.fromtimestamp(int(start))
1330
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1331

    
1332
    start = datetime(today.year, today.month, 1).strftime("%s")
1333
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1334
    r = request_billing.apply(args=('pgerakios@grnet.gr',
1335
                                    int(start) * 1000,
1336
                                    int(end) * 1000))
1337
    data = {}
1338

    
1339
    try:
1340
        status, data = r.result
1341
        data = _clear_billing_data(data)
1342
        if status != 200:
1343
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1344
    except:
1345
        messages.error(request, r.result)
1346

    
1347
    return render_response(
1348
        template='im/billing.html',
1349
        context_instance=get_context(request),
1350
        data=data,
1351
        zerodate=datetime(month=1, year=1970, day=1),
1352
        today=today,
1353
        start=int(start),
1354
        month_last_day=month_last_day)
1355

    
1356

    
1357
def _clear_billing_data(data):
1358

    
1359
    # remove addcredits entries
1360
    def isnotcredit(e):
1361
        return e['serviceName'] != "addcredits"
1362

    
1363
    # separate services
1364
    def servicefilter(service_name):
1365
        service = service_name
1366

    
1367
        def fltr(e):
1368
            return e['serviceName'] == service
1369
        return fltr
1370

    
1371
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1372
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1373
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1374
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1375

    
1376
    return data
1377

    
1378

    
1379
#@require_http_methods(["GET"])
1380
@require_http_methods(["POST", "GET"])
1381
@signed_terms_required
1382
@login_required
1383
def timeline(request):
1384
#    data = {'entity':request.user.email}
1385
    timeline_body = ()
1386
    timeline_header = ()
1387
#    form = TimelineForm(data)
1388
    form = TimelineForm()
1389
    if request.method == 'POST':
1390
        data = request.POST
1391
        form = TimelineForm(data)
1392
        if form.is_valid():
1393
            data = form.cleaned_data
1394
            timeline_header = ('entity', 'resource',
1395
                               'event name', 'event date',
1396
                               'incremental cost', 'total cost')
1397
            timeline_body = timeline_charge(
1398
                data['entity'], data['resource'],
1399
                data['start_date'], data['end_date'],
1400
                data['details'], data['operation'])
1401

    
1402
    return render_response(template='im/timeline.html',
1403
                           context_instance=get_context(request),
1404
                           form=form,
1405
                           timeline_header=timeline_header,
1406
                           timeline_body=timeline_body)
1407
    return data
1408

    
1409
# TODO: action only on POST and user should confirm the removal
1410
@require_http_methods(["GET", "POST"])
1411
@login_required
1412
@signed_terms_required
1413
def remove_auth_provider(request, pk):
1414
    try:
1415
        provider = request.user.auth_providers.get(pk=pk)
1416
    except AstakosUserAuthProvider.DoesNotExist:
1417
        raise Http404
1418

    
1419
    if provider.can_remove():
1420
        provider.delete()
1421
        return HttpResponseRedirect(reverse('edit_profile'))
1422
    else:
1423
        raise PermissionDenied
1424