Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 394c9834

History | View | Annotate | Download (51.7 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

    
40
from urllib import quote
41
from functools import wraps
42
from datetime import datetime
43

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

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

    
90
import astakos.im.messages as astakos_messages
91
from astakos.im import settings
92
from astakos.im import auth_providers
93

    
94
logger = logging.getLogger(__name__)
95

    
96
callpoint = AstakosCallpoint()
97

    
98
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
99
    """
100
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
101
    keyword argument and returns an ``django.http.HttpResponse`` with the
102
    specified ``status``.
103
    """
104
    if tab is None:
105
        tab = template.partition('_')[0].partition('.html')[0]
106
    kwargs.setdefault('tab', tab)
107
    html = template_loader.render_to_string(
108
        template, kwargs, context_instance=context_instance)
109
    response = HttpResponse(html, status=status)
110
    return response
111

    
112
def requires_auth_provider(provider_id, **perms):
113
    """
114
    """
115
    def decorator(func, *args, **kwargs):
116
        @wraps(func)
117
        def wrapper(request, *args, **kwargs):
118
            provider = auth_providers.get_provider(provider_id)
119

    
120
            if not provider or not provider.is_active():
121
                raise PermissionDenied
122

    
123
            if provider:
124
                for pkey, value in perms.iteritems():
125
                    attr = 'is_available_for_%s' % pkey.lower()
126
                    if getattr(provider, attr)() != value:
127
                        raise PermissionDenied
128
            return func(request, *args)
129
        return wrapper
130
    return decorator
131

    
132

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

    
147

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

    
163

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

170
    **Arguments**
171

172
    ``login_template_name``
173
        A custom login template to use. This is optional; if not specified,
174
        this will default to ``im/login.html``.
175

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

180
    ``extra_context``
181
        An dictionary of variables to add to the template context.
182

183
    **Template:**
184

185
    im/profile.html or im/login.html or ``template_name`` keyword argument.
186

187
    """
188
    extra_context = extra_context or {}
189
    template_name = login_template_name
190
    if request.user.is_authenticated():
191
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
192

    
193
    return render_response(
194
        template_name,
195
        login_form = LoginForm(request=request),
196
        context_instance = get_context(request, extra_context)
197
    )
198

    
199

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

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

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

215
    If the user isn't logged in, redirects to settings.LOGIN_URL.
216

217
    **Arguments**
218

219
    ``template_name``
220
        A custom template to use. This is optional; if not specified,
221
        this will default to ``im/invitations.html``.
222

223
    ``extra_context``
224
        An dictionary of variables to add to the template context.
225

226
    **Template:**
227

228
    im/invitations.html or ``template_name`` keyword argument.
229

230
    **Settings:**
231

232
    The view expectes the following settings are defined:
233

234
    * LOGIN_URL: login uri
235
    """
236
    extra_context = extra_context or {}
237
    status = None
238
    message = None
239
    form = InvitationForm()
240

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

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

    
278

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

286
    In case of GET request renders a form for displaying the user information.
287
    In case of POST updates the user informantion and redirects to ``next``
288
    url parameter if exists.
289

290
    If the user isn't logged in, redirects to settings.LOGIN_URL.
291

292
    **Arguments**
293

294
    ``template_name``
295
        A custom template to use. This is optional; if not specified,
296
        this will default to ``im/profile.html``.
297

298
    ``extra_context``
299
        An dictionary of variables to add to the template context.
300

301
    **Template:**
302

303
    im/profile.html or ``template_name`` keyword argument.
304

305
    **Settings:**
306

307
    The view expectes the following settings are defined:
308

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

    
345
    # existing providers
346
    user_providers = request.user.get_active_auth_providers()
347

    
348
    # providers that user can add
349
    user_available_providers = request.user.get_available_auth_providers()
350

    
351
    return render_response(template_name,
352
                           profile_form = form,
353
                           user_providers = user_providers,
354
                           user_available_providers = user_available_providers,
355
                           context_instance = get_context(request,
356
                                                          extra_context))
357

    
358

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

365
    In case of GET request renders a form for entering the user information.
366
    In case of POST handles the signup.
367

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

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

376
    On unsuccessful creation, renders ``template_name`` with an error message.
377

378
    **Arguments**
379

380
    ``template_name``
381
        A custom template to render. This is optional;
382
        if not specified, this will default to ``im/signup.html``.
383

384
    ``on_success``
385
        A custom template to render in case of success. This is optional;
386
        if not specified, this will default to ``im/signup_complete.html``.
387

388
    ``extra_context``
389
        An dictionary of variables to add to the template context.
390

391
    **Template:**
392

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

    
400
    provider = get_query(request).get('provider', 'local')
401
    if not auth_providers.get_provider(provider).is_available_for_create():
402
        raise PermissionDenied
403

    
404
    id = get_query(request).get('id')
405
    try:
406
        instance = AstakosUser.objects.get(id=id) if id else None
407
    except AstakosUser.DoesNotExist:
408
        instance = None
409

    
410
    third_party_token = request.REQUEST.get('third_party_token', None)
411

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

    
427
                form.store_user(user, request)
428

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

    
470

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

478
    In case of GET request renders a form for providing the feedback information.
479
    In case of POST sends an email to support team.
480

481
    If the user isn't logged in, redirects to settings.LOGIN_URL.
482

483
    **Arguments**
484

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

489
    ``extra_context``
490
        An dictionary of variables to add to the template context.
491

492
    **Template:**
493

494
    im/signup.html or ``template_name`` keyword argument.
495

496
    **Settings:**
497

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

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

    
522

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

    
550

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

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

    
569
    if user.is_active:
570
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
571
        messages.error(request, message)
572
        return index(request)
573

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

    
592

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

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

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

    
639

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

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

    
693

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

    
696
    if settings.MODERATION_ENABLED:
697
        raise PermissionDenied
698

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

    
720
class ResourcePresentation():
721

    
722
    def __init__(self, data):
723
        self.data = data
724

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

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

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

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

    
747

    
748
            self.data['resources'][rname]['value'] = value
749

    
750

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

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

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

    
766
    def get_group_resources(self, group):
767
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
768

    
769
    def get_groups_resources(self):
770
        for g in self.data['groups']:
771
            yield g, self.get_group_resources(g)
772

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

    
780

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

    
787
    def __repr__(self):
788
        return self.data.__repr__()
789

    
790
    def __iter__(self, *args, **kwargs):
791
        return self.data.__iter__(*args, **kwargs)
792

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

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

    
799

    
800

    
801
@require_http_methods(["GET", "POST"])
802
@signed_terms_required
803
@login_required
804
def group_add(request, kind_name='default'):
805

    
806
    result = callpoint.list_resources()
807
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
808
    resource_catalog.update_from_result(result)
809

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

    
816
    try:
817
        kind = GroupKind.objects.get(name=kind_name)
818
    except:
819
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
820

    
821

    
822

    
823
    post_save_redirect = '/im/group/%(id)s/'
824
    context_processors = None
825
    model, form_class = get_model_and_form_class(
826
        model=None,
827
        form_class=AstakosGroupCreationForm
828
    )
829

    
830
    if request.method == 'POST':
831
        form = form_class(request.POST, request.FILES)
832
        if form.is_valid():
833
            policies = form.policies()
834
            return render_response(
835
                template='im/astakosgroup_form_summary.html',
836
                context_instance=get_context(request),
837
                form=AstakosGroupCreationSummaryForm(form.cleaned_data),
838
                policies=resource_catalog.get_policies(policies)
839
            )
840
    else:
841
        now = datetime.now()
842
        data = {
843
            'kind': kind,
844
        }
845
        for group, resources in resource_catalog.get_groups_resources():
846
            data['is_selected_%s' % group] = False
847
            for resource in resources:
848
                data['%s_uplimit' % resource] = ''
849

    
850
        form = form_class(data)
851

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

    
865

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

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

    
909

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

    
943
    # validate sorting
944
    sorting = 'groupname'
945
    sort_form = AstakosGroupSortForm(request.GET)
946
    if sort_form.is_valid():
947
        sorting = sort_form.cleaned_data.get('sorting')
948
    query = query+" ORDER BY %s ASC" %sorting
949
    
950
    q = AstakosGroup.objects.raw(query)
951
    
952
    # Create the template, context, response
953
    template_name = "%s/%s_list.html" % (
954
        q.model._meta.app_label,
955
        q.model._meta.object_name.lower()
956
    )
957
    extra_context = dict(
958
        is_search=False,
959
        q=q,
960
        sorting=sorting,
961
        page=request.GET.get('page', 1)
962
    )
963
    return render_response(template_name,
964
                           context_instance=get_context(request, extra_context)
965
    )
966

    
967

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

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

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

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

    
1032
    # validate sorting
1033
    sorting = 'person__email'
1034
    form = MembersSortForm(request.GET)
1035
    if form.is_valid():
1036
        sorting = form.cleaned_data.get('sorting')
1037
    
1038
    result = callpoint.list_resources()
1039
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1040
    resource_catalog.update_from_result(result)
1041

    
1042

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

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

    
1065

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

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

    
1133

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

    
1181

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

    
1202

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

    
1224

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

    
1241

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

    
1262

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

    
1277

    
1278
#@require_http_methods(["GET"])
1279
@require_http_methods(["POST", "GET"])
1280
@signed_terms_required
1281
@login_required
1282
def resource_usage(request):
1283
    def with_class(entry):
1284
        entry['load_class'] = 'red'
1285
        max_value = float(entry['maxValue'])
1286
        curr_value = float(entry['currValue'])
1287
        entry['ratio_limited']= 0
1288
        if max_value > 0 :
1289
            entry['ratio'] = (curr_value / max_value) * 100
1290
        else:
1291
            entry['ratio'] = 0
1292
        if entry['ratio'] < 66:
1293
            entry['load_class'] = 'yellow'
1294
        if entry['ratio'] < 33:
1295
            entry['load_class'] = 'green'
1296
        if entry['ratio']<0:
1297
            entry['ratio'] = 0
1298
        if entry['ratio']>100:
1299
            entry['ratio_limited'] = 100
1300
        else:
1301
            entry['ratio_limited'] = entry['ratio']
1302
        
1303
        return entry
1304

    
1305
    def pluralize(entry):
1306
        entry['plural'] = engine.plural(entry.get('name'))
1307
        return entry
1308

    
1309
    result = callpoint.get_user_usage(request.user.id)
1310
    if result.is_success:
1311
        backenddata = map(with_class, result.data)
1312
        data = map(pluralize, result.data)
1313
    else:
1314
        data = None
1315
        messages.error(request, result.reason)
1316
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1317
    resource_catalog.update_from_result_report(result)
1318
    return render_response('im/resource_usage.html',
1319
                           data=data,
1320
                           context_instance=get_context(request),
1321
                           resource_catalog=resource_catalog,
1322
                           result=result)
1323

    
1324

    
1325
def group_create_list(request):
1326
    form = PickResourceForm()
1327
    return render_response(
1328
        template='im/astakosgroup_create_list.html',
1329
        context_instance=get_context(request),)
1330

    
1331

    
1332
##@require_http_methods(["GET"])
1333
#@require_http_methods(["POST", "GET"])
1334
#@signed_terms_required
1335
#@login_required
1336
#def billing(request):
1337
#
1338
#    today = datetime.today()
1339
#    month_last_day = calendar.monthrange(today.year, today.month)[1]
1340
#    start = request.POST.get('datefrom', None)
1341
#    if start:
1342
#        today = datetime.fromtimestamp(int(start))
1343
#        month_last_day = calendar.monthrange(today.year, today.month)[1]
1344
#
1345
#    start = datetime(today.year, today.month, 1).strftime("%s")
1346
#    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1347
#    r = request_billing.apply(args=('pgerakios@grnet.gr',
1348
#                                    int(start) * 1000,
1349
#                                    int(end) * 1000))
1350
#    data = {}
1351
#
1352
#    try:
1353
#        status, data = r.result
1354
#        data = _clear_billing_data(data)
1355
#        if status != 200:
1356
#            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1357
#    except:
1358
#        messages.error(request, r.result)
1359
#
1360
#    return render_response(
1361
#        template='im/billing.html',
1362
#        context_instance=get_context(request),
1363
#        data=data,
1364
#        zerodate=datetime(month=1, year=1970, day=1),
1365
#        today=today,
1366
#        start=int(start),
1367
#        month_last_day=month_last_day)
1368

    
1369

    
1370
#def _clear_billing_data(data):
1371
#
1372
#    # remove addcredits entries
1373
#    def isnotcredit(e):
1374
#        return e['serviceName'] != "addcredits"
1375
#
1376
#    # separate services
1377
#    def servicefilter(service_name):
1378
#        service = service_name
1379
#
1380
#        def fltr(e):
1381
#            return e['serviceName'] == service
1382
#        return fltr
1383
#
1384
#    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1385
#    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1386
#    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1387
#    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1388
#
1389
#    return data
1390

    
1391

    
1392
#@require_http_methods(["GET"])
1393
@require_http_methods(["POST", "GET"])
1394
@signed_terms_required
1395
@login_required
1396
def timeline(request):
1397
#    data = {'entity':request.user.email}
1398
    timeline_body = ()
1399
    timeline_header = ()
1400
#    form = TimelineForm(data)
1401
    form = TimelineForm()
1402
    if request.method == 'POST':
1403
        data = request.POST
1404
        form = TimelineForm(data)
1405
        if form.is_valid():
1406
            data = form.cleaned_data
1407
            timeline_header = ('entity', 'resource',
1408
                               'event name', 'event date',
1409
                               'incremental cost', 'total cost')
1410
            timeline_body = timeline_charge(
1411
                data['entity'], data['resource'],
1412
                data['start_date'], data['end_date'],
1413
                data['details'], data['operation'])
1414

    
1415
    return render_response(template='im/timeline.html',
1416
                           context_instance=get_context(request),
1417
                           form=form,
1418
                           timeline_header=timeline_header,
1419
                           timeline_body=timeline_body)
1420
    return data
1421

    
1422

    
1423
# TODO: action only on POST and user should confirm the removal
1424
@require_http_methods(["GET", "POST"])
1425
@login_required
1426
@signed_terms_required
1427
def remove_auth_provider(request, pk):
1428
    try:
1429
        provider = request.user.auth_providers.get(pk=pk)
1430
    except AstakosUserAuthProvider.DoesNotExist:
1431
        raise Http404
1432

    
1433
    if provider.can_remove():
1434
        provider.delete()
1435
        return HttpResponseRedirect(reverse('edit_profile'))
1436
    else:
1437
        raise PermissionDenied
1438

    
1439

    
1440
def how_it_works(request):
1441
    return render_response(
1442
        template='im/how_it_works.html',
1443
        context_instance=get_context(request),)
1444