Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 4e748491

History | View | Annotate | Download (67.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.shortcuts import get_object_or_404
45
from django.contrib import messages
46
from django.contrib.auth.decorators import login_required
47
from django.core.urlresolvers import reverse
48
from django.db import transaction
49
from django.db.utils import IntegrityError
50
from django.http import (
51
    HttpResponse, HttpResponseBadRequest,
52
    HttpResponseForbidden, HttpResponseRedirect,
53
    HttpResponseBadRequest, Http404)
54
from django.shortcuts import redirect
55
from django.template import RequestContext, loader as template_loader
56
from django.utils.http import urlencode
57
from django.utils.translation import ugettext as _
58
from django.views.generic.create_update import (
59
    apply_extra_context, lookup_object, delete_object, get_model_and_form_class)
60
from django.views.generic.list_detail import object_list, object_detail
61
from django.core.xheaders import populate_xheaders
62
from django.core.exceptions import ValidationError, PermissionDenied
63
from django.template.loader import render_to_string
64
from django.views.decorators.http import require_http_methods
65
from django.db.models import Q
66
from django.core.exceptions import PermissionDenied
67

    
68
import astakos.im.messages as astakos_messages
69

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

    
109
from astakos.im import settings
110
from astakos.im import auth_providers
111

    
112
logger = logging.getLogger(__name__)
113

    
114
callpoint = AstakosCallpoint()
115

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

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

    
138
            if not provider or not provider.is_active():
139
                raise PermissionDenied
140

    
141
            if provider:
142
                for pkey, value in perms.iteritems():
143
                    attr = 'is_available_for_%s' % pkey.lower()
144
                    if getattr(provider, attr)() != value:
145
                        #TODO: add session message
146
                        return HttpResponseRedirect(reverse('login'))
147
            return func(request, *args)
148
        return wrapper
149
    return decorator
150

    
151

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

    
166

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

    
182

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

189
    **Arguments**
190

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

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

199
    ``extra_context``
200
        An dictionary of variables to add to the template context.
201

202
    **Template:**
203

204
    im/profile.html or im/login.html or ``template_name`` keyword argument.
205

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

    
212
    return render_response(
213
        template_name,
214
        login_form = LoginForm(request=request),
215
        context_instance = get_context(request, extra_context)
216
    )
217

    
218

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

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

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

234
    If the user isn't logged in, redirects to settings.LOGIN_URL.
235

236
    **Arguments**
237

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

242
    ``extra_context``
243
        An dictionary of variables to add to the template context.
244

245
    **Template:**
246

247
    im/invitations.html or ``template_name`` keyword argument.
248

249
    **Settings:**
250

251
    The view expectes the following settings are defined:
252

253
    * LOGIN_URL: login uri
254
    """
255
    extra_context = extra_context or {}
256
    status = None
257
    message = None
258
    form = InvitationForm()
259

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

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

    
297

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

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

309
    If the user isn't logged in, redirects to settings.LOGIN_URL.
310

311
    **Arguments**
312

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

317
    ``extra_context``
318
        An dictionary of variables to add to the template context.
319

320
    **Template:**
321

322
    im/profile.html or ``template_name`` keyword argument.
323

324
    **Settings:**
325

326
    The view expectes the following settings are defined:
327

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

    
364
    # existing providers
365
    user_providers = request.user.get_active_auth_providers()
366

    
367
    # providers that user can add
368
    user_available_providers = request.user.get_available_auth_providers()
369

    
370
    return render_response(template_name,
371
                           profile_form = form,
372
                           user_providers = user_providers,
373
                           user_available_providers = user_available_providers,
374
                           context_instance = get_context(request,
375
                                                          extra_context))
376

    
377

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

384
    In case of GET request renders a form for entering the user information.
385
    In case of POST handles the signup.
386

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

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

395
    On unsuccessful creation, renders ``template_name`` with an error message.
396

397
    **Arguments**
398

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

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

407
    ``extra_context``
408
        An dictionary of variables to add to the template context.
409

410
    **Template:**
411

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

    
419
    provider = get_query(request).get('provider', 'local')
420
    if not auth_providers.get_provider(provider).is_available_for_create():
421
        raise PermissionDenied
422

    
423
    id = get_query(request).get('id')
424
    try:
425
        instance = AstakosUser.objects.get(id=id) if id else None
426
    except AstakosUser.DoesNotExist:
427
        instance = None
428

    
429
    third_party_token = request.REQUEST.get('third_party_token', None)
430
    if third_party_token:
431
        pending = get_object_or_404(PendingThirdPartyUser,
432
                                    token=third_party_token)
433
        provider = pending.provider
434
        instance = pending.get_user_instance()
435

    
436
    try:
437
        if not backend:
438
            backend = get_backend(request)
439
        form = backend.get_signup_form(provider, instance)
440
    except Exception, e:
441
        form = SimpleBackend(request).get_signup_form(provider)
442
        messages.error(request, e)
443
    if request.method == 'POST':
444
        if form.is_valid():
445
            user = form.save(commit=False)
446
            try:
447
                result = backend.handle_activation(user)
448
                status = messages.SUCCESS
449
                message = result.message
450

    
451
                form.store_user(user, request)
452

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

    
494

    
495
@require_http_methods(["GET", "POST"])
496
@login_required
497
@signed_terms_required
498
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
499
    """
500
    Allows a user to send feedback.
501

502
    In case of GET request renders a form for providing the feedback information.
503
    In case of POST sends an email to support team.
504

505
    If the user isn't logged in, redirects to settings.LOGIN_URL.
506

507
    **Arguments**
508

509
    ``template_name``
510
        A custom template to use. This is optional; if not specified,
511
        this will default to ``im/feedback.html``.
512

513
    ``extra_context``
514
        An dictionary of variables to add to the template context.
515

516
    **Template:**
517

518
    im/signup.html or ``template_name`` keyword argument.
519

520
    **Settings:**
521

522
    * LOGIN_URL: login uri
523
    """
524
    extra_context = extra_context or {}
525
    if request.method == 'GET':
526
        form = FeedbackForm()
527
    if request.method == 'POST':
528
        if not request.user:
529
            return HttpResponse('Unauthorized', status=401)
530

    
531
        form = FeedbackForm(request.POST)
532
        if form.is_valid():
533
            msg = form.cleaned_data['feedback_msg']
534
            data = form.cleaned_data['feedback_data']
535
            try:
536
                send_feedback(msg, data, request.user, email_template_name)
537
            except SendMailError, e:
538
                messages.error(request, message)
539
            else:
540
                message = _(astakos_messages.FEEDBACK_SENT)
541
                messages.success(request, message)
542
    return render_response(template_name,
543
                           feedback_form=form,
544
                           context_instance=get_context(request, extra_context))
545

    
546

    
547
@require_http_methods(["GET"])
548
@signed_terms_required
549
def logout(request, template='registration/logged_out.html', extra_context=None):
550
    """
551
    Wraps `django.contrib.auth.logout`.
552
    """
553
    extra_context = extra_context or {}
554
    response = HttpResponse()
555
    if request.user.is_authenticated():
556
        email = request.user.email
557
        auth_logout(request)
558
    else:
559
        response['Location'] = reverse('index')
560
        response.status_code = 301
561
        return response
562

    
563
    next = restrict_next(
564
        request.GET.get('next'),
565
        domain=COOKIE_DOMAIN
566
    )
567

    
568
    if next:
569
        response['Location'] = next
570
        response.status_code = 302
571
    elif LOGOUT_NEXT:
572
        response['Location'] = LOGOUT_NEXT
573
        response.status_code = 301
574
    else:
575
        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
576
        response['Location'] = reverse('index')
577
        response.status_code = 301
578
    return response
579

    
580

    
581
@require_http_methods(["GET", "POST"])
582
@transaction.commit_manually
583
def activate(request, greeting_email_template_name='im/welcome_email.txt',
584
             helpdesk_email_template_name='im/helpdesk_notification.txt'):
585
    """
586
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
587
    and renews the user token.
588

589
    The view uses commit_manually decorator in order to ensure the user state will be updated
590
    only if the email will be send successfully.
591
    """
592
    token = request.GET.get('auth')
593
    next = request.GET.get('next')
594
    try:
595
        user = AstakosUser.objects.get(auth_token=token)
596
    except AstakosUser.DoesNotExist:
597
        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
598

    
599
    if user.is_active:
600
        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
601
        messages.error(request, message)
602
        return index(request)
603

    
604
    try:
605
        activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
606
        response = prepare_response(request, user, next, renew=True)
607
        transaction.commit()
608
        return response
609
    except SendMailError, e:
610
        message = e.message
611
        messages.add_message(request, messages.ERROR, message)
612
        transaction.rollback()
613
        return index(request)
614
    except BaseException, e:
615
        status = messages.ERROR
616
        message = _(astakos_messages.GENERIC_ERROR)
617
        messages.add_message(request, messages.ERROR, message)
618
        logger.exception(e)
619
        transaction.rollback()
620
        return index(request)
621

    
622

    
623
@require_http_methods(["GET", "POST"])
624
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
625
    extra_context = extra_context or {}
626
    term = None
627
    terms = None
628
    if not term_id:
629
        try:
630
            term = ApprovalTerms.objects.order_by('-id')[0]
631
        except IndexError:
632
            pass
633
    else:
634
        try:
635
            term = ApprovalTerms.objects.get(id=term_id)
636
        except ApprovalTerms.DoesNotExist, e:
637
            pass
638

    
639
    if not term:
640
        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
641
        return HttpResponseRedirect(reverse('index'))
642
    f = open(term.location, 'r')
643
    terms = f.read()
644

    
645
    if request.method == 'POST':
646
        next = restrict_next(
647
            request.POST.get('next'),
648
            domain=COOKIE_DOMAIN
649
        )
650
        if not next:
651
            next = reverse('index')
652
        form = SignApprovalTermsForm(request.POST, instance=request.user)
653
        if not form.is_valid():
654
            return render_response(template_name,
655
                                   terms=terms,
656
                                   approval_terms_form=form,
657
                                   context_instance=get_context(request, extra_context))
658
        user = form.save()
659
        return HttpResponseRedirect(next)
660
    else:
661
        form = None
662
        if request.user.is_authenticated() and not request.user.signed_terms:
663
            form = SignApprovalTermsForm(instance=request.user)
664
        return render_response(template_name,
665
                               terms=terms,
666
                               approval_terms_form=form,
667
                               context_instance=get_context(request, extra_context))
668

    
669

    
670
@require_http_methods(["GET", "POST"])
671
@login_required
672
@signed_terms_required
673
@transaction.commit_manually
674
def change_email(request, activation_key=None,
675
                 email_template_name='registration/email_change_email.txt',
676
                 form_template_name='registration/email_change_form.html',
677
                 confirm_template_name='registration/email_change_done.html',
678
                 extra_context=None):
679
    extra_context = extra_context or {}
680
    if activation_key:
681
        try:
682
            user = EmailChange.objects.change_email(activation_key)
683
            if request.user.is_authenticated() and request.user == user:
684
                msg = _(astakos_messages.EMAIL_CHANGED)
685
                messages.success(request, msg)
686
                auth_logout(request)
687
                response = prepare_response(request, user)
688
                transaction.commit()
689
                return response
690
        except ValueError, e:
691
            messages.error(request, e)
692
        return render_response(confirm_template_name,
693
                               modified_user=user if 'user' in locals(
694
                               ) else None,
695
                               context_instance=get_context(request,
696
                                                            extra_context))
697

    
698
    if not request.user.is_authenticated():
699
        path = quote(request.get_full_path())
700
        url = request.build_absolute_uri(reverse('index'))
701
        return HttpResponseRedirect(url + '?next=' + path)
702
    form = EmailChangeForm(request.POST or None)
703
    if request.method == 'POST' and form.is_valid():
704
        try:
705
            ec = form.save(email_template_name, request)
706
        except SendMailError, e:
707
            msg = e
708
            messages.error(request, msg)
709
            transaction.rollback()
710
        except IntegrityError, e:
711
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
712
            messages.error(request, msg)
713
        else:
714
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
715
            messages.success(request, msg)
716
            transaction.commit()
717
    return render_response(
718
        form_template_name,
719
        form=form,
720
        context_instance=get_context(request, extra_context)
721
    )
722

    
723

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

    
726
    if settings.MODERATION_ENABLED:
727
        raise PermissionDenied
728

    
729
    extra_context = extra_context or {}
730
    try:
731
        u = AstakosUser.objects.get(id=user_id)
732
    except AstakosUser.DoesNotExist:
733
        messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
734
    else:
735
        try:
736
            send_activation_func(u)
737
            msg = _(astakos_messages.ACTIVATION_SENT)
738
            messages.success(request, msg)
739
        except SendMailError, e:
740
            messages.error(request, e)
741
    return render_response(
742
        template_name,
743
        login_form = LoginForm(request=request),
744
        context_instance = get_context(
745
            request,
746
            extra_context
747
        )
748
    )
749

    
750
# class ResourcePresentation():
751
# 
752
#     def __init__(self, data):
753
#         self.data = data
754
# 
755
#     def update_from_result(self, result):
756
#         if result.is_success:
757
#             for r in result.data:
758
#                 rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
759
#                 if not rname in self.data['resources']:
760
#                     self.data['resources'][rname] = {}
761
# 
762
#                 self.data['resources'][rname].update(r)
763
#                 self.data['resources'][rname]['id'] = rname
764
#                 group = r.get('group')
765
#                 if not group in self.data['groups']:
766
#                     self.data['groups'][group] = {}
767
# 
768
#                 self.data['groups'][r.get('group')].update({'name': r.get('group')})
769
# 
770
#     def test(self, quota_dict):
771
#         for k, v in quota_dict.iteritems():
772
#             rname = k
773
#             value = v
774
#             if not rname in self.data['resources']:
775
#                 self.data['resources'][rname] = {}
776
# 
777
# 
778
#             self.data['resources'][rname]['value'] = value
779
# 
780
# 
781
#     def update_from_result_report(self, result):
782
#         if result.is_success:
783
#             for r in result.data:
784
#                 rname = r.get('name')
785
#                 if not rname in self.data['resources']:
786
#                     self.data['resources'][rname] = {}
787
# 
788
#                 self.data['resources'][rname].update(r)
789
#                 self.data['resources'][rname]['id'] = rname
790
#                 group = r.get('group')
791
#                 if not group in self.data['groups']:
792
#                     self.data['groups'][group] = {}
793
# 
794
#                 self.data['groups'][r.get('group')].update({'name': r.get('group')})
795
# 
796
#     def get_group_resources(self, group):
797
#         return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
798
# 
799
#     def get_groups_resources(self):
800
#         for g in self.data['groups']:
801
#             yield g, self.get_group_resources(g)
802
# 
803
#     def get_quota(self, group_quotas):
804
#         for r, v in group_quotas:
805
#             rname = str(r)
806
#             quota = self.data['resources'].get(rname)
807
#             quota['value'] = v
808
#             yield quota
809
# 
810
# 
811
#     def get_policies(self, policies_data):
812
#         for policy in policies_data:
813
#             rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
814
#             policy.update(self.data['resources'].get(rname))
815
#             yield policy
816
# 
817
#     def __repr__(self):
818
#         return self.data.__repr__()
819
# 
820
#     def __iter__(self, *args, **kwargs):
821
#         return self.data.__iter__(*args, **kwargs)
822
# 
823
#     def __getitem__(self, *args, **kwargs):
824
#         return self.data.__getitem__(*args, **kwargs)
825
# 
826
#     def get(self, *args, **kwargs):
827
#         return self.data.get(*args, **kwargs)
828

    
829

    
830

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

    
894

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

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

    
997

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

    
1095

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

    
1163

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

    
1211

    
1212
##@require_http_methods(["POST"])
1213
# @require_http_methods(["POST", "GET"])
1214
# @signed_terms_required
1215
# @login_required
1216
# def group_join(request, group_id):
1217
#     m = Membership(group_id=group_id,
1218
#                    person=request.user,
1219
#                    date_requested=datetime.now())
1220
#     try:
1221
#         m.save()
1222
#         post_save_redirect = reverse(
1223
#             'group_detail',
1224
#             kwargs=dict(group_id=group_id))
1225
#         return HttpResponseRedirect(post_save_redirect)
1226
#     except IntegrityError, e:
1227
#         logger.exception(e)
1228
#         msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1229
#         messages.error(request, msg)
1230
#         return group_search(request)
1231
# 
1232
# 
1233
# @require_http_methods(["POST"])
1234
# @signed_terms_required
1235
# @login_required
1236
# def group_leave(request, group_id):
1237
#     try:
1238
#         m = Membership.objects.select_related().get(
1239
#             group__id=group_id,
1240
#             person=request.user)
1241
#     except Membership.DoesNotExist:
1242
#         return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1243
#     if request.user in m.group.owner.all():
1244
#         return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1245
#     return delete_object(
1246
#         request,
1247
#         model=Membership,
1248
#         object_id=m.id,
1249
#         template_name='im/astakosgroup_list.html',
1250
#         post_delete_redirect=reverse(
1251
#             'group_detail',
1252
#             kwargs=dict(group_id=group_id)))
1253

    
1254

    
1255
# def handle_membership(func):
1256
#     @wraps(func)
1257
#     def wrapper(request, group_id, user_id):
1258
#         try:
1259
#             m = Membership.objects.select_related().get(
1260
#                 group__id=group_id,
1261
#                 person__id=user_id)
1262
#         except Membership.DoesNotExist:
1263
#             return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1264
#         else:
1265
#             if request.user not in m.group.owner.all():
1266
#                 return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1267
#             func(request, m)
1268
#             return group_detail(request, group_id)
1269
#     return wrapper
1270

    
1271

    
1272
#@require_http_methods(["POST"])
1273
# @require_http_methods(["POST", "GET"])
1274
# @signed_terms_required
1275
# @login_required
1276
# @handle_membership
1277
# def approve_member(request, membership):
1278
#     try:
1279
#         membership.approve()
1280
#         realname = membership.person.realname
1281
#         msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1282
#         messages.success(request, msg)
1283
#     except AssertionError:
1284
#         msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1285
#         messages.error(request, msg)
1286
#     except BaseException, e:
1287
#         logger.exception(e)
1288
#         realname = membership.person.realname
1289
#         msg = _(astakos_messages.GENERIC_ERROR)
1290
#         messages.error(request, msg)
1291

    
1292

    
1293
# @signed_terms_required
1294
# @login_required
1295
# @handle_membership
1296
# def disapprove_member(request, membership):
1297
#     try:
1298
#         membership.disapprove()
1299
#         realname = membership.person.realname
1300
#         msg = astakos_messages.MEMBER_REMOVED % locals()
1301
#         messages.success(request, msg)
1302
#     except BaseException, e:
1303
#         logger.exception(e)
1304
#         msg = _(astakos_messages.GENERIC_ERROR)
1305
#         messages.error(request, msg)
1306

    
1307

    
1308
@require_http_methods(["GET"])
1309
@require_http_methods(["POST", "GET"])
1310
@signed_terms_required
1311
@login_required
1312
def resource_usage(request):
1313
    def with_class(entry):
1314
        entry['load_class'] = 'red'
1315
        max_value = float(entry['maxValue'])
1316
        curr_value = float(entry['currValue'])
1317
        entry['ratio_limited']= 0
1318
        if max_value > 0 :
1319
            entry['ratio'] = (curr_value / max_value) * 100
1320
        else:
1321
            entry['ratio'] = 0
1322
        if entry['ratio'] < 66:
1323
            entry['load_class'] = 'yellow'
1324
        if entry['ratio'] < 33:
1325
            entry['load_class'] = 'green'
1326
        if entry['ratio']<0:
1327
            entry['ratio'] = 0
1328
        if entry['ratio']>100:
1329
            entry['ratio_limited'] = 100
1330
        else:
1331
            entry['ratio_limited'] = entry['ratio']
1332

    
1333
        return entry
1334

    
1335
    def pluralize(entry):
1336
        entry['plural'] = engine.plural(entry.get('name'))
1337
        return entry
1338

    
1339
    result = callpoint.get_user_usage(request.user.id)
1340
    if result.is_success:
1341
        backenddata = map(with_class, result.data)
1342
        data = map(pluralize, result.data)
1343
    else:
1344
        data = None
1345
        messages.error(request, result.reason)
1346
    resource_catalog = result.data
1347
#     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1348
#     resource_catalog.update_from_result_report(result)
1349
    return render_response('im/resource_usage.html',
1350
                           data=data,
1351
                           context_instance=get_context(request),
1352
                           resource_catalog=resource_catalog,
1353
                           result=result)
1354

    
1355

    
1356
# def group_create_list(request):
1357
#     form = PickResourceForm()
1358
#     return render_response(
1359
#         template='im/astakosgroup_create_list.html',
1360
#         context_instance=get_context(request),)
1361

    
1362

    
1363
##@require_http_methods(["GET"])
1364
#@require_http_methods(["POST", "GET"])
1365
#@signed_terms_required
1366
#@login_required
1367
#def billing(request):
1368
#
1369
#    today = datetime.today()
1370
#    month_last_day = calendar.monthrange(today.year, today.month)[1]
1371
#    start = request.POST.get('datefrom', None)
1372
#    if start:
1373
#        today = datetime.fromtimestamp(int(start))
1374
#        month_last_day = calendar.monthrange(today.year, today.month)[1]
1375
#
1376
#    start = datetime(today.year, today.month, 1).strftime("%s")
1377
#    end = datetime(today.year, today.month, month_last_day).strftime("%s")
1378
#    r = request_billing.apply(args=('pgerakios@grnet.gr',
1379
#                                    int(start) * 1000,
1380
#                                    int(end) * 1000))
1381
#    data = {}
1382
#
1383
#    try:
1384
#        status, data = r.result
1385
#        data = _clear_billing_data(data)
1386
#        if status != 200:
1387
#            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1388
#    except:
1389
#        messages.error(request, r.result)
1390
#
1391
#    return render_response(
1392
#        template='im/billing.html',
1393
#        context_instance=get_context(request),
1394
#        data=data,
1395
#        zerodate=datetime(month=1, year=1970, day=1),
1396
#        today=today,
1397
#        start=int(start),
1398
#        month_last_day=month_last_day)
1399

    
1400

    
1401
#def _clear_billing_data(data):
1402
#
1403
#    # remove addcredits entries
1404
#    def isnotcredit(e):
1405
#        return e['serviceName'] != "addcredits"
1406
#
1407
#    # separate services
1408
#    def servicefilter(service_name):
1409
#        service = service_name
1410
#
1411
#        def fltr(e):
1412
#            return e['serviceName'] == service
1413
#        return fltr
1414
#
1415
#    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1416
#    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1417
#    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1418
#    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1419
#
1420
#    return data
1421

    
1422

    
1423
# #@require_http_methods(["GET"])
1424
# @require_http_methods(["POST", "GET"])
1425
# @signed_terms_required
1426
# @login_required
1427
# def timeline(request):
1428
# #    data = {'entity':request.user.email}
1429
#     timeline_body = ()
1430
#     timeline_header = ()
1431
# #    form = TimelineForm(data)
1432
#     form = TimelineForm()
1433
#     if request.method == 'POST':
1434
#         data = request.POST
1435
#         form = TimelineForm(data)
1436
#         if form.is_valid():
1437
#             data = form.cleaned_data
1438
#             timeline_header = ('entity', 'resource',
1439
#                                'event name', 'event date',
1440
#                                'incremental cost', 'total cost')
1441
#             timeline_body = timeline_charge(
1442
#                 data['entity'], data['resource'],
1443
#                 data['start_date'], data['end_date'],
1444
#                 data['details'], data['operation'])
1445
# 
1446
#     return render_response(template='im/timeline.html',
1447
#                            context_instance=get_context(request),
1448
#                            form=form,
1449
#                            timeline_header=timeline_header,
1450
#                            timeline_body=timeline_body)
1451
#     return data
1452

    
1453

    
1454
# TODO: action only on POST and user should confirm the removal
1455
@require_http_methods(["GET", "POST"])
1456
@login_required
1457
@signed_terms_required
1458
def remove_auth_provider(request, pk):
1459
    try:
1460
        provider = request.user.auth_providers.get(pk=pk)
1461
    except AstakosUserAuthProvider.DoesNotExist:
1462
        raise Http404
1463

    
1464
    if provider.can_remove():
1465
        provider.delete()
1466
        return HttpResponseRedirect(reverse('edit_profile'))
1467
    else:
1468
        raise PermissionDenied
1469

    
1470

    
1471
def how_it_works(request):
1472
    return render_response(
1473
        'im/how_it_works.html',
1474
        context_instance=get_context(request))
1475

    
1476
@transaction.commit_manually
1477
def _create_object(request, model=None, template_name=None,
1478
        template_loader=template_loader, extra_context=None, post_save_redirect=None,
1479
        login_required=False, context_processors=None, form_class=None):
1480
    """
1481
    Based of django.views.generic.create_update.create_object which displays a
1482
    summary page before creating the object.
1483
    """
1484
    rollback = False
1485
    response = None
1486

    
1487
    if extra_context is None: extra_context = {}
1488
    if login_required and not request.user.is_authenticated():
1489
        return redirect_to_login(request.path)
1490
    try:
1491
    
1492
        model, form_class = get_model_and_form_class(model, form_class)
1493
        extra_context['edit'] = 0
1494
        if request.method == 'POST':
1495
            form = form_class(request.POST, request.FILES)
1496
            if form.is_valid():
1497
                verify = request.GET.get('verify')
1498
                edit = request.GET.get('edit')
1499
                if verify == '1':
1500
                    extra_context['show_form'] = False
1501
                    extra_context['form_data'] = form.cleaned_data
1502
                elif edit == '1':
1503
                    extra_context['show_form'] = True
1504
                else:
1505
                    new_object = form.save()
1506
                    
1507
                    msg = _("The %(verbose_name)s was created successfully.") %\
1508
                                {"verbose_name": model._meta.verbose_name}
1509
                    messages.success(request, msg, fail_silently=True)
1510
                    response = redirect(post_save_redirect, new_object)
1511
        else:
1512
            form = form_class()
1513
    except BaseException, e:
1514
        logger.exception(e)
1515
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1516
        rollback = True
1517
    finally:
1518
        if rollback:
1519
            transaction.rollback()
1520
        else:
1521
            transaction.commit()
1522

    
1523
        if response == None:
1524
            # Create the template, context, response
1525
            if not template_name:
1526
                template_name = "%s/%s_form.html" %\
1527
                     (model._meta.app_label, model._meta.object_name.lower())
1528
            t = template_loader.get_template(template_name)
1529
            c = RequestContext(request, {
1530
                'form': form
1531
            }, context_processors)
1532
            apply_extra_context(extra_context, c)
1533
            response = HttpResponse(t.render(c))
1534
        return response
1535

    
1536
@transaction.commit_manually
1537
def _update_object(request, model=None, object_id=None, slug=None,
1538
        slug_field='slug', template_name=None, template_loader=template_loader,
1539
        extra_context=None, post_save_redirect=None, login_required=False,
1540
        context_processors=None, template_object_name='object',
1541
        form_class=None):
1542
    """
1543
    Based of django.views.generic.create_update.update_object which displays a
1544
    summary page before updating the object.
1545
    """
1546
    rollback = False
1547
    response = None
1548

    
1549
    if extra_context is None: extra_context = {}
1550
    if login_required and not request.user.is_authenticated():
1551
        return redirect_to_login(request.path)
1552
    
1553
    try:
1554
        model, form_class = get_model_and_form_class(model, form_class)
1555
        obj = lookup_object(model, object_id, slug, slug_field)
1556
    
1557
        if request.method == 'POST':
1558
            form = form_class(request.POST, request.FILES, instance=obj)
1559
            if form.is_valid():
1560
                verify = request.GET.get('verify')
1561
                edit = request.GET.get('edit')
1562
                if verify == '1':
1563
                    extra_context['show_form'] = False
1564
                    extra_context['form_data'] = form.cleaned_data
1565
                elif edit == '1':
1566
                    extra_context['show_form'] = True
1567
                else:                
1568
                    obj = form.save()
1569
                    msg = _("The %(verbose_name)s was updated successfully.") %\
1570
                                {"verbose_name": model._meta.verbose_name}
1571
                    messages.success(request, msg, fail_silently=True)
1572
                    response = redirect(post_save_redirect, obj)
1573
        else:
1574
            form = form_class(instance=obj)
1575
    except BaseException, e:
1576
        logger.exception(e)
1577
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1578
        rollback = True
1579
    finally:
1580
        if rollback:
1581
            transaction.rollback()
1582
        else:
1583
            transaction.commit()
1584
        if response == None:
1585
            if not template_name:
1586
                template_name = "%s/%s_form.html" %\
1587
                    (model._meta.app_label, model._meta.object_name.lower())
1588
            t = template_loader.get_template(template_name)
1589
            c = RequestContext(request, {
1590
                'form': form,
1591
                template_object_name: obj,
1592
            }, context_processors)
1593
            apply_extra_context(extra_context, c)
1594
            response = HttpResponse(t.render(c))
1595
            populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.attname))
1596
        return response
1597

    
1598
@require_http_methods(["GET", "POST"])
1599
@signed_terms_required
1600
@login_required
1601
def project_add(request):
1602
    result = callpoint.list_resources()
1603
    if not result.is_success:
1604
        messages.error(
1605
            request,
1606
            'Unable to retrieve system resources: %s' % result.reason
1607
    )
1608
    else:
1609
        resource_catalog = result.data
1610
    extra_context = {'resource_catalog':resource_catalog, 'show_form':True}
1611
    return _create_object(request, template_name='im/projects/projectapplication_form.html',
1612
        extra_context=extra_context, post_save_redirect='/im/project/list/',
1613
        form_class=ProjectApplicationForm)
1614

    
1615

    
1616
@require_http_methods(["GET"])
1617
@signed_terms_required
1618
@login_required
1619
def project_list(request):
1620
    q = ProjectApplication.objects.filter(owner=request.user)
1621
    q |= ProjectApplication.objects.filter(
1622
        project__in=request.user.projectmembership_set.values_list('project', flat=True)
1623
    )
1624
    q = q.select_related()
1625
    sorting = 'name'
1626
    sort_form = ProjectSortForm(request.GET)
1627
    if sort_form.is_valid():
1628
        sorting = sort_form.cleaned_data.get('sorting')
1629
    q = q.order_by(sorting)
1630
    
1631
    return object_list(
1632
        request,
1633
        q,
1634
        paginate_by=PAGINATE_BY_ALL,
1635
        page=request.GET.get('page') or 1,
1636
        template_name='im/projects/project_list.html',
1637
        extra_context={
1638
            'is_search':False,
1639
            'sorting':sorting
1640
        }
1641
    )
1642

    
1643
@require_http_methods(["GET", "POST"])
1644
@signed_terms_required
1645
@login_required
1646
def project_update(request, application_id):
1647
    result = callpoint.list_resources()
1648
    if not result.is_success:
1649
        messages.error(
1650
            request,
1651
            'Unable to retrieve system resources: %s' % result.reason
1652
    )
1653
    else:
1654
        resource_catalog = result.data
1655
    extra_context = {'resource_catalog':resource_catalog, 'show_form':True}
1656
    return _update_object(
1657
        request,
1658
        object_id=application_id,
1659
        template_name='im/projects/projectapplication_form.html',
1660
        extra_context=extra_context, post_save_redirect='/im/project/list/',
1661
        form_class=ProjectApplicationForm)
1662

    
1663

    
1664
@require_http_methods(["GET", "POST"])
1665
@signed_terms_required
1666
@login_required
1667
@transaction.commit_manually
1668
def project_detail(request, application_id):
1669
    result = callpoint.list_resources()
1670
    if not result.is_success:
1671
        messages.error(
1672
            request,
1673
            'Unable to retrieve system resources: %s' % result.reason
1674
    )
1675
    else:
1676
        resource_catalog = result.data
1677
#     resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1678
#     resource_catalog.update_from_result(result)
1679

    
1680
    addmembers_form = AddProjectMembersForm()
1681
    if request.method == 'POST':
1682
        addmembers_form = AddProjectMembersForm(request.POST)
1683
        if addmembers_form.is_valid():
1684
            try:
1685
                rollback = False
1686
                map(lambda u: accept_membership(
1687
                        application_id,
1688
                        u,
1689
                        request_user=request.user),
1690
                    addmembers_form.valid_users)
1691
            except (IOError, PermissionDenied), e:
1692
                messages.error(request, e)
1693
            except BaseException, e:
1694
                rollback = True
1695
                messages.error(request, e)
1696
            finally:
1697
                if rollback == True:
1698
                    transaction.rollback()
1699
                else:
1700
                    transaction.commit()
1701
            addmembers_form = AddProjectMembersForm()
1702
    
1703
    # validate sorting
1704
    sorting = 'person__email'
1705
    form = ProjectMembersSortForm(request.GET or request.POST)
1706
    if form.is_valid():
1707
        sorting = form.cleaned_data.get('sorting')
1708

    
1709
    return object_detail(
1710
        request,
1711
        queryset=ProjectApplication.objects.select_related(),
1712
        object_id=application_id,
1713
        template_name='im/projects/project_detail.html',
1714
        extra_context={
1715
            'resource_catalog':resource_catalog,
1716
            'sorting':sorting,
1717
            'addmembers_form':addmembers_form
1718
        }
1719
    )
1720

    
1721
@require_http_methods(["GET", "POST"])
1722
@signed_terms_required
1723
@login_required
1724
def project_search(request):
1725
    queryset = ProjectApplication.objects
1726
    if request.method == 'GET':
1727
        form = ProjectSearchForm()
1728
        queryset = queryset.none()
1729
    else:
1730
        form = ProjectSearchForm(request.POST)
1731
        if form.is_valid():
1732
            q = form.cleaned_data['q'].strip()
1733
            queryset = queryset.filter(~Q(project__last_approval_date__isnull=True))
1734
            queryset = queryset.filter(name__contains=q)
1735
    sorting = 'name'        
1736
    # validate sorting
1737
    sort_form = ProjectSortForm(request.GET)
1738
    if sort_form.is_valid():
1739
        sorting = sort_form.cleaned_data.get('sorting')
1740
    queryset = queryset.order_by(sorting)
1741
    return object_list(
1742
        request,
1743
        queryset,
1744
        paginate_by=PAGINATE_BY_ALL,
1745
        page=request.GET.get('page') or 1,
1746
        template_name='im/projects/project_list.html',
1747
        extra_context=dict(
1748
            form=form,
1749
            is_search=True,
1750
            sorting=sorting
1751
        )
1752
    )
1753

    
1754

    
1755
@require_http_methods(["GET"])
1756
@signed_terms_required
1757
@login_required
1758
def project_all(request):
1759
    q = ProjectApplication.objects.filter(
1760
        ~Q(project__last_approval_date__isnull=True))
1761
    q = q.select_related()
1762
    sorting = 'name'
1763
    sort_form = ProjectSortForm(request.GET)
1764
    if sort_form.is_valid():
1765
        sorting = sort_form.cleaned_data.get('sorting')
1766
    q = q.order_by(sorting)
1767
    
1768
    return object_list(
1769
        request,
1770
        q,
1771
        paginate_by=PAGINATE_BY_ALL,
1772
        page=request.GET.get('page') or 1,
1773
        template_name='im/projects/project_list.html',
1774
        extra_context={
1775
            'form':ProjectSearchForm(),
1776
            'is_search':True,
1777
            'sorting':sorting
1778
        }
1779
    )
1780

    
1781
@require_http_methods(["POST"])
1782
@signed_terms_required
1783
@login_required
1784
@transaction.commit_manually
1785
def project_join(request, application_id):
1786
    next = request.POST.get('next')
1787
    if not next:
1788
        return HttpResponseBadRequest(
1789
            _(astakos_messages.MISSING_NEXT_PARAMETER))
1790

    
1791
    rollback = False
1792
    try:
1793
        join_project(application_id, request.user)
1794
    except (IOError, PermissionDenied), e:
1795
        messages.error(request, e)
1796
    except BaseException, e:
1797
        logger.exception(e)
1798
        messages.error(request, _(astakos_messages.GENERIC_ERROR))
1799
        rollback = True
1800
    else:
1801
        return project_detail(request, id)
1802
    finally:
1803
        if rollback:
1804
            transaction.rollback()
1805
        else:
1806
            transaction.commit()
1807

    
1808
    next = restrict_next(
1809
        request.POST.get('next'),
1810
        domain=COOKIE_DOMAIN)
1811
    return redirect(next)
1812

    
1813
@require_http_methods(["POST"])
1814
@signed_terms_required
1815
@login_required
1816
@transaction.commit_manually
1817
def project_leave(request, application_id):
1818
    next = request.POST.get('next')
1819
    if not next:
1820
        return HttpResponseBadRequest(
1821
            _(astakos_messages.MISSING_NEXT_PARAMETER))
1822

    
1823
    rollback = False
1824
    try:
1825
        leave_project(application_id, request.user)
1826
    except (IOError, PermissionDenied), e:
1827
        messages.error(request, e)
1828
    except BaseException, e:
1829
        logger.exception(e)
1830
        messages.error(_(astakos_messages.GENERIC_ERRO))
1831
        rollback = True
1832
    finally:
1833
        if rollback:
1834
            transaction.rollback()
1835
        else:
1836
            transaction.commit()
1837
    
1838
    next = restrict_next(
1839
        request.POST.get('next'),
1840
        domain=COOKIE_DOMAIN)
1841
    return redirect(next)
1842

    
1843
@require_http_methods(["GET"])
1844
@signed_terms_required
1845
@login_required
1846
@transaction.commit_manually
1847
def project_approve_member(request, application_id, user_id):
1848
    rollback = False
1849
    try:
1850
        m = accept_membership(application_id, user_id, request.user)
1851
    except (IOError, PermissionDenied), e:
1852
        messages.error(request, e)
1853
    except BaseException, e:
1854
        logger.exception(e)
1855
        messages.error(_(astakos_messages.GENERIC_ERRO))
1856
        rollback = True
1857
    else:
1858
        realname = m.person.realname
1859
        msg = _(astakos_messages.USER_JOINED_PROJECT) % locals()
1860
        messages.success(request, msg)
1861
    finally:
1862
        if rollback:
1863
            transaction.rollback()
1864
        else:
1865
            transaction.commit()
1866
    return project_detail(request, application_id)
1867

    
1868
@require_http_methods(["GET"])
1869
@signed_terms_required
1870
@login_required
1871
@transaction.commit_manually
1872
def project_remove_member(request, application_id, user_id):
1873
    rollback = False
1874
    try:
1875
        m = remove_membership(application_id, user_id, request.user)
1876
    except (IOError, PermissionDenied), e:
1877
        messages.error(request, e)
1878
    except BaseException, e:
1879
        logger.exception(e)
1880
        messages.error(_(astakos_messages.GENERIC_ERRO))
1881
        rollback = True
1882
    else:
1883
        realname = m.person.realname
1884
        msg = _(astakos_messages.USER_LEFT_PROJECT) % locals()
1885
        messages.success(request, msg)
1886
    finally:
1887
        if rollback:
1888
            transaction.rollback()
1889
        else:
1890
            transaction.commit()
1891
    return project_detail(request, application_id)
1892

    
1893
@require_http_methods(["GET"])
1894
@signed_terms_required
1895
@login_required
1896
@transaction.commit_manually
1897
def project_reject_member(request, application_id, user_id):
1898
    rollback = False
1899
    try:
1900
        m = reject_membership(application_id, user_id, request.user)
1901
    except (IOError, PermissionDenied), e:
1902
        messages.error(request, e)
1903
    except BaseException, e:
1904
        logger.exception(e)
1905
        messages.error(_(astakos_messages.GENERIC_ERRO))
1906
        rollback = True
1907
    else:
1908
        realname = m.person.realname
1909
        msg = _(astakos_messages.USER_LEFT_PROJECT) % locals()
1910
        messages.success(request, msg)
1911
    finally:
1912
        if rollback:
1913
            transaction.rollback()
1914
        else:
1915
            transaction.commit()
1916
    return project_detail(request, application_id)
1917

    
1918