Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 792fad7a

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,
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
DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
97
                                     'https://', '')"""
98

    
99
callpoint = AstakosCallpoint()
100

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

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

    
123
            if not provider or not provider.is_active():
124
                raise PermissionDenied
125

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

    
135

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

    
150

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

    
166

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

173
    **Arguments**
174

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

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

183
    ``extra_context``
184
        An dictionary of variables to add to the template context.
185

186
    **Template:**
187

188
    im/profile.html or im/login.html or ``template_name`` keyword argument.
189

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

    
196
    return render_response(
197
        template_name,
198
        login_form = LoginForm(request=request),
199
        context_instance = get_context(request, extra_context)
200
    )
201

    
202

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

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

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

218
    If the user isn't logged in, redirects to settings.LOGIN_URL.
219

220
    **Arguments**
221

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

226
    ``extra_context``
227
        An dictionary of variables to add to the template context.
228

229
    **Template:**
230

231
    im/invitations.html or ``template_name`` keyword argument.
232

233
    **Settings:**
234

235
    The view expectes the following settings are defined:
236

237
    * LOGIN_URL: login uri
238
    """
239
    extra_context = extra_context or {}
240
    status = None
241
    message = None
242
    form = InvitationForm()
243

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

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

    
281

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

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

293
    If the user isn't logged in, redirects to settings.LOGIN_URL.
294

295
    **Arguments**
296

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

301
    ``extra_context``
302
        An dictionary of variables to add to the template context.
303

304
    **Template:**
305

306
    im/profile.html or ``template_name`` keyword argument.
307

308
    **Settings:**
309

310
    The view expectes the following settings are defined:
311

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

    
348
    # existing providers
349
    user_providers = request.user.get_active_auth_providers()
350

    
351
    # providers that user can add
352
    user_available_providers = request.user.get_available_auth_providers()
353

    
354
    return render_response(template_name,
355
                           profile_form = form,
356
                           user_providers = user_providers,
357
                           user_available_providers = user_available_providers,
358
                           context_instance = get_context(request,
359
                                                          extra_context))
360

    
361

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

368
    In case of GET request renders a form for entering the user information.
369
    In case of POST handles the signup.
370

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

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

379
    On unsuccessful creation, renders ``template_name`` with an error message.
380

381
    **Arguments**
382

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

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

391
    ``extra_context``
392
        An dictionary of variables to add to the template context.
393

394
    **Template:**
395

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

    
403
    provider = get_query(request).get('provider', 'local')
404
    if not auth_providers.get_provider(provider).is_available_for_create():
405
        raise PermissionDenied
406

    
407
    id = get_query(request).get('id')
408
    try:
409
        instance = AstakosUser.objects.get(id=id) if id else None
410
    except AstakosUser.DoesNotExist:
411
        instance = None
412

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

    
428
                form.store_user(user, request)
429

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

    
470

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

478
    In case of GET request renders a form for providing the feedback information.
479
    In case of POST sends an email to support team.
480

481
    If the user isn't logged in, redirects to settings.LOGIN_URL.
482

483
    **Arguments**
484

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

489
    ``extra_context``
490
        An dictionary of variables to add to the template context.
491

492
    **Template:**
493

494
    im/signup.html or ``template_name`` keyword argument.
495

496
    **Settings:**
497

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

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

    
522

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

    
550

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

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

    
569
    if user.is_active:
570
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
571
        messages.error(request, message)
572
        return index(request)
573

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

    
592

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

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

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

    
639

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

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

    
693

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

    
696
    if settings.MODERATION_ENABLED:
697
        raise PermissionDenied
698

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

    
720
class ResourcePresentation():
721

    
722
    def __init__(self, data):
723
        self.data = data
724

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

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

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

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

    
747

    
748
            self.data['resources'][rname]['value'] = value
749

    
750

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

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

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

    
766
    def get_group_resources(self, group):
767
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
768

    
769
    def get_groups_resources(self):
770
        for g in self.data['groups']:
771
            yield g, self.get_group_resources(g)
772

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

    
780

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

    
787
    def __repr__(self):
788
        return self.data.__repr__()
789

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

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

    
796
    def get(self, *args, **kwargs):
797
        return self.data.get(*args, **kwargs)
798

    
799

    
800

    
801
@require_http_methods(["GET", "POST"])
802
@signed_terms_required
803
@login_required
804
def group_add(request, kind_name='default'):
805

    
806
    result = callpoint.list_resources()
