Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (52.2 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
    return render_response(
199
        template_name,
200
        login_form = LoginForm(request=request),
201
        context_instance = get_context(request, extra_context)
202
    )
203

    
204

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

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

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

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

222
    **Arguments**
223

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

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

231
    **Template:**
232

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

235
    **Settings:**
236

237
    The view expectes the following settings are defined:
238

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

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

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

    
283

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

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

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

297
    **Arguments**
298

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

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

306
    **Template:**
307

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

310
    **Settings:**
311

312
    The view expectes the following settings are defined:
313

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

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

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

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

    
363

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

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

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

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

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

383
    **Arguments**
384

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

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

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

396
    **Template:**
397

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

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

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

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

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

    
437
                form.store_user(user, request)
438

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

    
480

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

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

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

493
    **Arguments**
494

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

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

502
    **Template:**
503

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

506
    **Settings:**
507

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

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

    
532

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

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

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

    
566

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

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

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

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

    
608

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

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

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

    
655

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

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

    
709

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

    
712
    if settings.MODERATION_ENABLED:
713
        raise PermissionDenied
714

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

    
736
class ResourcePresentation():
737

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

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

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

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

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

    
763

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

    
766

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

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

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

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

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

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

    
796

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

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

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

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

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

    
815

    
816

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

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

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

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

    
837

    
838

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

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

    
866
        form = form_class(data)
867

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

    
881

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

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

    
925

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

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

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

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

    
983

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

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

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

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

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

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

    
1058

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

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

    
1081

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

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

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

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

    
1149

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

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

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

    
1197

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

    
1218

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

    
1240

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

    
1257

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

    
1278

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

    
1293

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

    
1319
        return entry
1320

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

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

    
1340

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

    
1347

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

    
1385

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

    
1407

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

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

    
1438

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

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

    
1455

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