Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 8ab484ea

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,
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
    third_party_token = request.REQUEST.get('third_party_token', None)
414

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

    
430
                form.store_user(user, request)
431

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

    
473

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

481
    In case of GET request renders a form for providing the feedback information.
482
    In case of POST sends an email to support team.
483

484
    If the user isn't logged in, redirects to settings.LOGIN_URL.
485

486
    **Arguments**
487

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

492
    ``extra_context``
493
        An dictionary of variables to add to the template context.
494

495
    **Template:**
496

497
    im/signup.html or ``template_name`` keyword argument.
498

499
    **Settings:**
500

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

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

    
525

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

    
553

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

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

    
572
    if user.is_active:
573
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
574
        messages.error(request, message)
575
        return index(request)
576

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

    
595

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

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

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

    
642

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

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

    
696

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

    
699
    if settings.MODERATION_ENABLED:
700
        raise PermissionDenied
701

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

    
723
class ResourcePresentation():
724

    
725
    def __init__(self, data):
726
        self.data = data
727

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

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

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

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

    
750

    
751
            self.data['resources'][rname]['value'] = value
752

    
753

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

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

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

    
769
    def get_group_resources(self, group):
770
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
771

    
772
    def get_groups_resources(self):
773
        for g in self.data['groups']:
774
            yield g, self.get_group_resources(g)
775

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

    
783

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

    
790
    def __repr__(self):
791
        return self.data.__repr__()
792

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

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

    
799
    def get(self, *args, **kwargs):
800
        return self.data.get(*args, **kwargs)
801

    
802

    
803

    
804
@require_http_methods(["GET", "POST"])
805
@signed_terms_required
806
@login_required
807
def group_add(request, kind_name='default'):
808

    
809
    result = callpoint.list_resources()
810
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
811
    resource_catalog.update_from_result(result)
812

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

    
819
    try:
820
        kind = GroupKind.objects.get(name=kind_name)
821
    except:
822
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
823

    
824

    
825

    
826
    post_save_redirect = '/im/group/%(id)s/'
827
    context_processors = None
828
    model, form_class = get_model_and_form_class(
829
        model=None,
830
        form_class=AstakosGroupCreationForm
831
    )
832

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

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

    
854
        form = form_class(data)
855

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

    
869

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

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

    
911

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

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

    
951

    
952

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

    
968

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

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

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

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

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

    
1035
    else:
1036
        form = MembersSortForm({'sort_by': 'person_first_name'})
1037

    
1038
    result = callpoint.list_resources()
1039
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1040
    resource_catalog.update_from_result(result)
1041

    
1042

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

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

    
1065

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

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

    
1130

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

    
1166
    return object_list(
1167
        request,
1168
        q,
1169
        paginate_by=PAGINATE_BY_ALL,
1170
        page=request.GET.get('page') or 1,
1171
        template_name='im/astakosgroup_list.html',
1172
        extra_context=dict(form=AstakosGroupSearchForm(),
1173
                           is_search=True,
1174
                           sorting=sorting))
1175

    
1176

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

    
1197

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

    
1219

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

    
1236

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

    
1257

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

    
1272

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

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

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

    
1306

    
1307

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

    
1314

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

    
1321

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

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

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

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

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

    
1359

    
1360
def _clear_billing_data(data):
1361

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

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

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

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

    
1379
    return data
1380

    
1381

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

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

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

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