Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (52.1 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
                        #TODO: add session message
131
                        return HttpResponseRedirect(reverse('login'))
132
            return func(request, *args)
133
        return wrapper
134
    return decorator
135

    
136

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

    
151

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

    
167

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

174
    **Arguments**
175

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

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

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

187
    **Template:**
188

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

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

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

    
203

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

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

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

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

221
    **Arguments**
222

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

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

230
    **Template:**
231

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

234
    **Settings:**
235

236
    The view expectes the following settings are defined:
237

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

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

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

    
282

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

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

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

296
    **Arguments**
297

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

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

305
    **Template:**
306

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

309
    **Settings:**
310

311
    The view expectes the following settings are defined:
312

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

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

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

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

    
362

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

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

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

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

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

382
    **Arguments**
383

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

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

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

395
    **Template:**
396

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

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

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

    
414
    third_party_token = request.REQUEST.get('third_party_token', None)
415
    if third_party_token:
416
        pending = get_object_or_404(PendingThirdPartyUser,
417
                                    token=third_party_token)
418
        provider = pending.provider
419
        instance = pending.get_user_instance()
420

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

    
436
                form.store_user(user, request)
437

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

    
479

    
480
@require_http_methods(["GET", "POST"])
481
@login_required
482
@signed_terms_required
483
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
484
    """
485
    Allows a user to send feedback.
486

487
    In case of GET request renders a form for providing the feedback information.
488
    In case of POST sends an email to support team.
489

490
    If the user isn't logged in, redirects to settings.LOGIN_URL.
491

492
    **Arguments**
493

494
    ``template_name``
495
        A custom template to use. This is optional; if not specified,
496
        this will default to ``im/feedback.html``.
497

498
    ``extra_context``
499
        An dictionary of variables to add to the template context.
500

501
    **Template:**
502

503
    im/signup.html or ``template_name`` keyword argument.
504

505
    **Settings:**
506

507
    * LOGIN_URL: login uri
508
    """
509
    extra_context = extra_context or {}
510
    if request.method == 'GET':
511
        form = FeedbackForm()
512
    if request.method == 'POST':
513
        if not request.user:
514
            return HttpResponse('Unauthorized', status=401)
515

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

    
531

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

    
548
    next = restrict_next(
549
        request.GET.get('next'),
550
        domain=COOKIE_DOMAIN
551
    )
552

    
553
    if next:
554
        response['Location'] = next
555
        response.status_code = 302
556
    elif LOGOUT_NEXT:
557
        response['Location'] = LOGOUT_NEXT
558
        response.status_code = 301
559
    else:
560
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
561
        response['Location'] = reverse('index')
562
        response.status_code = 301
563
    return response
564

    
565

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

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

    
584
    if user.is_active:
585
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
586
        messages.error(request, message)
587
        return index(request)
588

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

    
607

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

    
624
    if not term:
625
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
626
        return HttpResponseRedirect(reverse('index'))
627
    f = open(term.location, 'r')
628
    terms = f.read()
629

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

    
654

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

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

    
708

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

    
711
    if settings.MODERATION_ENABLED:
712
        raise PermissionDenied
713

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

    
735
class ResourcePresentation():
736

    
737
    def __init__(self, data):
738
        self.data = data
739

    
740
    def update_from_result(self, result):
741
        if result.is_success:
742
            for r in result.data:
743
                rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
744
                if not rname in self.data['resources']:
745
                    self.data['resources'][rname] = {}
746

    
747
                self.data['resources'][rname].update(r)
748
                self.data['resources'][rname]['id'] = rname
749
                group = r.get('group')
750
                if not group in self.data['groups']:
751
                    self.data['groups'][group] = {}
752

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

    
755
    def test(self, quota_dict):
756
        for k, v in quota_dict.iteritems():
757
            rname = k
758
            value = v
759
            if not rname in self.data['resources']:
760
                self.data['resources'][rname] = {}
761

    
762

    
763
            self.data['resources'][rname]['value'] = value
764

    
765

    
766
    def update_from_result_report(self, result):
767
        if result.is_success:
768
            for r in result.data:
769
                rname = r.get('name')
770
                if not rname in self.data['resources']:
771
                    self.data['resources'][rname] = {}
772

    
773
                self.data['resources'][rname].update(r)
774
                self.data['resources'][rname]['id'] = rname
775
                group = r.get('group')
776
                if not group in self.data['groups']:
777
                    self.data['groups'][group] = {}
778

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

    
781
    def get_group_resources(self, group):
782
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
783

    
784
    def get_groups_resources(self):
785
        for g in self.data['groups']:
786
            yield g, self.get_group_resources(g)
787

    
788
    def get_quota(self, group_quotas):
789
        for r, v in group_quotas.iteritems():
790
            rname = str(r)
791
            quota = self.data['resources'].get(rname)
792
            quota['value'] = v
793
            yield quota
794

    
795

    
796
    def get_policies(self, policies_data):
797
        for policy in policies_data:
798
            rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
799
            policy.update(self.data['resources'].get(rname))
800
            yield policy
801

    
802
    def __repr__(self):
803
        return self.data.__repr__()
804

    
805
    def __iter__(self, *args, **kwargs):
806
        return self.data.__iter__(*args, **kwargs)
807

    
808
    def __getitem__(self, *args, **kwargs):
809
        return self.data.__getitem__(*args, **kwargs)
810

    
811
    def get(self, *args, **kwargs):
812
        return self.data.get(*args, **kwargs)
813

    
814

    
815

    
816
@require_http_methods(["GET", "POST"])
817
@signed_terms_required
818
@login_required
819
def group_add(request, kind_name='default'):
820

    
821
    result = callpoint.list_resources()
822
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
823
    resource_catalog.update_from_result(result)
824

    
825
    if not result.is_success:
826
        messages.error(
827
            request,
828
            'Unable to retrieve system resources: %s' % result.reason
829
    )
830

    
831
    try:
832
        kind = GroupKind.objects.get(name=kind_name)
833
    except:
834
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
835

    
836

    
837

    
838
    post_save_redirect = '/im/group/%(id)s/'
839
    context_processors = None
840
    model, form_class = get_model_and_form_class(
841
        model=None,
842
        form_class=AstakosGroupCreationForm
843
    )
844

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

    
865
        form = form_class(data)
866

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

    
880

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

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

    
924

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

    
958
    # validate sorting
959
    sorting = 'groupname'
960
    sort_form = AstakosGroupSortForm(request.GET)
961
    if sort_form.is_valid():
962
        sorting = sort_form.cleaned_data.get('sorting')
963
    query = query+" ORDER BY %s ASC" %sorting
964

    
965
    q = AstakosGroup.objects.raw(query)
966

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

    
982

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

    
1007
    model = q.model
1008
    context_processors = None
1009
    mimetype = None
1010
    try:
1011
        obj = q.get()
1012
    except AstakosGroup.DoesNotExist:
1013
        raise Http404("No %s found matching the query" % (
1014
            model._meta.verbose_name))
1015

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

    
1040
    template_name = "%s/%s_detail.html" % (
1041
        model._meta.app_label, model._meta.object_name.lower())
1042
    t = template_loader.get_template(template_name)
1043
    c = RequestContext(request, {
1044
        'object': obj,
1045
    }, context_processors)
1046

    
1047
    # validate sorting
1048
    sorting = 'person__email'
1049
    form = MembersSortForm(request.GET)
1050
    if form.is_valid():
1051
        sorting = form.cleaned_data.get('sorting')
1052

    
1053
    result = callpoint.list_resources()
1054
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1055
    resource_catalog.update_from_result(result)
1056

    
1057

    
1058
    if not result.is_success:
1059
        messages.error(
1060
            request,
1061
            'Unable to retrieve system resources: %s' % result.reason
1062
    )
1063

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

    
1080

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

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

    
1129
        # validate sorting
1130
        sort_form = AstakosGroupSortForm(request.GET)
1131
        if sort_form.is_valid():
1132
            sorting = sort_form.cleaned_data.get('sorting')
1133
        queryset = queryset.order_by(sorting)
1134

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

    
1148

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

    
1179
    # validate sorting
1180
    sorting = 'groupname'
1181
    sort_form = AstakosGroupSortForm(request.GET)
1182
    if sort_form.is_valid():
1183
        sorting = sort_form.cleaned_data.get('sorting')
1184
    q = q.order_by(sorting)
1185

    
1186
    return object_list(
1187
        request,
1188
        q,
1189
        paginate_by=PAGINATE_BY_ALL,
1190
        page=request.GET.get('page') or 1,
1191
        template_name='im/astakosgroup_list.html',
1192
        extra_context=dict(form=AstakosGroupSearchForm(),
1193
                           is_search=True,
1194
                           sorting=sorting))
1195

    
1196

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

    
1217

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

    
1239

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

    
1256

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

    
1277

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

    
1292

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

    
1318
        return entry
1319

    
1320
    def pluralize(entry):
1321
        entry['plural'] = engine.plural(entry.get('name'))
1322
        return entry
1323

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

    
1339

    
1340
def group_create_list(request):
1341
    form = PickResourceForm()
1342
    return render_response(
1343
        template='im/astakosgroup_create_list.html',
1344
        context_instance=get_context(request),)
1345

    
1346

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

    
1384

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

    
1406

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

    
1430
    return render_response(template='im/timeline.html',
1431
                           context_instance=get_context(request),
1432
                           form=form,
1433
                           timeline_header=timeline_header,
1434
                           timeline_body=timeline_body)
1435
    return data
1436

    
1437

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

    
1448
    if provider.can_remove():
1449
        provider.delete()
1450
        return HttpResponseRedirect(reverse('edit_profile'))
1451
    else:
1452
        raise PermissionDenied
1453

    
1454

    
1455
def how_it_works(request):
1456
    return render_response(
1457
        template='im/how_it_works.html',
1458
        context_instance=get_context(request),)
1459