Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 513571c3

History | View | Annotate | Download (50.9 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 = request.GET.get('sorting')
1026
    if sorting:
1027
        form = MembersSortForm({'sort_by': sorting})
1028
        if form.is_valid():
1029
            sorting = form.cleaned_data.get('sort_by')
1030

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

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

    
1038

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

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

    
1061

    
1062
@require_http_methods(["GET", "POST"])
1063
@signed_terms_required
1064
@login_required
1065
def group_search(request, extra_context=None, **kwargs):
1066
    q = request.GET.get('q')
1067
    if request.method == 'GET':
1068
        form = AstakosGroupSearchForm({'q': q} if q else None)
1069
    else:
1070
        form = AstakosGroupSearchForm(get_query(request))
1071
        if form.is_valid():
1072
            q = form.cleaned_data['q'].strip()
1073
    
1074
    sorting = 'groupname'
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': "auth_group.name",
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
        
1109
        # validate sorting
1110
        sort_form = AstakosGroupSortForm(request.GET)
1111
        if sort_form.is_valid():
1112
            sorting = sort_form.cleaned_data.get('sorting')
1113
        queryset = queryset.order_by(sorting)
1114

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

    
1128

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

    
1177

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

    
1198

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

    
1220

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

    
1237

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

    
1258

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

    
1273

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

    
1293
    def pluralize(entry):
1294
        entry['plural'] = engine.plural(entry.get('name'))
1295
        return entry
1296

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

    
1307

    
1308

    
1309
    return render_response('im/resource_list.html',
1310
                           data=data,
1311
                           context_instance=get_context(request),
1312
                           resource_catalog=resource_catalog,
1313
                           result=result)
1314

    
1315

    
1316
def group_create_list(request):
1317
    form = PickResourceForm()
1318
    return render_response(
1319
        template='im/astakosgroup_create_list.html',
1320
        context_instance=get_context(request),)
1321

    
1322

    
1323
#@require_http_methods(["GET"])
1324
@require_http_methods(["POST", "GET"])
1325
@signed_terms_required
1326
@login_required
1327
def billing(request):
1328

    
1329
    today = datetime.today()
1330
    month_last_day = calendar.monthrange(today.year, today.month)[1]
1331
    start = request.POST.get('datefrom', None)
1332
    if start:
1333
        today = datetime.fromtimestamp(int(start))
1334
        month_last_day = calendar.monthrange(today.year, today.month)[1]
1335

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

    
1343
    try:
1344
        status, data = r.result
1345
        data = _clear_billing_data(data)
1346
        if status != 200:
1347
            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1348
    except:
1349
        messages.error(request, r.result)
1350

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

    
1360

    
1361
def _clear_billing_data(data):
1362

    
1363
    # remove addcredits entries
1364
    def isnotcredit(e):
1365
        return e['serviceName'] != "addcredits"
1366

    
1367
    # separate services
1368
    def servicefilter(service_name):
1369
        service = service_name
1370

    
1371
        def fltr(e):
1372
            return e['serviceName'] == service
1373
        return fltr
1374

    
1375
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1376
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1377
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1378
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1379

    
1380
    return data
1381

    
1382

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

    
1406
    return render_response(template='im/timeline.html',
1407
                           context_instance=get_context(request),
1408
                           form=form,
1409
                           timeline_header=timeline_header,
1410
                           timeline_body=timeline_body)
1411
    return data
1412

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

    
1423
    if provider.can_remove():
1424
        provider.delete()
1425
        return HttpResponseRedirect(reverse('edit_profile'))
1426
    else:
1427
        raise PermissionDenied
1428