Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 2a965273

History | View | Annotate | Download (54.3 kB)

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

    
34
import logging
35
import calendar
36
import inflect
37

    
38
engine = inflect.engine()
39

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

    
44
from django.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 (
57
    create_object, delete_object, get_model_and_form_class
58
)
59
from django.views.generic.list_detail import object_list, object_detail
60
from django.core.xheaders import populate_xheaders
61
from django.core.exceptions import ValidationError, PermissionDenied
62
from django.template.loader import render_to_string
63
from django.views.decorators.http import require_http_methods
64
from django.db.models import Q
65

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

    
102
import astakos.im.messages as astakos_messages
103
from astakos.im import settings
104
from astakos.im import auth_providers
105

    
106
logger = logging.getLogger(__name__)
107

    
108
callpoint = AstakosCallpoint()
109

    
110
def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
111
    """
112
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
113
    keyword argument and returns an ``django.http.HttpResponse`` with the
114
    specified ``status``.
115
    """
116
    if tab is None:
117
        tab = template.partition('_')[0].partition('.html')[0]
118
    kwargs.setdefault('tab', tab)
119
    html = template_loader.render_to_string(
120
        template, kwargs, context_instance=context_instance)
121
    response = HttpResponse(html, status=status)
122
    return response
123

    
124
def requires_auth_provider(provider_id, **perms):
125
    """
126
    """
127
    def decorator(func, *args, **kwargs):
128
        @wraps(func)
129
        def wrapper(request, *args, **kwargs):
130
            provider = auth_providers.get_provider(provider_id)
131

    
132
            if not provider or not provider.is_active():
133
                raise PermissionDenied
134

    
135
            if provider:
136
                for pkey, value in perms.iteritems():
137
                    attr = 'is_available_for_%s' % pkey.lower()
138
                    if getattr(provider, attr)() != value:
139
                        raise PermissionDenied
140
            return func(request, *args)
141
        return wrapper
142
    return decorator
143

    
144

    
145
def requires_anonymous(func):
146
    """
147
    Decorator checkes whether the request.user is not Anonymous and in that case
148
    redirects to `logout`.
149
    """
150
    @wraps(func)
151
    def wrapper(request, *args):
152
        if not request.user.is_anonymous():
153
            next = urlencode({'next': request.build_absolute_uri()})
154
            logout_uri = reverse(logout) + '?' + next
155
            return HttpResponseRedirect(logout_uri)
156
        return func(request, *args)
157
    return wrapper
158

    
159

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

    
175

    
176
@require_http_methods(["GET", "POST"])
177
@signed_terms_required
178
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
179
    """
180
    If there is logged on user renders the profile page otherwise renders login page.
181

182
    **Arguments**
183

184
    ``login_template_name``
185
        A custom login template to use. This is optional; if not specified,
186
        this will default to ``im/login.html``.
187

188
    ``profile_template_name``
189
        A custom profile template to use. This is optional; if not specified,
190
        this will default to ``im/profile.html``.
191

192
    ``extra_context``
193
        An dictionary of variables to add to the template context.
194

195
    **Template:**
196

197
    im/profile.html or im/login.html or ``template_name`` keyword argument.
198

199
    """
200
    extra_context = extra_context or {}
201
    template_name = login_template_name
202
    if request.user.is_authenticated():
203
        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
204

    
205
    return render_response(
206
        template_name,
207
        login_form = LoginForm(request=request),
208
        context_instance = get_context(request, extra_context)
209
    )
210

    
211

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

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

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

227
    If the user isn't logged in, redirects to settings.LOGIN_URL.
228

229
    **Arguments**
230

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

235
    ``extra_context``
236
        An dictionary of variables to add to the template context.
237

238
    **Template:**
239

240
    im/invitations.html or ``template_name`` keyword argument.
241

242
    **Settings:**
243

244
    The view expectes the following settings are defined:
245

246
    * LOGIN_URL: login uri
247
    """
248
    extra_context = extra_context or {}
249
    status = None
250
    message = None
251
    form = InvitationForm()
252

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

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

    
290

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

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

302
    If the user isn't logged in, redirects to settings.LOGIN_URL.
303

304
    **Arguments**
305

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

310
    ``extra_context``
311
        An dictionary of variables to add to the template context.
312

313
    **Template:**
314

315
    im/profile.html or ``template_name`` keyword argument.
316

317
    **Settings:**
318

319
    The view expectes the following settings are defined:
320

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

    
357
    # existing providers
358
    user_providers = request.user.get_active_auth_providers()
359

    
360
    # providers that user can add
361
    user_available_providers = request.user.get_available_auth_providers()
362

    
363
    return render_response(template_name,
364
                           profile_form = form,
365
                           user_providers = user_providers,
366
                           user_available_providers = user_available_providers,
367
                           context_instance = get_context(request,
368
                                                          extra_context))
369

    
370

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

377
    In case of GET request renders a form for entering the user information.
378
    In case of POST handles the signup.
379

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

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

388
    On unsuccessful creation, renders ``template_name`` with an error message.
389

390
    **Arguments**
391

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

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

400
    ``extra_context``
401
        An dictionary of variables to add to the template context.
402

403
    **Template:**
404

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

    
412
    provider = get_query(request).get('provider', 'local')
413
    if not auth_providers.get_provider(provider).is_available_for_create():
414
        raise PermissionDenied
415

    
416
    id = get_query(request).get('id')
417
    try:
418
        instance = AstakosUser.objects.get(id=id) if id else None
419
    except AstakosUser.DoesNotExist:
420
        instance = None
421

    
422
    third_party_token = request.REQUEST.get('third_party_token', None)
423

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

    
439
                form.store_user(user, request)
440

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

    
482

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

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

493
    If the user isn't logged in, redirects to settings.LOGIN_URL.
494

495
    **Arguments**
496

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

501
    ``extra_context``
502
        An dictionary of variables to add to the template context.
503

504
    **Template:**
505

506
    im/signup.html or ``template_name`` keyword argument.
507

508
    **Settings:**
509

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

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

    
534

    
535
@require_http_methods(["GET"])
536
@signed_terms_required
537
def logout(request, template='registration/logged_out.html', extra_context=None):
538
    """
