Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 73fbaec4

History | View | Annotate | Download (62.9 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 (
50
    HttpResponse, HttpResponseBadRequest,
51
    HttpResponseForbidden, HttpResponseRedirect,
52
    HttpResponseBadRequest, Http404)
53
from django.shortcuts import redirect
54
from django.template import RequestContext, loader as template_loader
55
from django.utils.http import urlencode
56
from django.utils.translation import ugettext as _
57
from django.views.generic.create_update import (
58
    create_object, update_object, delete_object, get_model_and_form_class)
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,
70
#     AstakosGroup, Membership
71
    EmailChange, GroupKind,
72
    RESOURCE_SEPARATOR, AstakosUserAuthProvider,
73
    ProjectApplication, ProjectMembership, Project)
74
from astakos.im.util import (
75
    get_context, prepare_response, get_query, restrict_next)
76
from astakos.im.forms import (
77
    LoginForm, InvitationForm, ProfileForm,
78
    FeedbackForm, SignApprovalTermsForm,
79
    EmailChangeForm,
80
#     AstakosGroupCreationForm, AstakosGroupSearchForm,
81
#     AstakosGroupUpdateForm, AddGroupMembersForm,
82
#     MembersSortForm, AstakosGroupSortForm,
83
#     TimelineForm, PickResourceForm,
84
#     AstakosGroupCreationSummaryForm,
85
    ProjectApplicationForm, ProjectSortForm,
86
    AddProjectMembersForm, ProjectSearchForm,
87
    ProjectMembersSortForm)
88
from astakos.im.functions import (
89
    send_feedback, SendMailError,
90
    logout as auth_logout,
91
    activate as activate_func,
92
    invite,
93
    send_activation as send_activation_func,
94
#     send_group_creation_notification,
95
    SendNotificationError,
96
    accept_membership, reject_membership, remove_membership,
97
    leave_project, join_project)
