Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (60.8 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, update_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
from django.core.exceptions import PermissionDenied
66

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

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

    
107
logger = logging.getLogger(__name__)
108

    
109
callpoint = AstakosCallpoint()
110

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

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

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

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

    
145

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

    
160

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

    
176

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

183
    **Arguments**
184

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

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

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

196
    **Template:**
197

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

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

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

    
212

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

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

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

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

230
    **Arguments**
231

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

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

239
    **Template:**
240

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

243
    **Settings:**
244

245
    The view expectes the following settings are defined:
246

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

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

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

    
291

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

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

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

305
    **Arguments**
306

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

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

314
    **Template:**
315

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

318
    **Settings:**
319

320
    The view expectes the following settings are defined:
321

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

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

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

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

    
371

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

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

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

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

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

391
    **Arguments**
392

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

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

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

404
    **Template:**
405

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

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

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

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

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

    
440
                form.store_user(user, request)
441

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

    
483

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

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

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

496
    **Arguments**
497

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

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

505
    **Template:**
506

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

509
    **Settings:**
510

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

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

    
535

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

    
563

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

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

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

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

    
605

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

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

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

    
652

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

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

    
706

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

    
709
    if settings.MODERATION_ENABLED:
710
        raise PermissionDenied
711

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

    
733
class ResourcePresentation():
734

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

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

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

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

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

    
760

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

    
763

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

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

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

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

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

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

    
793

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

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

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

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

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

    
812

    
813

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

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

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

    
833

    
834

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

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

    
862
        form = form_class(data)
863

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

    
877

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

    
923

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

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

    
981

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

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

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

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

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

    
1056

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

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

    
1079

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

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

    
1147

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

    
1195

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

    
1216

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

    
1238

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

    
1255

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

    
1276

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

    
1291

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

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

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

    
1338

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

    
1345

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

    
1383

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

    
1405

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

    
1436

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

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

    
1453

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

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

    
1469
    if not result.is_success:
1470
        messages.error(
1471
            request,
1472
            'Unable to retrieve system resources: %s' % result.reason
1473
    )
1474
    extra_context = {'resource_catalog':resource_catalog}
1475

    
1476
    try:
1477
        r = create_object(request, template_name='im/projects/projectapplication_form.html',
1478
            extra_context=extra_context, post_save_redirect='/im/project/list/',
1479
            form_class=ProjectApplicationForm)
1480
        return r
1481
    except BaseException, e:
1482
        logger.exception(e)
1483
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1484
        rollback = True
1485
        return render_response(
1486
            'im/projects/projectapplication_form.html',
1487
            sorting = 'definition__name',
1488
            form = ProjectApplicationForm(),
1489
            context_instance=get_context(request, extra_context)
1490
        )
1491
    finally:
1492
        if rollback:
1493
            transaction.rollback()
1494
        else:
1495
            transaction.commit()
1496

    
1497

    
1498
@require_http_methods(["GET"])
1499
@signed_terms_required
1500
@login_required
1501
def project_list(request):
1502
    q = ProjectApplication.objects.filter(owner=request.user)
1503
    q |= ProjectApplication.objects.filter(
1504
        project__in=request.user.projectmembership_set.values_list('project', flat=True)
1505
    )
1506
    q = q.select_related()
1507
    sorting = 'definition__name'
1508
    sort_form = ProjectSortForm(request.GET)
1509
    if sort_form.is_valid():
1510
        sorting = sort_form.cleaned_data.get('sorting')
1511
    q = q.order_by(sorting)
1512
    
1513
    return object_list(
1514
        request,
1515
        q,
1516
        paginate_by=PAGINATE_BY_ALL,
1517
        page=request.GET.get('page') or 1,
1518
        template_name='im/projects/project_list.html',
1519
        extra_context={
1520
            'is_search':False,
1521
            'sorting':sorting
1522
        }
1523
    )
1524

    
1525
@require_http_methods(["GET", "POST"])
1526
@signed_terms_required
1527
@login_required
1528
def project_update(request, id):
1529
    result = callpoint.list_resources()
1530
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1531
    resource_catalog.update_from_result(result)
1532

    
1533
    if not result.is_success:
1534
        messages.error(
1535
            request,
1536
            'Unable to retrieve system resources: %s' % result.reason
1537
    )
1538
    extra_context = {'resource_catalog':resource_catalog}
1539
    return update_object(
1540
        request,
1541
        slug=id,
1542
        slug_field='projectapplication__id',
1543
        template_name='im/projects/projectapplication_form.html',
1544
        extra_context=extra_context, post_save_redirect='/im/project/list/',
1545
        form_class=ProjectApplicationForm)
1546

    
1547

    
1548
@require_http_methods(["GET", "POST"])
1549
@signed_terms_required
1550
@login_required
1551
def project_detail(request, id):
1552
    result = callpoint.list_resources()
1553
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1554
    resource_catalog.update_from_result(result)
1555

    
1556
    addmembers_form = AddProjectMembersForm()
1557
    if request.method == 'POST':
1558
        addmembers_form = AddProjectMembersForm(request.POST)
1559
        if addmembers_form.is_valid():
1560
            try:
1561
                obj = ProjectApplication.objects.get(id=id)
1562
                map(obj.project.accept_member, addmembers_form.valid_users)
1563
            except ProjectApplication.DoesNotExist, e:
1564
                messages.error(request, _(astakos_messages.UNKNOWN_IDENTIFIER))
1565
            except BaseException, e:
1566
                messages.error(request, e)
1567
            addmembers_form = AddProjectMembersForm()
1568
    
1569
    # validate sorting
1570
    sorting = 'person__email'
1571
    form = MembersSortForm(request.GET or request.POST)
1572
    if form.is_valid():
1573
        sorting = form.cleaned_data.get('sorting')
1574

    
1575
    return object_detail(
1576
        request,
1577
        queryset=ProjectApplication.objects.select_related(),
1578
        object_id=id,
1579
        template_name='im/projects/project_detail.html',
1580
        extra_context={
1581
            'resource_catalog':resource_catalog,
1582
            'sorting':sorting,
1583
            'addmembers_form':addmembers_form
1584
        }
1585
    )
1586

    
1587
@require_http_methods(["GET", "POST"])
1588
@signed_terms_required
1589
@login_required
1590
def project_search(request):
1591
    queryset = ProjectApplication.objects
1592
    if request.method == 'GET':
1593
        form = ProjectSearchForm()
1594
        queryset = queryset.none()
1595
    else:
1596
        form = ProjectSearchForm(request.POST)
1597
        if form.is_valid():
1598
            q = form.cleaned_data['q'].strip()
1599
            queryset = queryset.filter(~Q(project__last_approval_date__isnull=True))
1600
            queryset = queryset.filter(definition__name__contains=q)
1601
    sorting = 'definition__name'        
1602
    # validate sorting
1603
    sort_form = AstakosGroupSortForm(request.GET)
1604
    if sort_form.is_valid():
1605
        sorting = sort_form.cleaned_data.get('sorting')
1606
    queryset = queryset.order_by(sorting)
1607
    return object_list(
1608
        request,
1609
        queryset,
1610
        paginate_by=PAGINATE_BY_ALL,
1611
        page=request.GET.get('page') or 1,
1612
        template_name='im/projects/project_list.html',
1613
        extra_context=dict(
1614
            form=form,
1615
            is_search=True,
1616
            sorting=sorting
1617
        )
1618
    )
1619

    
1620

    
1621
@require_http_methods(["GET"])
1622
@signed_terms_required
1623
@login_required
1624
def project_all(request):
1625
    q = ProjectApplication.objects.filter(~Q(project__last_approval_date__isnull=True))
1626
    q = q.select_related()
1627
    sorting = 'definition__name'
1628
    sort_form = ProjectSortForm(request.GET)
1629
    if sort_form.is_valid():
1630
        sorting = sort_form.cleaned_data.get('sorting')
1631
    q = q.order_by(sorting)
1632
    
1633
    return object_list(
1634
        request,
1635
        q,
1636
        paginate_by=PAGINATE_BY_ALL,
1637
        page=request.GET.get('page') or 1,
1638
        template_name='im/projects/project_list.html',
1639
        extra_context={
1640
            'form':ProjectSearchForm(),
1641
            'is_search':True,
1642
            'sorting':sorting
1643
        }
1644
    )
1645

    
1646
@require_http_methods(["GET", "POST"])
1647
@signed_terms_required
1648
@login_required
1649
@transaction.commit_manually
1650
def project_join(request, id):
1651
    rollback = False
1652
    try:
1653
        project = Project.objects.get(application__id=id)
1654
        m = ProjectMembership(
1655
            project=project,
1656
            person=request.user,
1657
            request_date=datetime.now())
1658
        m.save()
1659
    except Project.DoesNotExist, e:
1660
        msg = _(astakos_messages.UNKNOWN_IDENTIFIER)
1661
        messages.error(request, msg)
1662
    except IntegrityError, e:
1663
        logger.exception(e)
1664
        msg = _(astakos_messages.MEMBERSHIP_REQUEST_EXISTS)
1665
        messages.error(request, msg)
1666
    except PermissionDenied, e:
1667
        messages.error(request, e)
1668
    except BaseException, e:
1669
        logger.exception(e)
1670
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1671
        rollback = True
1672
    else:
1673
        return project_detail(request, id)
1674
    finally:
1675
        if rollback:
1676
            transaction.rollback()
1677
        else:
1678
            transaction.commit()
1679
    return project_search(request)
1680

    
1681

    
1682
@transaction.commit_manually
1683
def handle_project_membership(func):
1684
    @wraps(func)
1685
    def wrapper(request, id, user_id=None):
1686
        rollback = False
1687
        if not user_id:
1688
            user_id = request.user.id
1689
        try:
1690
            m = ProjectMembership.objects.select_related().get(
1691
                project__application__id=id,
1692
                person__id=user_id)
1693
        except AstakosUser.DoesNotExist:
1694
            msg = _(astakos_messages.ACCOUNT_UNKNOWN)
1695
            messages.error(request, msg)
1696
        except ProjectMembership.DoesNotExist:
1697
            msg = _(astakos_messages.NOT_MEMBER)
1698
            messages.error(request, msg)
1699
        else:
1700
            try:
1701
                func(request, m)
1702
            except PermissionDenied, e:
1703
                messages.error(request, e)
1704
            except BaseException, e:
1705
                logger.exception(e)
1706
                messages.error(_(astakos_messages.GENERIC_ERROR ))
1707
                rollback = True
1708
        finally:
1709
            if rollback:
1710
                transaction.rollback()
1711
            else:
1712
                transaction.commit()
1713
        return project_detail(request, id)
1714
    return wrapper
1715

    
1716

    
1717
@require_http_methods(["GET", "POST"])
1718
@signed_terms_required
1719
@login_required
1720
@handle_project_membership
1721
def project_leave(request, m):
1722
    m.leave()
1723

    
1724

    
1725
@require_http_methods(["GET"])
1726
@signed_terms_required
1727
@login_required
1728
@handle_project_membership
1729
def project_approve_member(request, m):
1730
    m.accept(request_user=request.user)
1731
    realname = m.person.realname
1732
    msg = _(astakos_messages.USER_JOINED_GROUP) % locals()
1733
    messages.success(request, msg)
1734

    
1735

    
1736
@require_http_methods(["GET"])
1737
@signed_terms_required
1738
@login_required
1739
@handle_project_membership
1740
def project_remove_member(request, m):
1741
    m.remove(request_user=request.user)
1742
    realname = m.person.realname
1743
    msg = _(astakos_messages.USER_LEFT_GROUP) % locals()
1744
    messages.success(request, msg)
1745

    
1746

    
1747
@require_http_methods(["GET"])
1748
@signed_terms_required
1749
@login_required
1750
@handle_project_membership
1751
def project_reject_member(request, m):
1752
    m.remove(request_user=request.user)
1753
    realname = m.person.realname
1754
    msg = _(astakos_messages.USER_MEMBERSHIP_REJECTED) % locals()
1755
    messages.success(request, msg)