539
    Wraps `django.contrib.auth.logout`.
540
    """
541
    extra_context = extra_context or {}
542
    response = HttpResponse()
543
    if request.user.is_authenticated():
544
        email = request.user.email
545
        auth_logout(request)
546
    next = restrict_next(
547
        request.GET.get('next'),
548
        domain=COOKIE_DOMAIN
549
    )
550
    if next:
551
        response['Location'] = next
552
        response.status_code = 302
553
    elif LOGOUT_NEXT:
554
        response['Location'] = LOGOUT_NEXT
555
        response.status_code = 301
556
    else:
557
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
558
        context = get_context(request, extra_context)
559
        response.write(render_to_string(template, context_instance=context))
560
    return response
561

    
562

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

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

    
581
    if user.is_active:
582
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
583
        messages.error(request, message)
584
        return index(request)
585

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

    
604

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

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

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

    
651

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

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

    
705

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

    
708
    if settings.MODERATION_ENABLED:
709
        raise PermissionDenied
710

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

    
732
class ResourcePresentation():
733

    
734
    def __init__(self, data):
735
        self.data = data
736

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

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

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

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

    
759

    
760
            self.data['resources'][rname]['value'] = value
761

    
762

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

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

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

    
778
    def get_group_resources(self, group):
779
        return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
780

    
781
    def get_groups_resources(self):
782
        for g in self.data['groups']:
783
            yield g, self.get_group_resources(g)
784

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

    
792

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

    
799
    def __repr__(self):
800
        return self.data.__repr__()
801

    
802
    def __iter__(self, *args, **kwargs):
803
        return self.data.__iter__(*args, **kwargs)
804

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

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

    
811

    
812

    
813
@require_http_methods(["GET", "POST"])
814
@signed_terms_required
815
@login_required
816
def group_add(request, kind_name='default'):
817
    result = callpoint.list_resources()
818
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
819
    resource_catalog.update_from_result(result)
820

    
821
    if not result.is_success:
822
        messages.error(
823
            request,
824
            'Unable to retrieve system resources: %s' % result.reason
825
    )
826

    
827
    try:
828
        kind = GroupKind.objects.get(name=kind_name)
829
    except:
830
        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
831

    
832

    
833

    
834
    post_save_redirect = '/im/group/%(id)s/'
835
    context_processors = None
836
    model, form_class = get_model_and_form_class(
837
        model=None,
838
        form_class=AstakosGroupCreationForm
839
    )
840

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

    
861
        form = form_class(data)
862

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

    
876

    
877
#@require_hsttp_methods(["POST"])
878
@require_http_methods(["GET", "POST"])
879
@signed_terms_required
880
@login_required
881
@transaction.commit_manually
882
def group_add_complete(request):
883
    model = AstakosGroup
884
    form = AstakosGroupCreationSummaryForm(request.POST)
885
    if form.is_valid():
886
        d = form.cleaned_data
887
        d['owners'] = [request.user]
888
        result = callpoint.create_groups((d,)).next()
889
        if result.is_success:
890
            new_object = result.data[0]
891
            # send notification
892
            try:
893
                send_group_creation_notification(
894
                    template_name='im/group_creation_notification.txt',
895
                    dictionary={
896
                        'group': new_object,
897
                        'owner': request.user,
898
                        'policies': d.get('policies', [])
899
                    }
900
                )
901
            except SendNotificationError, e:
902
                messages.error(request, e, fail_silently=True)
903
                transaction.rollback()
904
            else:
905
                msg = _(astakos_messages.OBJECT_CREATED) %\
906
                    {"verbose_name": model._meta.verbose_name}
907
                message = _(astakos_messages.NOTIFICATION_SENT) % 'a project'
908
                messages.success(request, msg, fail_silently=True)
909
                transaction.commit()
910
        else:
911
            d = {"verbose_name": model._meta.verbose_name,
912
                 "reason":result.reason}
913
            msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
914
            messages.error(request, msg, fail_silently=True)
915
    return render_response(
916
        template='im/astakosgroup_form_summary.html',
917
        context_instance=get_context(request),
918
        form=form,
919
        policies=form.cleaned_data.get('policies')
920
    )
921

    
922

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

    
956
    # validate sorting
957
    sorting = 'groupname'
958
    sort_form = AstakosGroupSortForm(request.GET)
959
    if sort_form.is_valid():
960
        sorting = sort_form.cleaned_data.get('sorting')
961
    query = query+" ORDER BY %s ASC" %sorting
962
    
963
    q = AstakosGroup.objects.raw(query)
964
    
965
    # Create the template, context, response
966
    template_name = "%s/%s_list.html" % (
967
        q.model._meta.app_label,
968
        q.model._meta.object_name.lower()
969
    )
970
    extra_context = dict(
971
        is_search=False,
972
        q=q,
973
        sorting=sorting,
974
        page=request.GET.get('page', 1)
975
    )
976
    return render_response(template_name,
977
                           context_instance=get_context(request, extra_context)
978
    )
979

    
980

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

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

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

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

    
1045
    # validate sorting
1046
    sorting = 'person__email'
1047
    form = MembersSortForm(request.GET)
1048
    if form.is_valid():
1049
        sorting = form.cleaned_data.get('sorting')
1050
    
1051
    result = callpoint.list_resources()
1052
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1053
    resource_catalog.update_from_result(result)
1054

    
1055

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

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

    
1078

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

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

    
1146

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

    
1194

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

    
1215

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

    
1237

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

    
1254

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

    
1275

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

    
1290

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

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

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

    
1337

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

    
1344

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

    
1382

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

    
1404

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

    
1435

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

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

    
1452

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

    
1458
@require_http_methods(["GET", "POST"])
1459
@signed_terms_required
1460
@login_required
1461
def project_add(request):
1462
    result = callpoint.list_resources()
1463
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1464
    resource_catalog.update_from_result(result)
1465

    
1466
    if not result.is_success:
1467
        messages.error(
1468
            request,
1469
            'Unable to retrieve system resources: %s' % result.reason
1470
    )
1471
    extra_context = {'resource_catalog':resource_catalog}
1472
    return create_object(request, template_name='im/projects/projectapplication_form.html',
1473
        extra_context=extra_context, post_save_redirect='/im/project/list/',
1474
        form_class=ProjectApplicationForm)
1475

    
1476
@require_http_methods(["GET"])
1477
@signed_terms_required
1478
@login_required
1479
def project_list(request):
1480
    q = ProjectApplication.objects.filter(owner=request.user)
1481
    q |= ProjectApplication.objects.filter(
1482
        project__in=request.user.projectmembership_set.values_list('project', flat=True)
1483
    )
1484
    return object_list(
1485
        request,
1486
        q,
1487
        paginate_by=PAGINATE_BY_ALL,
1488
        page=request.GET.get('page') or 1,
1489
        template_name='im/projects/project_list.html',
1490
        extra_context={
1491
            'is_search':False,
1492
            'sorting':request.GET.get('sorting'),
1493
        }
1494
    )
1495

    
1496

    
1497
@require_http_methods(["GET", "POST"])
1498
@signed_terms_required
1499
@login_required
1500
def project_application_detail(request, serial):
1501
    return object_detail(
1502
        request,
1503
        queryset=ProjectApplication.objects.select_related(), 
1504
        slug=serial,
1505
        slug_field='serial',
1506
        template_name='im/projects/projectapplication_detail.html'
1507
    )
1508

    
1509
@require_http_methods(["GET", "POST"])
1510
@signed_terms_required
1511
@login_required
1512
def project_detail(request, serial):
1513
    return object_detail(
1514
        request,
1515
        queryset=ProjectApplication.objects.select_related(),
1516
        slug=serial,
1517
        slug_field='serial',
1518
        template_name='im/projects/project_detail.html',
1519
        extra_context={
1520
            'sorting':request.GET.get('sorting', request.POST.get('sorting')),
1521
        }
1522
    )
1523

    
1524
@require_http_methods(["GET", "POST"])
1525
@signed_terms_required
1526
@login_required
1527
def project_search(request):
1528
    pass
1529

    
1530
@require_http_methods(["GET"])
1531
@signed_terms_required
1532
@login_required
1533
def project_all(request):
1534
    pass
1535

    
1536
@require_http_methods(["GET", "POST"])
1537
@signed_terms_required
1538
@login_required
1539
def project_join(request, serial):
1540
    pass
1541

    
1542
@require_http_methods(["GET", "POST"])
1543
@signed_terms_required
1544
@login_required
1545
def project_leave(request, serial):
1546
    pass
1547

    
1548
@require_http_methods(["POST"])
1549
@signed_terms_required
1550
@login_required
1551
def project_approve_member(request, serial, user_id):
1552
    pass
1553

    
1554
@require_http_methods(["POST"])
1555
@signed_terms_required
1556
@login_required
1557
def project_remove_member(request, serial, user_id):
1558
    pass
1559