98
# from astakos.im.endpoints.qh import timeline_charge
99
from astakos.im.settings import (
100
    COOKIE_DOMAIN, LOGOUT_NEXT,
101
    LOGGING_LEVEL, PAGINATE_BY,
102
    RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
103
#from astakos.im.tasks import request_billing
104
from astakos.im.api.callpoint import AstakosCallpoint
105

    
106
import astakos.im.messages as astakos_messages
107
from astakos.im import settings
108
from astakos.im import auth_providers
109

    
110
logger = logging.getLogger(__name__)
111

    
112
callpoint = AstakosCallpoint()
113

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

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

    
136
            if not provider or not provider.is_active():
137
                raise PermissionDenied
138

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

    
148

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

    
163

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

    
179

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

186
    **Arguments**
187

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

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

196
    ``extra_context``
197
        An dictionary of variables to add to the template context.
198

199
    **Template:**
200

201
    im/profile.html or im/login.html or ``template_name`` keyword argument.
202

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

    
209
    return render_response(
210
        template_name,
211
        login_form = LoginForm(request=request),
212
        context_instance = get_context(request, extra_context)
213
    )
214

    
215

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

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

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

231
    If the user isn't logged in, redirects to settings.LOGIN_URL.
232

233
    **Arguments**
234

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

239
    ``extra_context``
240
        An dictionary of variables to add to the template context.
241

242
    **Template:**
243

244
    im/invitations.html or ``template_name`` keyword argument.
245

246
    **Settings:**
247

248
    The view expectes the following settings are defined:
249

250
    * LOGIN_URL: login uri
251
    """
252
    extra_context = extra_context or {}
253
    status = None
254
    message = None
255
    form = InvitationForm()
256

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

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

    
294

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

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

306
    If the user isn't logged in, redirects to settings.LOGIN_URL.
307

308
    **Arguments**
309

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

314
    ``extra_context``
315
        An dictionary of variables to add to the template context.
316

317
    **Template:**
318

319
    im/profile.html or ``template_name`` keyword argument.
320

321
    **Settings:**
322

323
    The view expectes the following settings are defined:
324

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

    
361
    # existing providers
362
    user_providers = request.user.get_active_auth_providers()
363

    
364
    # providers that user can add
365
    user_available_providers = request.user.get_available_auth_providers()
366

    
367
    return render_response(template_name,
368
                           profile_form = form,
369
                           user_providers = user_providers,
370
                           user_available_providers = user_available_providers,
371
                           context_instance = get_context(request,
372
                                                          extra_context))
373

    
374

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

381
    In case of GET request renders a form for entering the user information.
382
    In case of POST handles the signup.
383

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

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

392
    On unsuccessful creation, renders ``template_name`` with an error message.
393

394
    **Arguments**
395

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

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

404
    ``extra_context``
405
        An dictionary of variables to add to the template context.
406

407
    **Template:**
408

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

    
416
    provider = get_query(request).get('provider', 'local')
417
    if not auth_providers.get_provider(provider).is_available_for_create():
418
        raise PermissionDenied
419

    
420
    id = get_query(request).get('id')
421
    try:
422
        instance = AstakosUser.objects.get(id=id) if id else None
423
    except AstakosUser.DoesNotExist:
424
        instance = None
425

    
426
    third_party_token = request.REQUEST.get('third_party_token', None)
427

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

    
443
                form.store_user(user, request)
444

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

    
486

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

494
    In case of GET request renders a form for providing the feedback information.
495
    In case of POST sends an email to support team.
496

497
    If the user isn't logged in, redirects to settings.LOGIN_URL.
498

499
    **Arguments**
500

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

505
    ``extra_context``
506
        An dictionary of variables to add to the template context.
507

508
    **Template:**
509

510
    im/signup.html or ``template_name`` keyword argument.
511

512
    **Settings:**
513

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

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

    
538

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

    
566

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

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

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

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

    
608

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

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

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

    
655

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

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

    
709

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

    
712
    if settings.MODERATION_ENABLED:
713
        raise PermissionDenied
714

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

    
736
class ResourcePresentation():
737

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

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

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

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

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

    
763

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

    
766

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

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

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

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

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

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

    
796

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

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

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

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

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

    
815

    
816

    
817
# @require_http_methods(["GET", "POST"])
818
# @signed_terms_required
819
# @login_required
820
# def group_add(request, kind_name='default'):
821
#     result = callpoint.list_resources()
822
#     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
823
#     resource_catalog.update_from_result(result)
824
# 
825
#     if not result.is_success:
826
#         messages.error(
827
#             request,
828
#             'Unable to retrieve system resources: %s' % result.reason
829
#     )
830
# 
831
#     try:
832
#         kind = GroupKind.objects.get(name=kind_name)
833
#     except:
834
#         return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
835
# 
836
# 
837
# 
838
#     post_save_redirect = '/im/group/%(id)s/'
839
#     context_processors = None
840
#     model, form_class = get_model_and_form_class(
841
#         model=None,
842
#         form_class=AstakosGroupCreationForm
843
#     )
844
# 
845
#     if request.method == 'POST':
846
#         form = form_class(request.POST, request.FILES)
847
#         if form.is_valid():
848
#             policies = form.policies()
849
#             return render_response(
850
#                 template='im/astakosgroup_form_summary.html',
851
#                 context_instance=get_context(request),
852
#                 form=AstakosGroupCreationSummaryForm(form.cleaned_data),
853
#                 policies=resource_catalog.get_policies(policies)
854
#             )
855
#     else:
856
#         now = datetime.now()
857
#         data = {
858
#             'kind': kind,
859
#         }
860
#         for group, resources in resource_catalog.get_groups_resources():
861
#             data['is_selected_%s' % group] = False
862
#             for resource in resources:
863
#                 data['%s_uplimit' % resource] = ''
864
# 
865
#         form = form_class(data)
866
# 
867
#     # Create the template, context, response
868
#     template_name = "%s/%s_form.html" % (
869
#         model._meta.app_label,
870
#         model._meta.object_name.lower()
871
#     )
872
#     t = template_loader.get_template(template_name)
873
#     c = RequestContext(request, {
874
#         'form': form,
875
#         'kind': kind,
876
#         'resource_catalog':resource_catalog,
877
#     }, context_processors)
878
#     return HttpResponse(t.render(c))
879

    
880

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

    
926

    
927
##@require_http_methods(["GET"])
928
# @require_http_methods(["GET", "POST"])
929
# @signed_terms_required
930
# @login_required
931
# def group_list(request):
932
#     none = request.user.astakos_groups.none()
933
#     query = """
934
#         SELECT auth_group.id,
935
#         auth_group.name AS groupname,
936
#         im_groupkind.name AS kindname,
937
#         im_astakosgroup.*,
938
#         owner.email AS groupowner,
939
#         (SELECT COUNT(*) FROM im_membership
940
#             WHERE group_id = im_astakosgroup.group_ptr_id
941
#             AND date_joined IS NOT NULL) AS approved_members_num,
942
#         (SELECT CASE WHEN(
943
#                     SELECT date_joined FROM im_membership
944
#                     WHERE group_id = im_astakosgroup.group_ptr_id
945
#                     AND person_id = %(id)s) IS NULL
946
#                     THEN 0 ELSE 1 END) AS membership_status
947
#         FROM im_astakosgroup
948
#         INNER JOIN im_membership ON (
949
#             im_astakosgroup.group_ptr_id = im_membership.group_id)
950
#         INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
951
#         INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
952
#         LEFT JOIN im_astakosuser_owner ON (
953
#             im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
954
#         LEFT JOIN auth_user as owner ON (
955
#             im_astakosuser_owner.astakosuser_id = owner.id)
956
#         WHERE im_membership.person_id = %(id)s
957
#         AND im_groupkind.name != 'default'
958
#         """ % request.user.__dict__
959
# 
960
#     # validate sorting
961
#     sorting = 'groupname'
962
#     sort_form = AstakosGroupSortForm(request.GET)
963
#     if sort_form.is_valid():
964
#         sorting = sort_form.cleaned_data.get('sorting')
965
#     query = query+" ORDER BY %s ASC" %sorting
966
#     
967
#     q = AstakosGroup.objects.raw(query)
968
#     
969
#     # Create the template, context, response
970
#     template_name = "%s/%s_list.html" % (
971
#         q.model._meta.app_label,
972
#         q.model._meta.object_name.lower()
973
#     )
974
#     extra_context = dict(
975
#         is_search=False,
976
#         q=q,
977
#         sorting=sorting,
978
#         page=request.GET.get('page', 1)
979
#     )
980
#     return render_response(template_name,
981
#                            context_instance=get_context(request, extra_context)
982
#     )
983

    
984

    
985
# @require_http_methods(["GET", "POST"])
986
# @signed_terms_required
987
# @login_required
988
# def group_detail(request, group_id):
989
#     q = AstakosGroup.objects.select_related().filter(pk=group_id)
990
#     q = q.extra(select={
991
#         'is_member': """SELECT CASE WHEN EXISTS(
992
#                             SELECT id FROM im_membership
993
#                             WHERE group_id = im_astakosgroup.group_ptr_id
994
#                             AND person_id = %s)
995
#                         THEN 1 ELSE 0 END""" % request.user.id,
996
#         'is_owner': """SELECT CASE WHEN EXISTS(
997
#                         SELECT id FROM im_astakosuser_owner
998
#                         WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
999
#                         AND astakosuser_id = %s)
1000
#                         THEN 1 ELSE 0 END""" % request.user.id,
1001
#         'is_active_member': """SELECT CASE WHEN(
1002
#                         SELECT date_joined FROM im_membership
1003
#                         WHERE group_id = im_astakosgroup.group_ptr_id
1004
#                         AND person_id = %s) IS NULL
1005
#                         THEN 0 ELSE 1 END""" % request.user.id,
1006
#         'kindname': """SELECT name FROM im_groupkind
1007
#                        WHERE id = im_astakosgroup.kind_id"""})
1008
# 
1009
#     model = q.model
1010
#     context_processors = None
1011
#     mimetype = None
1012
#     try:
1013
#         obj = q.get()
1014
#     except AstakosGroup.DoesNotExist:
1015
#         raise Http404("No %s found matching the query" % (
1016
#             model._meta.verbose_name))
1017
# 
1018
#     update_form = AstakosGroupUpdateForm(instance=obj)
1019
#     addmembers_form = AddGroupMembersForm()
1020
#     if request.method == 'POST':
1021
#         update_data = {}
1022
#         addmembers_data = {}
1023
#         for k, v in request.POST.iteritems():
1024
#             if k in update_form.fields:
1025
#                 update_data[k] = v
1026
#             if k in addmembers_form.fields:
1027
#                 addmembers_data[k] = v
1028
#         update_data = update_data or None
1029
#         addmembers_data = addmembers_data or None
1030
#         update_form = AstakosGroupUpdateForm(update_data, instance=obj)
1031
#         addmembers_form = AddGroupMembersForm(addmembers_data)
1032
#         if update_form.is_valid():
1033
#             update_form.save()
1034
#         if addmembers_form.is_valid():
1035
#             try:
1036
#                 map(obj.approve_member, addmembers_form.valid_users)
1037
#             except AssertionError:
1038
#                 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1039
#                 messages.error(request, msg)
1040
#             addmembers_form = AddGroupMembersForm()
1041
# 
1042
#     template_name = "%s/%s_detail.html" % (
1043
#         model._meta.app_label, model._meta.object_name.lower())
1044
#     t = template_loader.get_template(template_name)
1045
#     c = RequestContext(request, {
1046
#         'object': obj,
1047
#     }, context_processors)
1048
# 
1049
#     # validate sorting
1050
#     sorting = 'person__email'
1051
#     form = MembersSortForm(request.GET)
1052
#     if form.is_valid():
1053
#         sorting = form.cleaned_data.get('sorting')
1054
#     
1055
#     result = callpoint.list_resources()
1056
#     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1057
#     resource_catalog.update_from_result(result)
1058
# 
1059
# 
1060
#     if not result.is_success:
1061
#         messages.error(
1062
#             request,
1063
#             'Unable to retrieve system resources: %s' % result.reason
1064
#     )
1065
# 
1066
#     extra_context = {'update_form': update_form,
1067
#                      'addmembers_form': addmembers_form,
1068
#                      'page': request.GET.get('page', 1),
1069
#                      'sorting': sorting,
1070
#                      'resource_catalog':resource_catalog,
1071
#                      'quota':resource_catalog.get_quota(obj.quota)}
1072
#     for key, value in extra_context.items():
1073
#         if callable(value):
1074
#             c[key] = value()
1075
#         else:
1076
#             c[key] = value
1077
#     response = HttpResponse(t.render(c), mimetype=mimetype)
1078
#     populate_xheaders(
1079
#         request, response, model, getattr(obj, obj._meta.pk.name))
1080
#     return response
1081

    
1082

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

    
1150

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

    
1198

    
1199
##@require_http_methods(["POST"])
1200
# @require_http_methods(["POST", "GET"])
1201
# @signed_terms_required
1202
# @login_required
1203
# def group_join(request, group_id):
1204
#     m = Membership(group_id=group_id,
1205
#                    person=request.user,
1206
#                    date_requested=datetime.now())
1207
#     try:
1208
#         m.save()
1209
#         post_save_redirect = reverse(
1210
#             'group_detail',
1211
#             kwargs=dict(group_id=group_id))
1212
#         return HttpResponseRedirect(post_save_redirect)
1213
#     except IntegrityError, e:
1214
#         logger.exception(e)
1215
#         msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1216
#         messages.error(request, msg)
1217
#         return group_search(request)
1218
# 
1219
# 
1220
# @require_http_methods(["POST"])
1221
# @signed_terms_required
1222
# @login_required
1223
# def group_leave(request, group_id):
1224
#     try:
1225
#         m = Membership.objects.select_related().get(
1226
#             group__id=group_id,
1227
#             person=request.user)
1228
#     except Membership.DoesNotExist:
1229
#         return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1230
#     if request.user in m.group.owner.all():
1231
#         return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1232
#     return delete_object(
1233
#         request,
1234
#         model=Membership,
1235
#         object_id=m.id,
1236
#         template_name='im/astakosgroup_list.html',
1237
#         post_delete_redirect=reverse(
1238
#             'group_detail',
1239
#             kwargs=dict(group_id=group_id)))
1240

    
1241

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

    
1258

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

    
1279

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

    
1294

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

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

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

    
1341

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

    
1348

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

    
1386

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

    
1408

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

    
1439

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

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

    
1456

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

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

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

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

    
1500

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

    
1528
@require_http_methods(["GET", "POST"])
1529
@signed_terms_required
1530
@login_required
1531
def project_update(request, application_id):
1532
    result = callpoint.list_resources()
1533
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1534
    resource_catalog.update_from_result(result)
1535

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

    
1550

    
1551
@require_http_methods(["GET", "POST"])
1552
@signed_terms_required
1553
@login_required
1554
@transaction.commit_manually
1555
def project_detail(request, application_id):
1556
    result = callpoint.list_resources()
1557
    resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1558
    resource_catalog.update_from_result(result)
1559

    
1560
    addmembers_form = AddProjectMembersForm()
1561
    if request.method == 'POST':
1562
        addmembers_form = AddProjectMembersForm(request.POST)
1563
        if addmembers_form.is_valid():
1564
            try:
1565
                rollback = False
1566
                map(lambda u: accept_membership(
1567
                        application_id,
1568
                        u,
1569
                        request_user=request.user),
1570
                    addmembers_form.valid_users)
1571
            except (IOError, PermissionDenied), e:
1572
                messages.error(request, e)
1573
            except BaseException, e:
1574
                rollback = True
1575
                messages.error(request, e)
1576
            finally:
1577
                if rollback == True:
1578
                    transaction.rollback()
1579
                else:
1580
                    transaction.commit()
1581
            addmembers_form = AddProjectMembersForm()
1582
    
1583
    # validate sorting
1584
    sorting = 'person__email'
1585
    form = ProjectMembersSortForm(request.GET or request.POST)
1586
    if form.is_valid():
1587
        sorting = form.cleaned_data.get('sorting')
1588

    
1589
    return object_detail(
1590
        request,
1591
        queryset=ProjectApplication.objects.select_related(),
1592
        object_id=application_id,
1593
        template_name='im/projects/project_detail.html',
1594
        extra_context={
1595
            'resource_catalog':resource_catalog,
1596
            'sorting':sorting,
1597
            'addmembers_form':addmembers_form
1598
        }
1599
    )
1600

    
1601
@require_http_methods(["GET", "POST"])
1602
@signed_terms_required
1603
@login_required
1604
def project_search(request):
1605
    queryset = ProjectApplication.objects
1606
    if request.method == 'GET':
1607
        form = ProjectSearchForm()
1608
        queryset = queryset.none()
1609
    else:
1610
        form = ProjectSearchForm(request.POST)
1611
        if form.is_valid():
1612
            q = form.cleaned_data['q'].strip()
1613
            queryset = queryset.filter(~Q(project__last_approval_date__isnull=True))
1614
            queryset = queryset.filter(name__contains=q)
1615
    sorting = 'name'        
1616
    # validate sorting
1617
    sort_form = ProjectSortForm(request.GET)
1618
    if sort_form.is_valid():
1619
        sorting = sort_form.cleaned_data.get('sorting')
1620
    queryset = queryset.order_by(sorting)
1621
    return object_list(
1622
        request,
1623
        queryset,
1624
        paginate_by=PAGINATE_BY_ALL,
1625
        page=request.GET.get('page') or 1,
1626
        template_name='im/projects/project_list.html',
1627
        extra_context=dict(
1628
            form=form,
1629
            is_search=True,
1630
            sorting=sorting
1631
        )
1632
    )
1633

    
1634

    
1635
@require_http_methods(["GET"])
1636
@signed_terms_required
1637
@login_required
1638
def project_all(request):
1639
    q = ProjectApplication.objects.filter(
1640
        ~Q(project__last_approval_date__isnull=True))
1641
    q = q.select_related()
1642
    sorting = 'name'
1643
    sort_form = ProjectSortForm(request.GET)
1644
    if sort_form.is_valid():
1645
        sorting = sort_form.cleaned_data.get('sorting')
1646
    q = q.order_by(sorting)
1647
    
1648
    return object_list(
1649
        request,
1650
        q,
1651
        paginate_by=PAGINATE_BY_ALL,
1652
        page=request.GET.get('page') or 1,
1653
        template_name='im/projects/project_list.html',
1654
        extra_context={
1655
            'form':ProjectSearchForm(),
1656
            'is_search':True,
1657
            'sorting':sorting
1658
        }
1659
    )
1660

    
1661
@require_http_methods(["POST"])
1662
@signed_terms_required
1663
@login_required
1664
@transaction.commit_manually
1665
def project_join(request, application_id):
1666
    next = request.POST.get('next')
1667
    if not next:
1668
        return HttpResponseBadRequest(
1669
            _(astakos_messages.MISSING_NEXT_PARAMETER))
1670

    
1671
    rollback = False
1672
    try:
1673
        join_project(application_id, request.user)
1674
    except (IOError, PermissionDenied), e:
1675
        messages.error(request, e)
1676
    except BaseException, e:
1677
        logger.exception(e)
1678
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1679
        rollback = True
1680
    else:
1681
        return project_detail(request, id)
1682
    finally:
1683
        if rollback:
1684
            transaction.rollback()
1685
        else:
1686
            transaction.commit()
1687

    
1688
    next = restrict_next(
1689
        request.POST.get('next'),
1690
        domain=COOKIE_DOMAIN)
1691
    return redirect(next)
1692

    
1693
@require_http_methods(["POST"])
1694
@signed_terms_required
1695
@login_required
1696
@transaction.commit_manually
1697
def project_leave(request, application_id):
1698
    next = request.POST.get('next')
1699
    if not next:
1700
        return HttpResponseBadRequest(
1701
            _(astakos_messages.MISSING_NEXT_PARAMETER))
1702

    
1703
    rollback = False
1704
    try:
1705
        leave_project(application_id, request.user)
1706
    except (IOError, PermissionDenied), e:
1707
        messages.error(request, e)
1708
    except BaseException, e:
1709
        logger.exception(e)
1710
        messages.error(_(astakos_messages.GENERIC_ERRO))
1711
        rollback = True
1712
    finally:
1713
        if rollback:
1714
            transaction.rollback()
1715
        else:
1716
            transaction.commit()
1717
    
1718
    next = restrict_next(
1719
        request.POST.get('next'),
1720
        domain=COOKIE_DOMAIN)
1721
    return redirect(next)
1722

    
1723
@require_http_methods(["GET"])
1724
@signed_terms_required
1725
@login_required
1726
@transaction.commit_manually
1727
def project_approve_member(request, application_id, user_id):
1728
    rollback = False
1729
    try:
1730
        m = accept_membership(application_id, user_id, request.user)
1731
    except (IOError, PermissionDenied), e:
1732
        messages.error(request, e)
1733
    except BaseException, e:
1734
        logger.exception(e)
1735
        messages.error(_(astakos_messages.GENERIC_ERRO))
1736
        rollback = True
1737
    else:
1738
        realname = m.person.realname
1739
        msg = _(astakos_messages.USER_JOINED_PROJECT) % locals()
1740
        messages.success(request, msg)
1741
    finally:
1742
        if rollback:
1743
            transaction.rollback()
1744
        else:
1745
            transaction.commit()
1746
    return project_detail(request, application_id)
1747

    
1748
@require_http_methods(["GET"])
1749
@signed_terms_required
1750
@login_required
1751
@transaction.commit_manually
1752
def project_remove_member(request, application_id, user_id):
1753
    rollback = False
1754
    try:
1755
        m = remove_membership(application_id, user_id, request.user)
1756
    except (IOError, PermissionDenied), e:
1757
        messages.error(request, e)
1758
    except BaseException, e:
1759
        logger.exception(e)
1760
        messages.error(_(astakos_messages.GENERIC_ERRO))
1761
        rollback = True
1762
    else:
1763
        realname = m.person.realname
1764
        msg = _(astakos_messages.USER_LEFT_PROJECT) % locals()
1765
        messages.success(request, msg)
1766
    finally:
1767
        if rollback:
1768
            transaction.rollback()
1769
        else:
1770
            transaction.commit()
1771
    return project_detail(request, application_id)
1772

    
1773
@require_http_methods(["GET"])
1774
@signed_terms_required
1775
@login_required
1776
@transaction.commit_manually
1777
def project_reject_member(request, application_id, user_id):
1778
    rollback = False
1779
    try:
1780
        m = reject_membership(application_id, user_id, request.user)
1781
    except (IOError, PermissionDenied), e:
1782
        messages.error(request, e)
1783
    except BaseException, e:
1784
        logger.exception(e)
1785
        messages.error(_(astakos_messages.GENERIC_ERRO))
1786
        rollback = True
1787
    else:
1788
        realname = m.person.realname
1789
        msg = _(astakos_messages.USER_LEFT_PROJECT) % locals()
1790
        messages.success(request, msg)
1791
    finally:
1792
        if rollback:
1793
            transaction.rollback()
1794
        else:
1795
            transaction.commit()
1796
    return project_detail(request, application_id)