Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 666c7490

History | View | Annotate | Download (51.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.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
        'kindname': """SELECT name FROM im_groupkind
985
                       WHERE id = im_astakosgroup.kind_id"""})
986

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

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

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

    
1027
    # validate sorting
1028
    sorting = 'person__email'
1029
    form = MembersSortForm(request.GET)
1030
    if form.is_valid():
1031
        sorting = form.cleaned_data.get('sorting')
1032
    
1033
    result = callpoint.list_resources()
1034
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1035
    resource_catalog.update_from_result(result)
1036

    
1037

    
1038
    if not result.is_success:
1039
        messages.error(
1040
            request,
1041
            'Unable to retrieve system resources: %s' % result.reason
1042
    )
1043

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

    
1060

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

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

    
1128

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

    
1176

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

    
1197

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

    
1219

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

    
1236

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

    
1257

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

    
1272

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

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

    
1296
    result = callpoint.get_user_usage(request.user.id)
1297
    if result.is_success:
1298
        backenddata = map(with_class, result.data)
1299
        data = map(pluralize, result.data)
1300
    else:
1301
        data = None
1302
        messages.error(request, result.reason)
1303
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1304
    resource_catalog.update_from_result_report(result)
1305
    return render_response('im/resource_usage.html',
1306
                           data=data,
1307
                           context_instance=get_context(request),
1308
                           resource_catalog=resource_catalog,
1309
                           result=result)
1310

    
1311

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

    
1318

    
1319
##@require_http_methods(["GET"])
1320
#@require_http_methods(["POST", "GET"])
1321
#@signed_terms_required
1322
#@login_required
1323
#def billing(request):
1324
#
1325
#    today = datetime.today()
1326
#    month_last_day = calendar.monthrange(today.year, today.month)[1]
1327
#    start = request.POST.get('datefrom', None)
1328
#    if start:
1329
#        today = datetime.fromtimestamp(int(start))
1330
#        month_last_day = calendar.monthrange(today.year, today.month)[1]
1331
#
1332
#    start = datetime(today.year, today.month, 1).strftime("%s")
1333
#    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1334
#    r = request_billing.apply(args=('pgerakios@grnet.gr',
1335
#                                    int(start) * 1000,
1336
#                                    int(end) * 1000))
1337
#    data = {}
1338
#
1339
#    try:
1340
#        status, data = r.result
1341
#        data = _clear_billing_data(data)
1342
#        if status != 200:
1343
#            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1344
#    except:
1345
#        messages.error(request, r.result)
1346
#
1347
#    return render_response(
1348
#        template='im/billing.html',
1349
#        context_instance=get_context(request),
1350
#        data=data,
1351
#        zerodate=datetime(month=1, year=1970, day=1),
1352
#        today=today,
1353
#        start=int(start),
1354
#        month_last_day=month_last_day)
1355

    
1356

    
1357
#def _clear_billing_data(data):
1358
#
1359
#    # remove addcredits entries
1360
#    def isnotcredit(e):
1361
#        return e['serviceName'] != "addcredits"
1362
#
1363
#    # separate services
1364
#    def servicefilter(service_name):
1365
#        service = service_name
1366
#
1367
#        def fltr(e):
1368
#            return e['serviceName'] == service
1369
#        return fltr
1370
#
1371
#    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1372
#    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1373
#    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1374
#    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1375
#
1376
#    return data
1377

    
1378

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

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

    
1409

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

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

    
1426

    
1427
def how_it_works(request):
1428
    return render_response(
1429
        template='im/how_it_works.html',
1430
        context_instance=get_context(request),)
1431