807
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
808
    resource_catalog.update_from_result(result)
809

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

    
816
    try:
817
        kind = GroupKind.objects.get(name=kind_name)
818
    except:
819
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
820

    
821

    
822

    
823
    post_save_redirect = '/im/group/%(id)s/'
824
    context_processors = None
825
    model, form_class = get_model_and_form_class(
826
        model=None,
827
        form_class=AstakosGroupCreationForm
828
    )
829

    
830
    if request.method == 'POST':
831
        form = form_class(request.POST, request.FILES)
832
        if form.is_valid():
833
            return render_response(
834
                template='im/astakosgroup_form_summary.html',
835
                context_instance=get_context(request),
836
                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
837
                policies = resource_catalog.get_policies(form.policies()),
838
                resource_catalog= resource_catalog,
839
            )
840

    
841
    else:
842
        now = datetime.now()
843
        data = {
844
            'kind': kind,
845
        }
846
        for group, resources in resource_catalog.get_groups_resources():
847
            data['is_selected_%s' % group] = False
848
            for resource in resources:
849
                data['%s_uplimit' % resource] = ''
850

    
851
        form = form_class(data)
852

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

    
866

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

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

    
908

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

    
942
    if sorting:
943
        query = query+" ORDER BY %s ASC" %sorting
944
    else:
945
        query = query+" ORDER BY groupname ASC"
946
    q = AstakosGroup.objects.raw(query)
947

    
948

    
949

    
950
    # Create the template, context, response
951
    template_name = "%s/%s_list.html" % (
952
        q.model._meta.app_label,
953
        q.model._meta.object_name.lower()
954
    )
955
    extra_context = dict(
956
        is_search=False,
957
        q=q,
958
        sorting=request.GET.get('sorting'),
959
        page=request.GET.get('page', 1)
960
    )
961
    return render_response(template_name,
962
                           context_instance=get_context(request, extra_context)
963
    )
964

    
965

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

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

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

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

    
1025
    # validate sorting
1026
    sorting = request.GET.get('sorting')
1027
    if sorting:
1028
        form = MembersSortForm({'sort_by': sorting})
1029
        if form.is_valid():
1030
            sorting = form.cleaned_data.get('sort_by')
1031

    
1032
    else:
1033
        form = MembersSortForm({'sort_by': 'person_first_name'})
1034

    
1035
    result = callpoint.list_resources()
1036
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1037
    resource_catalog.update_from_result(result)
1038

    
1039

    
1040
    if not result.is_success:
1041
        messages.error(
1042
            request,
1043
            'Unable to retrieve system resources: %s' % result.reason
1044
    )
1045

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

    
1062

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

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

    
1127

    
1128
@require_http_methods(["GET", "POST"])
1129
@signed_terms_required
1130
@login_required
1131
def group_all(request, extra_context=None, **kwargs):
1132
    q = AstakosGroup.objects.select_related()
1133
    q = q.filter(approval_date__isnull=False)
1134
    q = q.extra(select={
1135
                'groupname': DB_REPLACE_GROUP_SCHEME,
1136
                'kindname': "im_groupkind.name",
1137
                'approved_members_num': """
1138
                    SELECT COUNT(*) FROM im_membership
1139
                    WHERE group_id = im_astakosgroup.group_ptr_id
1140
                    AND date_joined IS NOT NULL""",
1141
                'membership_approval_date': """
1142
                    SELECT date_joined FROM im_membership
1143
                    WHERE group_id = im_astakosgroup.group_ptr_id
1144
                    AND person_id = %s""" % request.user.id,
1145
                'is_member': """
1146
                    SELECT CASE WHEN EXISTS(
1147
                    SELECT date_joined FROM im_membership
1148
                    WHERE group_id = im_astakosgroup.group_ptr_id
1149
                    AND person_id = %s)
1150
                    THEN 1 ELSE 0 END""" % request.user.id,
1151
                 'is_owner': """SELECT CASE WHEN EXISTS(
1152
                        SELECT id FROM im_astakosuser_owner
1153
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1154
                        AND astakosuser_id = %s)
1155
                        THEN 1 ELSE 0 END""" % request.user.id,   })
1156
    sorting = request.GET.get('sorting')
1157
    if sorting:
1158
        # TODO check sorting value
1159
        q = q.order_by(sorting)
1160
    else:
1161
        q = q.order_by("groupname")
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 % realname
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