Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (52.3 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.shortcuts import get_object_or_404
45
from django.contrib import messages
46
from django.contrib.auth.decorators import login_required
47
from django.core.urlresolvers import reverse
48
from django.db import transaction
49
from django.db.utils import IntegrityError
50
from django.http import (HttpResponse, HttpResponseBadRequest,
51
                         HttpResponseForbidden, HttpResponseRedirect,
52
                         HttpResponseBadRequest, Http404)
53
from django.shortcuts import redirect
54
from django.template import RequestContext, loader as template_loader
55
from django.utils.http import urlencode
56
from django.utils.translation import ugettext as _
57
from django.views.generic.create_update import (delete_object,
58
                                                get_model_and_form_class)
59
from django.views.generic.list_detail import object_list
60
from django.core.xheaders import populate_xheaders
61
from django.core.exceptions import ValidationError, PermissionDenied
62
from django.template.loader import render_to_string
63
from django.views.decorators.http import require_http_methods
64
from django.db.models import Q
65

    
66
import astakos.im.messages as astakos_messages
67

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

    
94
from astakos.im import settings
95
from astakos.im import auth_providers
96

    
97
logger = logging.getLogger(__name__)
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
                        msg = provider.get_message("NOT_ACTIVE_FOR_" + pkey.upper())
131
                        messages.error(request, msg)
132
                        return HttpResponseRedirect(reverse('login'))
133
            return func(request, *args)
134
        return wrapper
135
    return decorator
136

    
137

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

    
152

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

    
168

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

175
    **Arguments**
176

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

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

185
    ``extra_context``
186
        An dictionary of variables to add to the template context.
187

188
    **Template:**
189

190
    im/profile.html or im/login.html or ``template_name`` keyword argument.
191

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

    
198
    third_party_token = request.GET.get('key', False)
199
    if third_party_token:
200
        messages.info(request, astakos_messages.AUTH_PROVIDER_LOGIN_TO_ADD)
201

    
202
    return render_response(
203
        template_name,
204
        login_form = LoginForm(request=request),
205
        context_instance = get_context(request, extra_context)
206
    )
207

    
208

    
209
@require_http_methods(["GET", "POST"])
210
@login_required
211
@signed_terms_required
212
@transaction.commit_manually
213
def invite(request, template_name='im/invitations.html', extra_context=None):
214
    """
215
    Allows a user to invite somebody else.
216

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

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

224
    If the user isn't logged in, redirects to settings.LOGIN_URL.
225

226
    **Arguments**
227

228
    ``template_name``
229
        A custom template to use. This is optional; if not specified,
230
        this will default to ``im/invitations.html``.
231

232
    ``extra_context``
233
        An dictionary of variables to add to the template context.
234

235
    **Template:**
236

237
    im/invitations.html or ``template_name`` keyword argument.
238

239
    **Settings:**
240

241
    The view expectes the following settings are defined:
242

243
    * LOGIN_URL: login uri
244
    """
245
    extra_context = extra_context or {}
246
    status = None
247
    message = None
248
    form = InvitationForm()
249

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

    
276
    sent = [{'email': inv.username,
277
             'realname': inv.realname,
278
             'is_consumed': inv.is_consumed}
279
            for inv in request.user.invitations_sent.all()]
280
    kwargs = {'inviter': inviter,
281
              'sent': sent}
282
    context = get_context(request, extra_context, **kwargs)
283
    return render_response(template_name,
284
                           invitation_form=form,
285
                           context_instance=context)
286

    
287

    
288
@require_http_methods(["GET", "POST"])
289
@login_required
290
@signed_terms_required
291
def edit_profile(request, template_name='im/profile.html', extra_context=None):
292
    """
293
    Allows a user to edit his/her profile.
294

295
    In case of GET request renders a form for displaying the user information.
296
    In case of POST updates the user informantion and redirects to ``next``
297
    url parameter if exists.
298

299
    If the user isn't logged in, redirects to settings.LOGIN_URL.
300

301
    **Arguments**
302

303
    ``template_name``
304
        A custom template to use. This is optional; if not specified,
305
        this will default to ``im/profile.html``.
306

307
    ``extra_context``
308
        An dictionary of variables to add to the template context.
309

310
    **Template:**
311

312
    im/profile.html or ``template_name`` keyword argument.
313

314
    **Settings:**
315

316
    The view expectes the following settings are defined:
317

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

    
354
    # existing providers
355
    user_providers = request.user.get_active_auth_providers()
356

    
357
    # providers that user can add
358
    user_available_providers = request.user.get_available_auth_providers()
359

    
360
    return render_response(template_name,
361
                           profile_form = form,
362
                           user_providers = user_providers,
363
                           user_available_providers = user_available_providers,
364
                           context_instance = get_context(request,
365
                                                          extra_context))
366

    
367

    
368
@transaction.commit_manually
369
@require_http_methods(["GET", "POST"])
370
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
371
    """
372
    Allows a user to create a local account.
373

374
    In case of GET request renders a form for entering the user information.
375
    In case of POST handles the signup.
376

377
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
378
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
379
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
380
    (see activation_backends);
381

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

385
    On unsuccessful creation, renders ``template_name`` with an error message.
386

387
    **Arguments**
388

389
    ``template_name``
390
        A custom template to render. This is optional;
391
        if not specified, this will default to ``im/signup.html``.
392

393
    ``on_success``
394
        A custom template to render in case of success. This is optional;
395
        if not specified, this will default to ``im/signup_complete.html``.
396

397
    ``extra_context``
398
        An dictionary of variables to add to the template context.
399

400
    **Template:**
401

402
    im/signup.html or ``template_name`` keyword argument.
403
    im/signup_complete.html or ``on_success`` keyword argument.
404
    """
405
    extra_context = extra_context or {}
406
    if request.user.is_authenticated():
407
        return HttpResponseRedirect(reverse('edit_profile'))
408

    
409
    provider = get_query(request).get('provider', 'local')
410
    if not auth_providers.get_provider(provider).is_available_for_create():
411
        raise PermissionDenied
412

    
413
    id = get_query(request).get('id')
414
    try:
415
        instance = AstakosUser.objects.get(id=id) if id else None
416
    except AstakosUser.DoesNotExist:
417
        instance = None
418

    
419
    third_party_token = request.REQUEST.get('third_party_token', None)
420
    if third_party_token:
421
        pending = get_object_or_404(PendingThirdPartyUser,
422
                                    token=third_party_token)
423
        provider = pending.provider
424
        instance = pending.get_user_instance()
425

    
426
    try:
427
        if not backend:
428
            backend = get_backend(request)
429
        form = backend.get_signup_form(provider, instance)
430
    except Exception, e:
431
        form = SimpleBackend(request).get_signup_form(provider)
432
        messages.error(request, e)
433
    if request.method == 'POST':
434
        if form.is_valid():
435
            user = form.save(commit=False)
436
            try:
437
                result = backend.handle_activation(user)
438
                status = messages.SUCCESS
439
                message = result.message
440

    
441
                form.store_user(user, request)
442

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

    
484

    
485
@require_http_methods(["GET", "POST"])
486
@login_required
487
@signed_terms_required
488
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
489
    """
490
    Allows a user to send feedback.
491

492
    In case of GET request renders a form for providing the feedback information.
493
    In case of POST sends an email to support team.
494

495
    If the user isn't logged in, redirects to settings.LOGIN_URL.
496

497
    **Arguments**
498

499
    ``template_name``
500
        A custom template to use. This is optional; if not specified,
501
        this will default to ``im/feedback.html``.
502

503
    ``extra_context``
504
        An dictionary of variables to add to the template context.
505

506
    **Template:**
507

508
    im/signup.html or ``template_name`` keyword argument.
509

510
    **Settings:**
511

512
    * LOGIN_URL: login uri
513
    """
514
    extra_context = extra_context or {}
515
    if request.method == 'GET':
516
        form = FeedbackForm()
517
    if request.method == 'POST':
518
        if not request.user:
519
            return HttpResponse('Unauthorized', status=401)
520

    
521
        form = FeedbackForm(request.POST)
522
        if form.is_valid():
523
            msg = form.cleaned_data['feedback_msg']
524
            data = form.cleaned_data['feedback_data']
525
            try:
526
                send_feedback(msg, data, request.user, email_template_name)
527
            except SendMailError, e:
528
                messages.error(request, message)
529
            else:
530
                message = _(astakos_messages.FEEDBACK_SENT)
531
                messages.success(request, message)
532
    return render_response(template_name,
533
                           feedback_form=form,
534
                           context_instance=get_context(request, extra_context))
535

    
536

    
537
@require_http_methods(["GET"])
538
@signed_terms_required
539
def logout(request, template='registration/logged_out.html', extra_context=None):
540
    """
541
    Wraps `django.contrib.auth.logout`.
542
    """
543
    extra_context = extra_context or {}
544
    response = HttpResponse()
545
    if request.user.is_authenticated():
546
        email = request.user.email
547
        auth_logout(request)
548
    else:
549
        response['Location'] = reverse('index')
550
        response.status_code = 301
551
        return response
552

    
553
    next = restrict_next(
554
        request.GET.get('next'),
555
        domain=COOKIE_DOMAIN
556
    )
557

    
558
    if next:
559
        response['Location'] = next
560
        response.status_code = 302
561
    elif LOGOUT_NEXT:
562
        response['Location'] = LOGOUT_NEXT
563
        response.status_code = 301
564
    else:
565
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
566
        response['Location'] = reverse('index')
567
        response.status_code = 301
568
    return response
569

    
570

    
571
@require_http_methods(["GET", "POST"])
572
@transaction.commit_manually
573
def activate(request, greeting_email_template_name='im/welcome_email.txt',
574
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
575
    """
576
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
577
    and renews the user token.
578

579
    The view uses commit_manually decorator in order to ensure the user state will be updated
580
    only if the email will be send successfully.
581
    """
582
    token = request.GET.get('auth')
583
    next = request.GET.get('next')
584
    try:
585
        user = AstakosUser.objects.get(auth_token=token)
586
    except AstakosUser.DoesNotExist:
587
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
588

    
589
    if user.is_active:
590
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
591
        messages.error(request, message)
592
        return index(request)
593

    
594
    try:
595
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
596
        response = prepare_response(request, user, next, renew=True)
597
        transaction.commit()
598
        return response
599
    except SendMailError, e:
600
        message = e.message
601
        messages.add_message(request, messages.ERROR, message)
602
        transaction.rollback()
603
        return index(request)
604
    except BaseException, e:
605
        status = messages.ERROR
606
        message = _(astakos_messages.GENERIC_ERROR)
607
        messages.add_message(request, messages.ERROR, message)
608
        logger.exception(e)
609
        transaction.rollback()
610
        return index(request)
611

    
612

    
613
@require_http_methods(["GET", "POST"])
614
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
615
    extra_context = extra_context or {}
616
    term = None
617
    terms = None
618
    if not term_id:
619
        try:
620
            term = ApprovalTerms.objects.order_by('-id')[0]
621
        except IndexError:
622
            pass
623
    else:
624
        try:
625
            term = ApprovalTerms.objects.get(id=term_id)
626
        except ApprovalTerms.DoesNotExist, e:
627
            pass
628

    
629
    if not term:
630
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
631
        return HttpResponseRedirect(reverse('index'))
632
    f = open(term.location, 'r')
633
    terms = f.read()
634

    
635
    if request.method == 'POST':
636
        next = restrict_next(
637
            request.POST.get('next'),
638
            domain=COOKIE_DOMAIN
639
        )
640
        if not next:
641
            next = reverse('index')
642
        form = SignApprovalTermsForm(request.POST, instance=request.user)
643
        if not form.is_valid():
644
            return render_response(template_name,
645
                                   terms=terms,
646
                                   approval_terms_form=form,
647
                                   context_instance=get_context(request, extra_context))
648
        user = form.save()
649
        return HttpResponseRedirect(next)
650
    else:
651
        form = None
652
        if request.user.is_authenticated() and not request.user.signed_terms:
653
            form = SignApprovalTermsForm(instance=request.user)
654
        return render_response(template_name,
655
                               terms=terms,
656
                               approval_terms_form=form,
657
                               context_instance=get_context(request, extra_context))
658

    
659

    
660
@require_http_methods(["GET", "POST"])
661
@login_required
662
@signed_terms_required
663
@transaction.commit_manually
664
def change_email(request, activation_key=None,
665
                 email_template_name='registration/email_change_email.txt',
666
                 form_template_name='registration/email_change_form.html',
667
                 confirm_template_name='registration/email_change_done.html',
668
                 extra_context=None):
669
    extra_context = extra_context or {}
670
    if activation_key:
671
        try:
672
            user = EmailChange.objects.change_email(activation_key)
673
            if request.user.is_authenticated() and request.user == user:
674
                msg = _(astakos_messages.EMAIL_CHANGED)
675
                messages.success(request, msg)
676
                auth_logout(request)
677
                response = prepare_response(request, user)
678
                transaction.commit()
679
                return response
680
        except ValueError, e:
681
            messages.error(request, e)
682
        return render_response(confirm_template_name,
683
                               modified_user=user if 'user' in locals(
684
                               ) else None,
685
                               context_instance=get_context(request,
686
                                                            extra_context))
687

    
688
    if not request.user.is_authenticated():
689
        path = quote(request.get_full_path())
690
        url = request.build_absolute_uri(reverse('index'))
691
        return HttpResponseRedirect(url + '?next=' + path)
692
    form = EmailChangeForm(request.POST or None)
693
    if request.method == 'POST' and form.is_valid():
694
        try:
695
            ec = form.save(email_template_name, request)
696
        except SendMailError, e:
697
            msg = e
698
            messages.error(request, msg)
699
            transaction.rollback()
700
        except IntegrityError, e:
701
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
702
            messages.error(request, msg)
703
        else:
704
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
705
            messages.success(request, msg)
706
            transaction.commit()
707
    return render_response(
708
        form_template_name,
709
        form=form,
710
        context_instance=get_context(request, extra_context)
711
    )
712

    
713

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

    
716
    if settings.MODERATION_ENABLED:
717
        raise PermissionDenied
718

    
719
    extra_context = extra_context or {}
720
    try:
721
        u = AstakosUser.objects.get(id=user_id)
722
    except AstakosUser.DoesNotExist:
723
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
724
    else:
725
        try:
726
            send_activation_func(u)
727
            msg = _(astakos_messages.ACTIVATION_SENT)
728
            messages.success(request, msg)
729
        except SendMailError, e:
730
            messages.error(request, e)
731
    return render_response(
732
        template_name,
733
        login_form = LoginForm(request=request),
734
        context_instance = get_context(
735
            request,
736
            extra_context
737
        )
738
    )
739

    
740
class ResourcePresentation():
741

    
742
    def __init__(self, data):
743
        self.data = data
744

    
745
    def update_from_result(self, result):
746
        if result.is_success:
747
            for r in result.data:
748
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
749
                if not rname in self.data['resources']:
750
                    self.data['resources'][rname] = {}
751

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

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

    
760
    def test(self, quota_dict):
761
        for k, v in quota_dict.iteritems():
762
            rname = k
763
            value = v
764
            if not rname in self.data['resources']:
765
                self.data['resources'][rname] = {}
766

    
767

    
768
            self.data['resources'][rname]['value'] = value
769

    
770

    
771
    def update_from_result_report(self, result):
772
        if result.is_success:
773
            for r in result.data:
774
                rname = r.get('name')
775
                if not rname in self.data['resources']:
776
                    self.data['resources'][rname] = {}
777

    
778
                self.data['resources'][rname].update(r)
779
                self.data['resources'][rname]['id'] = rname
780
                group = r.get('group')
781
                if not group in self.data['groups']:
782
                    self.data['groups'][group] = {}
783

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

    
786
    def get_group_resources(self, group):
787
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
788

    
789
    def get_groups_resources(self):
790
        for g in self.data['groups']:
791
            yield g, self.get_group_resources(g)
792

    
793
    def get_quota(self, group_quotas):
794
        for r, v in group_quotas.iteritems():
795
            rname = str(r)
796
            quota = self.data['resources'].get(rname)
797
            quota['value'] = v
798
            yield quota
799

    
800

    
801
    def get_policies(self, policies_data):
802
        for policy in policies_data:
803
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
804
            policy.update(self.data['resources'].get(rname))
805
            yield policy
806

    
807
    def __repr__(self):
808
        return self.data.__repr__()
809

    
810
    def __iter__(self, *args, **kwargs):
811
        return self.data.__iter__(*args, **kwargs)
812

    
813
    def __getitem__(self, *args, **kwargs):
814
        return self.data.__getitem__(*args, **kwargs)
815

    
816
    def get(self, *args, **kwargs):
817
        return self.data.get(*args, **kwargs)
818

    
819

    
820

    
821
@require_http_methods(["GET", "POST"])
822
@signed_terms_required
823
@login_required
824
def group_add(request, kind_name='default'):
825

    
826
    result = callpoint.list_resources()
827
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
828
    resource_catalog.update_from_result(result)
829

    
830
    if not result.is_success:
831
        messages.error(
832
            request,
833
            'Unable to retrieve system resources: %s' % result.reason
834
    )
835

    
836
    try:
837
        kind = GroupKind.objects.get(name=kind_name)
838
    except:
839
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
840

    
841

    
842

    
843
    post_save_redirect = '/im/group/%(id)s/'
844
    context_processors = None
845
    model, form_class = get_model_and_form_class(
846
        model=None,
847
        form_class=AstakosGroupCreationForm
848
    )
849

    
850
    if request.method == 'POST':
851
        form = form_class(request.POST, request.FILES)
852
        if form.is_valid():
853
            policies = form.policies()
854
            return render_response(
855
                template='im/astakosgroup_form_summary.html',
856
                context_instance=get_context(request),
857
                form=AstakosGroupCreationSummaryForm(form.cleaned_data),
858
                policies=resource_catalog.get_policies(policies)
859
            )
860
    else:
861
        now = datetime.now()
862
        data = {
863
            'kind': kind,
864
        }
865
        for group, resources in resource_catalog.get_groups_resources():
866
            data['is_selected_%s' % group] = False
867
            for resource in resources:
868
                data['%s_uplimit' % resource] = ''
869

    
870
        form = form_class(data)
871

    
872
    # Create the template, context, response
873
    template_name = "%s/%s_form.html" % (
874
        model._meta.app_label,
875
        model._meta.object_name.lower()
876
    )
877
    t = template_loader.get_template(template_name)
878
    c = RequestContext(request, {
879
        'form': form,
880
        'kind': kind,
881
        'resource_catalog':resource_catalog,
882
    }, context_processors)
883
    return HttpResponse(t.render(c))
884

    
885

    
886
#@require_http_methods(["POST"])
887
@require_http_methods(["GET", "POST"])
888
@signed_terms_required
889
@login_required
890
def group_add_complete(request):
891
    model = AstakosGroup
892
    form = AstakosGroupCreationSummaryForm(request.POST)
893
    if form.is_valid():
894
        d = form.cleaned_data
895
        d['owners'] = [request.user]
896
        result = callpoint.create_groups((d,)).next()
897
        if result.is_success:
898
            new_object = result.data[0]
899
            msg = _(astakos_messages.OBJECT_CREATED) %\
900
                {"verbose_name": model._meta.verbose_name}
901
            messages.success(request, msg, fail_silently=True)
902

    
903
            # send notification
904
            try:
905
                send_group_creation_notification(
906
                    template_name='im/group_creation_notification.txt',
907
                    dictionary={
908
                        'group': new_object,
909
                        'owner': request.user,
910
                        'policies': d.get('policies', [])
911
                    }
912
                )
913
            except SendNotificationError, e:
914
                messages.error(request, e, fail_silently=True)
915
            post_save_redirect = '/im/group/%(id)s/'
916
            return HttpResponseRedirect(post_save_redirect % new_object)
917
        else:
918
            d = {"verbose_name": model._meta.verbose_name,
919
                 "reason":result.reason}
920
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
921
            messages.error(request, msg, fail_silently=True)
922
    return render_response(
923
        template='im/astakosgroup_form_summary.html',
924
        context_instance=get_context(request),
925
        form=form,
926
        policies=form.cleaned_data.get('policies')
927
    )
928

    
929

    
930
#@require_http_methods(["GET"])
931
@require_http_methods(["GET", "POST"])
932
@signed_terms_required
933
@login_required
934
def group_list(request):
935
    none = request.user.astakos_groups.none()
936
    query = """
937
        SELECT auth_group.id,
938
        auth_group.name AS groupname,
939
        im_groupkind.name AS kindname,
940
        im_astakosgroup.*,
941
        owner.email AS groupowner,
942
        (SELECT COUNT(*) FROM im_membership
943
            WHERE group_id = im_astakosgroup.group_ptr_id
944
            AND date_joined IS NOT NULL) AS approved_members_num,
945
        (SELECT CASE WHEN(
946
                    SELECT date_joined FROM im_membership
947
                    WHERE group_id = im_astakosgroup.group_ptr_id
948
                    AND person_id = %(id)s) IS NULL
949
                    THEN 0 ELSE 1 END) AS membership_status
950
        FROM im_astakosgroup
951
        INNER JOIN im_membership ON (
952
            im_astakosgroup.group_ptr_id = im_membership.group_id)
953
        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
954
        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
955
        LEFT JOIN im_astakosuser_owner ON (
956
            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
957
        LEFT JOIN auth_user as owner ON (
958
            im_astakosuser_owner.astakosuser_id = owner.id)
959
        WHERE im_membership.person_id = %(id)s
960
        AND im_groupkind.name != 'default'
961
        """ % request.user.__dict__
962

    
963
    # validate sorting
964
    sorting = 'groupname'
965
    sort_form = AstakosGroupSortForm(request.GET)
966
    if sort_form.is_valid():
967
        sorting = sort_form.cleaned_data.get('sorting')
968
    query = query+" ORDER BY %s ASC" %sorting
969

    
970
    q = AstakosGroup.objects.raw(query)
971

    
972
    # Create the template, context, response
973
    template_name = "%s/%s_list.html" % (
974
        q.model._meta.app_label,
975
        q.model._meta.object_name.lower()
976
    )
977
    extra_context = dict(
978
        is_search=False,
979
        q=q,
980
        sorting=sorting,
981
        page=request.GET.get('page', 1)
982
    )
983
    return render_response(template_name,
984
                           context_instance=get_context(request, extra_context)
985
    )
986

    
987

    
988
@require_http_methods(["GET", "POST"])
989
@signed_terms_required
990
@login_required
991
def group_detail(request, group_id):
992
    q = AstakosGroup.objects.select_related().filter(pk=group_id)
993
    q = q.extra(select={
994
        'is_member': """SELECT CASE WHEN EXISTS(
995
                            SELECT id FROM im_membership
996
                            WHERE group_id = im_astakosgroup.group_ptr_id
997
                            AND person_id = %s)
998
                        THEN 1 ELSE 0 END""" % request.user.id,
999
        'is_owner': """SELECT CASE WHEN EXISTS(
1000
                        SELECT id FROM im_astakosuser_owner
1001
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1002
                        AND astakosuser_id = %s)
1003
                        THEN 1 ELSE 0 END""" % request.user.id,
1004
        'is_active_member': """SELECT CASE WHEN(
1005
                        SELECT date_joined FROM im_membership
1006
                        WHERE group_id = im_astakosgroup.group_ptr_id
1007
                        AND person_id = %s) IS NULL
1008
                        THEN 0 ELSE 1 END""" % request.user.id,
1009
        'kindname': """SELECT name FROM im_groupkind
1010
                       WHERE id = im_astakosgroup.kind_id"""})
1011

    
1012
    model = q.model
1013
    context_processors = None
1014
    mimetype = None
1015
    try:
1016
        obj = q.get()
1017
    except AstakosGroup.DoesNotExist:
1018
        raise Http404("No %s found matching the query" % (
1019
            model._meta.verbose_name))
1020

    
1021
    update_form = AstakosGroupUpdateForm(instance=obj)
1022
    addmembers_form = AddGroupMembersForm()
1023
    if request.method == 'POST':
1024
        update_data = {}
1025
        addmembers_data = {}
1026
        for k, v in request.POST.iteritems():
1027
            if k in update_form.fields:
1028
                update_data[k] = v
1029
            if k in addmembers_form.fields:
1030
                addmembers_data[k] = v
1031
        update_data = update_data or None
1032
        addmembers_data = addmembers_data or None
1033
        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
1034
        addmembers_form = AddGroupMembersForm(addmembers_data)
1035
        if update_form.is_valid():
1036
            update_form.save()
1037
        if addmembers_form.is_valid():
1038
            try:
1039
                map(obj.approve_member, addmembers_form.valid_users)
1040
            except AssertionError:
1041
                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1042
                messages.error(request, msg)
1043
            addmembers_form = AddGroupMembersForm()
1044

    
1045
    template_name = "%s/%s_detail.html" % (
1046
        model._meta.app_label, model._meta.object_name.lower())
1047
    t = template_loader.get_template(template_name)
1048
    c = RequestContext(request, {
1049
        'object': obj,
1050
    }, context_processors)
1051

    
1052
    # validate sorting
1053
    sorting = 'person__email'
1054
    form = MembersSortForm(request.GET)
1055
    if form.is_valid():
1056
        sorting = form.cleaned_data.get('sorting')
1057

    
1058
    result = callpoint.list_resources()
1059
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1060
    resource_catalog.update_from_result(result)
1061

    
1062

    
1063
    if not result.is_success:
1064
        messages.error(
1065
            request,
1066
            'Unable to retrieve system resources: %s' % result.reason
1067
    )
1068

    
1069
    extra_context = {'update_form': update_form,
1070
                     'addmembers_form': addmembers_form,
1071
                     'page': request.GET.get('page', 1),
1072
                     'sorting': sorting,
1073
                     'resource_catalog':resource_catalog,
1074
                     'quota':resource_catalog.get_quota(obj.quota)}
1075
    for key, value in extra_context.items():
1076
        if callable(value):
1077
            c[key] = value()
1078
        else:
1079
            c[key] = value
1080
    response = HttpResponse(t.render(c), mimetype=mimetype)
1081
    populate_xheaders(
1082
        request, response, model, getattr(obj, obj._meta.pk.name))
1083
    return response
1084

    
1085

    
1086
@require_http_methods(["GET", "POST"])
1087
@signed_terms_required
1088
@login_required
1089
def group_search(request, extra_context=None, **kwargs):
1090
    q = request.GET.get('q')
1091
    if request.method == 'GET':
1092
        form = AstakosGroupSearchForm({'q': q} if q else None)
1093
    else:
1094
        form = AstakosGroupSearchForm(get_query(request))
1095
        if form.is_valid():
1096
            q = form.cleaned_data['q'].strip()
1097

    
1098
    sorting = 'groupname'
1099
    if q:
1100
        queryset = AstakosGroup.objects.select_related()
1101
        queryset = queryset.filter(~Q(kind__name='default'))
1102
        queryset = queryset.filter(name__contains=q)
1103
        queryset = queryset.filter(approval_date__isnull=False)
1104
        queryset = queryset.extra(select={
1105
                                  'groupname': "auth_group.name",
1106
                                  'kindname': "im_groupkind.name",
1107
                                  'approved_members_num': """
1108
                    SELECT COUNT(*) FROM im_membership
1109
                    WHERE group_id = im_astakosgroup.group_ptr_id
1110
                    AND date_joined IS NOT NULL""",
1111
                                  'membership_approval_date': """
1112
                    SELECT date_joined FROM im_membership
1113
                    WHERE group_id = im_astakosgroup.group_ptr_id
1114
                    AND person_id = %s""" % request.user.id,
1115
                                  'is_member': """
1116
                    SELECT CASE WHEN EXISTS(
1117
                    SELECT date_joined FROM im_membership
1118
                    WHERE group_id = im_astakosgroup.group_ptr_id
1119
                    AND person_id = %s)
1120
                    THEN 1 ELSE 0 END""" % request.user.id,
1121
                                  'is_owner': """
1122
                    SELECT CASE WHEN EXISTS(
1123
                    SELECT id FROM im_astakosuser_owner
1124
                    WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1125
                    AND astakosuser_id = %s)
1126
                    THEN 1 ELSE 0 END""" % request.user.id,
1127
                    'is_owner': """SELECT CASE WHEN EXISTS(
1128
                        SELECT id FROM im_astakosuser_owner
1129
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1130
                        AND astakosuser_id = %s)
1131
                        THEN 1 ELSE 0 END""" % request.user.id,
1132
                    })
1133

    
1134
        # validate sorting
1135
        sort_form = AstakosGroupSortForm(request.GET)
1136
        if sort_form.is_valid():
1137
            sorting = sort_form.cleaned_data.get('sorting')
1138
        queryset = queryset.order_by(sorting)
1139

    
1140
    else:
1141
        queryset = AstakosGroup.objects.none()
1142
    return object_list(
1143
        request,
1144
        queryset,
1145
        paginate_by=PAGINATE_BY_ALL,
1146
        page=request.GET.get('page') or 1,
1147
        template_name='im/astakosgroup_list.html',
1148
        extra_context=dict(form=form,
1149
                           is_search=True,
1150
                           q=q,
1151
                           sorting=sorting))
1152

    
1153

    
1154
@require_http_methods(["GET", "POST"])
1155
@signed_terms_required
1156
@login_required
1157
def group_all(request, extra_context=None, **kwargs):
1158
    q = AstakosGroup.objects.select_related()
1159
    q = q.filter(~Q(kind__name='default'))
1160
    q = q.filter(approval_date__isnull=False)
1161
    q = q.extra(select={
1162
                'groupname': "auth_group.name",
1163
                'kindname': "im_groupkind.name",
1164
                'approved_members_num': """
1165
                    SELECT COUNT(*) FROM im_membership
1166
                    WHERE group_id = im_astakosgroup.group_ptr_id
1167
                    AND date_joined IS NOT NULL""",
1168
                'membership_approval_date': """
1169
                    SELECT date_joined FROM im_membership
1170
                    WHERE group_id = im_astakosgroup.group_ptr_id
1171
                    AND person_id = %s""" % request.user.id,
1172
                'is_member': """
1173
                    SELECT CASE WHEN EXISTS(
1174
                    SELECT date_joined FROM im_membership
1175
                    WHERE group_id = im_astakosgroup.group_ptr_id
1176
                    AND person_id = %s)
1177
                    THEN 1 ELSE 0 END""" % request.user.id,
1178
                 'is_owner': """SELECT CASE WHEN EXISTS(
1179
                        SELECT id FROM im_astakosuser_owner
1180
                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1181
                        AND astakosuser_id = %s)
1182
                        THEN 1 ELSE 0 END""" % request.user.id,   })
1183

    
1184
    # validate sorting
1185
    sorting = 'groupname'
1186
    sort_form = AstakosGroupSortForm(request.GET)
1187
    if sort_form.is_valid():
1188
        sorting = sort_form.cleaned_data.get('sorting')
1189
    q = q.order_by(sorting)
1190

    
1191
    return object_list(
1192
        request,
1193
        q,
1194
        paginate_by=PAGINATE_BY_ALL,
1195
        page=request.GET.get('page') or 1,
1196
        template_name='im/astakosgroup_list.html',
1197
        extra_context=dict(form=AstakosGroupSearchForm(),
1198
                           is_search=True,
1199
                           sorting=sorting))
1200

    
1201

    
1202
#@require_http_methods(["POST"])
1203
@require_http_methods(["POST", "GET"])
1204
@signed_terms_required
1205
@login_required
1206
def group_join(request, group_id):
1207
    m = Membership(group_id=group_id,
1208
                   person=request.user,
1209
                   date_requested=datetime.now())
1210
    try:
1211
        m.save()
1212
        post_save_redirect = reverse(
1213
            'group_detail',
1214
            kwargs=dict(group_id=group_id))
1215
        return HttpResponseRedirect(post_save_redirect)
1216
    except IntegrityError, e:
1217
        logger.exception(e)
1218
        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1219
        messages.error(request, msg)
1220
        return group_search(request)
1221

    
1222

    
1223
@require_http_methods(["POST"])
1224
@signed_terms_required
1225
@login_required
1226
def group_leave(request, group_id):
1227
    try:
1228
        m = Membership.objects.select_related().get(
1229
            group__id=group_id,
1230
            person=request.user)
1231
    except Membership.DoesNotExist:
1232
        return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1233
    if request.user in m.group.owner.all():
1234
        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1235
    return delete_object(
1236
        request,
1237
        model=Membership,
1238
        object_id=m.id,
1239
        template_name='im/astakosgroup_list.html',
1240
        post_delete_redirect=reverse(
1241
            'group_detail',
1242
            kwargs=dict(group_id=group_id)))
1243

    
1244

    
1245
def handle_membership(func):
1246
    @wraps(func)
1247
    def wrapper(request, group_id, user_id):
1248
        try:
1249
            m = Membership.objects.select_related().get(
1250
                group__id=group_id,
1251
                person__id=user_id)
1252
        except Membership.DoesNotExist:
1253
            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1254
        else:
1255
            if request.user not in m.group.owner.all():
1256
                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1257
            func(request, m)
1258
            return group_detail(request, group_id)
1259
    return wrapper
1260

    
1261

    
1262
#@require_http_methods(["POST"])
1263
@require_http_methods(["POST", "GET"])
1264
@signed_terms_required
1265
@login_required
1266
@handle_membership
1267
def approve_member(request, membership):
1268
    try:
1269
        membership.approve()
1270
        realname = membership.person.realname
1271
        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1272
        messages.success(request, msg)
1273
    except AssertionError:
1274
        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1275
        messages.error(request, msg)
1276
    except BaseException, e:
1277
        logger.exception(e)
1278
        realname = membership.person.realname
1279
        msg = _(astakos_messages.GENERIC_ERROR)
1280
        messages.error(request, msg)
1281

    
1282

    
1283
@signed_terms_required
1284
@login_required
1285
@handle_membership
1286
def disapprove_member(request, membership):
1287
    try:
1288
        membership.disapprove()
1289
        realname = membership.person.realname
1290
        msg = astakos_messages.MEMBER_REMOVED % locals()
1291
        messages.success(request, msg)
1292
    except BaseException, e:
1293
        logger.exception(e)
1294
        msg = _(astakos_messages.GENERIC_ERROR)
1295
        messages.error(request, msg)
1296

    
1297

    
1298
#@require_http_methods(["GET"])
1299
@require_http_methods(["POST", "GET"])
1300
@signed_terms_required
1301
@login_required
1302
def resource_usage(request):
1303
    def with_class(entry):
1304
        entry['load_class'] = 'red'
1305
        max_value = float(entry['maxValue'])
1306
        curr_value = float(entry['currValue'])
1307
        entry['ratio_limited']= 0
1308
        if max_value > 0 :
1309
            entry['ratio'] = (curr_value / max_value) * 100
1310
        else:
1311
            entry['ratio'] = 0
1312
        if entry['ratio'] < 66:
1313
            entry['load_class'] = 'yellow'
1314
        if entry['ratio'] < 33:
1315
            entry['load_class'] = 'green'
1316
        if entry['ratio']<0:
1317
            entry['ratio'] = 0
1318
        if entry['ratio']>100:
1319
            entry['ratio_limited'] = 100
1320
        else:
1321
            entry['ratio_limited'] = entry['ratio']
1322

    
1323
        return entry
1324

    
1325
    def pluralize(entry):
1326
        entry['plural'] = engine.plural(entry.get('name'))
1327
        return entry
1328

    
1329
    result = callpoint.get_user_usage(request.user.id)
1330
    if result.is_success:
1331
        backenddata = map(with_class, result.data)
1332
        data = map(pluralize, result.data)
1333
    else:
1334
        data = None
1335
        messages.error(request, result.reason)
1336
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1337
    resource_catalog.update_from_result_report(result)
1338
    return render_response('im/resource_usage.html',
1339
                           data=data,
1340
                           context_instance=get_context(request),
1341
                           resource_catalog=resource_catalog,
1342
                           result=result)
1343

    
1344

    
1345
def group_create_list(request):
1346
    form = PickResourceForm()
1347
    return render_response(
1348
        template='im/astakosgroup_create_list.html',
1349
        context_instance=get_context(request),)
1350

    
1351

    
1352
##@require_http_methods(["GET"])
1353
#@require_http_methods(["POST", "GET"])
1354
#@signed_terms_required
1355
#@login_required
1356
#def billing(request):
1357
#
1358
#    today = datetime.today()
1359
#    month_last_day = calendar.monthrange(today.year, today.month)[1]
1360
#    start = request.POST.get('datefrom', None)
1361
#    if start:
1362
#        today = datetime.fromtimestamp(int(start))
1363
#        month_last_day = calendar.monthrange(today.year, today.month)[1]
1364
#
1365
#    start = datetime(today.year, today.month, 1).strftime("%s")
1366
#    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1367
#    r = request_billing.apply(args=('pgerakios@grnet.gr',
1368
#                                    int(start) * 1000,
1369
#                                    int(end) * 1000))
1370
#    data = {}
1371
#
1372
#    try:
1373
#        status, data = r.result
1374
#        data = _clear_billing_data(data)
1375
#        if status != 200:
1376
#            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1377
#    except:
1378
#        messages.error(request, r.result)
1379
#
1380
#    return render_response(
1381
#        template='im/billing.html',
1382
#        context_instance=get_context(request),
1383
#        data=data,
1384
#        zerodate=datetime(month=1, year=1970, day=1),
1385
#        today=today,
1386
#        start=int(start),
1387
#        month_last_day=month_last_day)
1388

    
1389

    
1390
#def _clear_billing_data(data):
1391
#
1392
#    # remove addcredits entries
1393
#    def isnotcredit(e):
1394
#        return e['serviceName'] != "addcredits"
1395
#
1396
#    # separate services
1397
#    def servicefilter(service_name):
1398
#        service = service_name
1399
#
1400
#        def fltr(e):
1401
#            return e['serviceName'] == service
1402
#        return fltr
1403
#
1404
#    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1405
#    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1406
#    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1407
#    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1408
#
1409
#    return data
1410

    
1411

    
1412
#@require_http_methods(["GET"])
1413
@require_http_methods(["POST", "GET"])
1414
@signed_terms_required
1415
@login_required
1416
def timeline(request):
1417
#    data = {'entity':request.user.email}
1418
    timeline_body = ()
1419
    timeline_header = ()
1420
#    form = TimelineForm(data)
1421
    form = TimelineForm()
1422
    if request.method == 'POST':
1423
        data = request.POST
1424
        form = TimelineForm(data)
1425
        if form.is_valid():
1426
            data = form.cleaned_data
1427
            timeline_header = ('entity', 'resource',
1428
                               'event name', 'event date',
1429
                               'incremental cost', 'total cost')
1430
            timeline_body = timeline_charge(
1431
                data['entity'], data['resource'],
1432
                data['start_date'], data['end_date'],
1433
                data['details'], data['operation'])
1434

    
1435
    return render_response(template='im/timeline.html',
1436
                           context_instance=get_context(request),
1437
                           form=form,
1438
                           timeline_header=timeline_header,
1439
                           timeline_body=timeline_body)
1440
    return data
1441

    
1442

    
1443
# TODO: action only on POST and user should confirm the removal
1444
@require_http_methods(["GET", "POST"])
1445
@login_required
1446
@signed_terms_required
1447
def remove_auth_provider(request, pk):
1448
    try:
1449
        provider = request.user.auth_providers.get(pk=pk)
1450
    except AstakosUserAuthProvider.DoesNotExist:
1451
        raise Http404
1452

    
1453
    if provider.can_remove():
1454
        provider.delete()
1455
        return HttpResponseRedirect(reverse('edit_profile'))
1456
    else:
1457
        raise PermissionDenied
1458

    
1459

    
1460
def how_it_works(request):
1461
    return render_response(
1462
        template='im/how_it_works.html',
1463
        context_instance=get_context(request),)
1464