Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 304acb60

History | View | Annotate | Download (29.1 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 socket
36

    
37
from smtplib import SMTPException
38
from urllib import quote
39
from functools import wraps
40

    
41
from django.contrib import messages
42
from django.contrib.auth.decorators import login_required
43
from django.contrib.auth.views import password_change
44
from django.core.exceptions import ValidationError
45
from django.core.mail import send_mail
46
from django.core.urlresolvers import reverse
47
from django.db import transaction
48
from django.db.models import Q
49
from django.db.utils import IntegrityError
50
from django.forms.fields import URLField
51
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
52
    HttpResponseRedirect, HttpResponseBadRequest
53
from django.shortcuts import redirect
54
from django.template.loader import render_to_string
55
from django.utils.http import urlencode
56
from django.utils.translation import ugettext as _
57
from django.views.generic.create_update import *
58
from django.views.generic.list_detail import *
59

    
60
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup
61
from astakos.im.activation_backends import get_backend, SimpleBackend
62
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
63
from astakos.im.forms import *
64
from astakos.im.functions import send_greeting, send_feedback, SendMailError, \
65
    invite as invite_func, logout as auth_logout, activate as activate_func, switch_account_to_shibboleth
66
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
67

    
68
logger = logging.getLogger(__name__)
69

    
70
def render_response(template, tab=None, status=200, reset_cookie=False, context_instance=None, **kwargs):
71
    """
72
    Calls ``django.template.loader.render_to_string`` with an additional ``tab``
73
    keyword argument and returns an ``django.http.HttpResponse`` with the
74
    specified ``status``.
75
    """
76
    if tab is None:
77
        tab = template.partition('_')[0].partition('.html')[0]
78
    kwargs.setdefault('tab', tab)
79
    html = render_to_string(template, kwargs, context_instance=context_instance)
80
    response = HttpResponse(html, status=status)
81
    if reset_cookie:
82
        set_cookie(response, context_instance['request'].user)
83
    return response
84

    
85

    
86
def requires_anonymous(func):
87
    """
88
    Decorator checkes whether the request.user is not Anonymous and in that case
89
    redirects to `logout`.
90
    """
91
    @wraps(func)
92
    def wrapper(request, *args):
93
        if not request.user.is_anonymous():
94
            next = urlencode({'next': request.build_absolute_uri()})
95
            logout_uri = reverse(logout) + '?' + next
96
            return HttpResponseRedirect(logout_uri)
97
        return func(request, *args)
98
    return wrapper
99

    
100
def signed_terms_required(func):
101
    """
102
    Decorator checkes whether the request.user is Anonymous and in that case
103
    redirects to `logout`.
104
    """
105
    @wraps(func)
106
    def wrapper(request, *args, **kwargs):
107
        if request.user.is_authenticated() and not request.user.signed_terms():
108
            params = urlencode({'next': request.build_absolute_uri(),
109
                              'show_form':''})
110
            terms_uri = reverse('latest_terms') + '?' + params
111
            return HttpResponseRedirect(terms_uri)
112
        return func(request, *args, **kwargs)
113
    return wrapper
114

    
115
@signed_terms_required
116
def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context={}):
117
    """
118
    If there is logged on user renders the profile page otherwise renders login page.
119

120
    **Arguments**
121

122
    ``login_template_name``
123
        A custom login template to use. This is optional; if not specified,
124
        this will default to ``im/login.html``.
125

126
    ``profile_template_name``
127
        A custom profile template to use. This is optional; if not specified,
128
        this will default to ``im/profile.html``.
129

130
    ``extra_context``
131
        An dictionary of variables to add to the template context.
132

133
    **Template:**
134

135
    im/profile.html or im/login.html or ``template_name`` keyword argument.
136

137
    """
138
    template_name = login_template_name
139
    if request.user.is_authenticated():
140
        return HttpResponseRedirect(reverse('edit_profile'))
141
    return render_response(template_name,
142
                           login_form = LoginForm(request=request),
143
                           context_instance = get_context(request, extra_context))
144

    
145
@login_required
146
@signed_terms_required
147
@transaction.commit_manually
148
def invite(request, template_name='im/invitations.html', extra_context={}):
149
    """
150
    Allows a user to invite somebody else.
151

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

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

159
    If the user isn't logged in, redirects to settings.LOGIN_URL.
160

161
    **Arguments**
162

163
    ``template_name``
164
        A custom template to use. This is optional; if not specified,
165
        this will default to ``im/invitations.html``.
166

167
    ``extra_context``
168
        An dictionary of variables to add to the template context.
169

170
    **Template:**
171

172
    im/invitations.html or ``template_name`` keyword argument.
173

174
    **Settings:**
175

176
    The view expectes the following settings are defined:
177

178
    * LOGIN_URL: login uri
179
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
180
    * ASTAKOS_DEFAULT_FROM_EMAIL: from email
181
    """
182
    status = None
183
    message = None
184
    form = InvitationForm()
185
    
186
    inviter = request.user
187
    if request.method == 'POST':
188
        form = InvitationForm(request.POST)
189
        if inviter.invitations > 0:
190
            if form.is_valid():
191
                try:
192
                    invitation = form.save()
193
                    invite_func(invitation, inviter)
194
                    message = _('Invitation sent to %s' % invitation.username)
195
                    messages.success(request, message)
196
                except SendMailError, e:
197
                    message = e.message
198
                    messages.error(request, message)
199
                    transaction.rollback()
200
                except BaseException, e:
201
                    message = _('Something went wrong.')
202
                    messages.error(request, message)
203
                    logger.exception(e)
204
                    transaction.rollback()
205
                else:
206
                    transaction.commit()
207
        else:
208
            message = _('No invitations left')
209
            messages.error(request, message)
210

    
211
    sent = [{'email': inv.username,
212
             'realname': inv.realname,
213
             'is_consumed': inv.is_consumed}
214
             for inv in request.user.invitations_sent.all()]
215
    kwargs = {'inviter': inviter,
216
              'sent':sent}
217
    context = get_context(request, extra_context, **kwargs)
218
    return render_response(template_name,
219
                           invitation_form = form,
220
                           context_instance = context)
221

    
222
@login_required
223
@signed_terms_required
224
def edit_profile(request, template_name='im/profile.html', extra_context={}):
225
    """
226
    Allows a user to edit his/her profile.
227

228
    In case of GET request renders a form for displaying the user information.
229
    In case of POST updates the user informantion and redirects to ``next``
230
    url parameter if exists.
231

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

234
    **Arguments**
235

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

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

243
    **Template:**
244

245
    im/profile.html or ``template_name`` keyword argument.
246

247
    **Settings:**
248

249
    The view expectes the following settings are defined:
250

251
    * LOGIN_URL: login uri
252
    """
253
    form = ProfileForm(instance=request.user)
254
    extra_context['next'] = request.GET.get('next')
255
    reset_cookie = False
256
    if request.method == 'POST':
257
        form = ProfileForm(request.POST, instance=request.user)
258
        if form.is_valid():
259
            try:
260
                prev_token = request.user.auth_token
261
                user = form.save()
262
                reset_cookie = user.auth_token != prev_token
263
                form = ProfileForm(instance=user)
264
                next = request.POST.get('next')
265
                if next:
266
                    return redirect(next)
267
                msg = _('Profile has been updated successfully')
268
                messages.success(request, msg)
269
            except ValueError, ve:
270
                messages.success(request, ve)
271
    elif request.method == "GET":
272
        if not request.user.is_verified:
273
            request.user.is_verified = True
274
            request.user.save()
275
    return render_response(template_name,
276
                           reset_cookie = reset_cookie,
277
                           profile_form = form,
278
                           context_instance = get_context(request,
279
                                                          extra_context))
280

    
281
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
282
    """
283
    Allows a user to create a local account.
284

285
    In case of GET request renders a form for entering the user information.
286
    In case of POST handles the signup.
287

288
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
289
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
290
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
291
    (see activation_backends);
292
    
293
    Upon successful user creation, if ``next`` url parameter is present the user is redirected there
294
    otherwise renders the same page with a success message.
295
    
296
    On unsuccessful creation, renders ``template_name`` with an error message.
297
    
298
    **Arguments**
299
    
300
    ``template_name``
301
        A custom template to render. This is optional;
302
        if not specified, this will default to ``im/signup.html``.
303

304
    ``on_success``
305
        A custom template to render in case of success. This is optional;
306
        if not specified, this will default to ``im/signup_complete.html``.
307

308
    ``extra_context``
309
        An dictionary of variables to add to the template context.
310

311
    **Template:**
312
    
313
    im/signup.html or ``template_name`` keyword argument.
314
    im/signup_complete.html or ``on_success`` keyword argument. 
315
    """
316
    if request.user.is_authenticated():
317
        return HttpResponseRedirect(reverse('edit_profile'))
318
    
319
    provider = get_query(request).get('provider', 'local')
320
    try:
321
        if not backend:
322
            backend = get_backend(request)
323
        form = backend.get_signup_form(provider)
324
    except Exception, e:
325
        form = SimpleBackend(request).get_signup_form(provider)
326
        messages.error(request, e)
327
    if request.method == 'POST':
328
        if form.is_valid():
329
            user = form.save(commit=False)
330
            try:
331
                result = backend.handle_activation(user)
332
                status = messages.SUCCESS
333
                message = result.message
334
                user.save()
335
                if 'additional_email' in form.cleaned_data:
336
                    additional_email = form.cleaned_data['additional_email']
337
                    if additional_email != user.email:
338
                        user.additionalmail_set.create(email=additional_email)
339
                        msg = 'Additional email: %s saved for user %s.' % (additional_email, user.email)
340
                        logger._log(LOGGING_LEVEL, msg, [])
341
                if user and user.is_active:
342
                    next = request.POST.get('next', '')
343
                    return prepare_response(request, user, next=next)
344
                messages.add_message(request, status, message)
345
                return render_response(on_success,
346
                                       context_instance=get_context(request, extra_context))
347
            except SendMailError, e:
348
                message = e.message
349
                messages.error(request, message)
350
            except BaseException, e:
351
                message = _('Something went wrong.')
352
                messages.error(request, message)
353
                logger.exception(e)
354
    return render_response(template_name,
355
                           signup_form = form,
356
                           provider = provider,
357
                           context_instance=get_context(request, extra_context))
358

    
359
@login_required
360
@signed_terms_required
361
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
362
    """
363
    Allows a user to send feedback.
364

365
    In case of GET request renders a form for providing the feedback information.
366
    In case of POST sends an email to support team.
367

368
    If the user isn't logged in, redirects to settings.LOGIN_URL.
369

370
    **Arguments**
371

372
    ``template_name``
373
        A custom template to use. This is optional; if not specified,
374
        this will default to ``im/feedback.html``.
375

376
    ``extra_context``
377
        An dictionary of variables to add to the template context.
378

379
    **Template:**
380

381
    im/signup.html or ``template_name`` keyword argument.
382

383
    **Settings:**
384

385
    * LOGIN_URL: login uri
386
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
387
    """
388
    if request.method == 'GET':
389
        form = FeedbackForm()
390
    if request.method == 'POST':
391
        if not request.user:
392
            return HttpResponse('Unauthorized', status=401)
393

    
394
        form = FeedbackForm(request.POST)
395
        if form.is_valid():
396
            msg = form.cleaned_data['feedback_msg']
397
            data = form.cleaned_data['feedback_data']
398
            try:
399
                send_feedback(msg, data, request.user, email_template_name)
400
            except SendMailError, e:
401
                status = messages.ERROR
402
                messages.error(request, message)
403
            else:
404
                message = _('Feedback successfully sent')
405
                messages.succeess(request, message)
406
    return render_response(template_name,
407
                           feedback_form = form,
408
                           context_instance = get_context(request, extra_context))
409

    
410
@signed_terms_required
411
def logout(request, template='registration/logged_out.html', extra_context={}):
412
    """
413
    Wraps `django.contrib.auth.logout` and delete the cookie.
414
    """
415
    response = HttpResponse()
416
    if request.user.is_authenticated():
417
        email = request.user.email
418
        auth_logout(request)
419
        response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
420
        msg = 'Cookie deleted for %s' % email
421
        logger._log(LOGGING_LEVEL, msg, [])
422
    next = request.GET.get('next')
423
    if next:
424
        response['Location'] = next
425
        response.status_code = 302
426
        return response
427
    elif LOGOUT_NEXT:
428
        response['Location'] = LOGOUT_NEXT
429
        response.status_code = 301
430
        return response
431
    messages.success(request, _('You have successfully logged out.'))
432
    context = get_context(request, extra_context)
433
    response.write(render_to_string(template, context_instance=context))
434
    return response
435

    
436
@transaction.commit_manually
437
def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
438
    """
439
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
440
    and renews the user token.
441

442
    The view uses commit_manually decorator in order to ensure the user state will be updated
443
    only if the email will be send successfully.
444
    """
445
    token = request.GET.get('auth')
446
    next = request.GET.get('next')
447
    try:
448
        user = AstakosUser.objects.get(auth_token=token)
449
    except AstakosUser.DoesNotExist:
450
        return HttpResponseBadRequest(_('No such user'))
451
    
452
    if user.is_active:
453
        message = _('Account already active.')
454
        messages.error(request, message)
455
        return index(request)
456
        
457
    try:
458
        local_user = AstakosUser.objects.get(~Q(id = user.id), email=user.email, is_active=True)
459
    except AstakosUser.DoesNotExist:
460
        try:
461
            activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
462
            response = prepare_response(request, user, next, renew=True)
463
            transaction.commit()
464
            return response
465
        except SendMailError, e:
466
            message = e.message
467
            messages.error(request, message)
468
            transaction.rollback()
469
            return index(request)
470
        except BaseException, e:
471
            message = _('Something went wrong.')
472
            messages.error(request, message)
473
            logger.exception(e)
474
            transaction.rollback()
475
            return index(request)
476
    else:
477
        try:
478
            user = switch_account_to_shibboleth(user, local_user, greeting_email_template_name)
479
            response = prepare_response(request, user, next, renew=True)
480
            transaction.commit()
481
            return response
482
        except SendMailError, e:
483
            message = e.message
484
            messages.error(request, message)
485
            transaction.rollback()
486
            return index(request)
487
        except BaseException, e:
488
            message = _('Something went wrong.')
489
            messages.error(request, message)
490
            logger.exception(e)
491
            transaction.rollback()
492
            return index(request)
493

    
494
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}):
495
    term = None
496
    terms = None
497
    if not term_id:
498
        try:
499
            term = ApprovalTerms.objects.order_by('-id')[0]
500
        except IndexError:
501
            pass
502
    else:
503
        try:
504
             term = ApprovalTerms.objects.get(id=term_id)
505
        except ApprovalTermDoesNotExist, e:
506
            pass
507

    
508
    if not term:
509
        return HttpResponseRedirect(reverse('index'))
510
    f = open(term.location, 'r')
511
    terms = f.read()
512

    
513
    if request.method == 'POST':
514
        next = request.POST.get('next')
515
        if not next:
516
            next = reverse('index')
517
        form = SignApprovalTermsForm(request.POST, instance=request.user)
518
        if not form.is_valid():
519
            return render_response(template_name,
520
                           terms = terms,
521
                           approval_terms_form = form,
522
                           context_instance = get_context(request, extra_context))
523
        user = form.save()
524
        return HttpResponseRedirect(next)
525
    else:
526
        form = None
527
        if request.user.is_authenticated() and not request.user.signed_terms():
528
            form = SignApprovalTermsForm(instance=request.user)
529
        return render_response(template_name,
530
                               terms = terms,
531
                               approval_terms_form = form,
532
                               context_instance = get_context(request, extra_context))
533

    
534
@signed_terms_required
535
def change_password(request):
536
    return password_change(request,
537
                            post_change_redirect=reverse('edit_profile'),
538
                            password_change_form=ExtendedPasswordChangeForm)
539

    
540
@signed_terms_required
541
@login_required
542
@transaction.commit_manually
543
def change_email(request, activation_key=None,
544
                 email_template_name='registration/email_change_email.txt',
545
                 form_template_name='registration/email_change_form.html',
546
                 confirm_template_name='registration/email_change_done.html',
547
                 extra_context={}):
548
    if activation_key:
549
        try:
550
            user = EmailChange.objects.change_email(activation_key)
551
            if request.user.is_authenticated() and request.user == user:
552
                msg = _('Email changed successfully.')
553
                messages.success(request, msg)
554
                auth_logout(request)
555
                response = prepare_response(request, user)
556
                transaction.commit()
557
                return response
558
        except ValueError, e:
559
            messages.error(request, e)
560
        return render_response(confirm_template_name,
561
                               modified_user = user if 'user' in locals() else None,
562
                               context_instance = get_context(request,
563
                                                              extra_context))
564
    
565
    if not request.user.is_authenticated():
566
        path = quote(request.get_full_path())
567
        url = request.build_absolute_uri(reverse('index'))
568
        return HttpResponseRedirect(url + '?next=' + path)
569
    form = EmailChangeForm(request.POST or None)
570
    if request.method == 'POST' and form.is_valid():
571
        try:
572
            ec = form.save(email_template_name, request)
573
        except SendMailError, e:
574
            msg = e
575
            messages.error(request, msg)
576
            transaction.rollback()
577
        except IntegrityError, e:
578
            msg = _('There is already a pending change email request.')
579
            messages.error(request, msg)
580
        else:
581
            msg = _('Change email request has been registered succefully.\
582
                    You are going to receive a verification email in the new address.')
583
            messages.success(request, msg)
584
            transaction.commit()
585
    return render_response(form_template_name,
586
                           form = form,
587
                           context_instance = get_context(request,
588
                                                          extra_context))
589

    
590
@signed_terms_required
591
@login_required
592
def group_add(request):
593
    return create_object(request,
594
                         form_class=get_astakos_group_creation_form(request),
595
                         post_save_redirect = '/im/group/%(id)s/')
596

    
597
@signed_terms_required
598
@login_required
599
def group_list(request):
600
    list = AstakosGroup.objects.filter(membership__person=request.user)
601
    return object_list(request, queryset=list)
602

    
603
@signed_terms_required
604
@login_required
605
def group_detail(request, group_id):
606
    try:
607
        group = AstakosGroup.objects.select_related().get(id=group_id)
608
    except AstakosGroup.DoesNotExist:
609
        return HttpResponseBadRequest(_('Invalid group.'))
610
    return object_detail(request,
611
                         AstakosGroup.objects.all(),
612
                         object_id=group_id,
613
                         extra_context = {'form':get_astakos_group_policy_creation_form(group),
614
                                          'quota':group.quota,
615
                                          'more_policies':group.has_undefined_policies})
616

    
617
@signed_terms_required
618
@login_required
619
def group_policies_list(request, group_id):
620
    list = AstakosGroupQuota.objects.filter(group__id=group_id)
621
    return object_list(request, queryset=list)
622

    
623
@signed_terms_required
624
@login_required
625
def group_policies_add(request, group_id):
626
    try:
627
        group = AstakosGroup.objects.select_related().get(id=group_id)
628
    except AstakosGroup.DoesNotExist:
629
        return HttpResponseBadRequest(_('Invalid group.'))
630
    return create_object(request,
631
                         form_class=get_astakos_group_policy_creation_form(group),
632
                         template_name = 'im/astakosgroup_detail.html',
633
                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)),
634
                         extra_context = {'group':group,
635
                                          'quota':group.quota,
636
                                          'more_policies':group.has_undefined_policies})
637
@signed_terms_required
638
@login_required
639
def group_approval_request(request, group_id):
640
    return HttpResponse()
641

    
642
@signed_terms_required
643
@login_required
644
def group_search(request, extra_context={}, **kwargs):
645
    join_forms = {}
646
    if request.method == 'GET':
647
        form = AstakosGroupSearchForm()
648
    else:
649
        form = AstakosGroupSearchForm(get_query(request))
650
        if form.is_valid():
651
            q = form.cleaned_data['q'].strip()
652
            q = URLField().to_python(q)
653
            queryset = AstakosGroup.objects.select_related().filter(name=q)
654
            f = MembershipCreationForm
655
            for g in queryset:
656
                join_forms[g.name] = f(dict(group=g,
657
                                            person=request.user,
658
                                            date_requested=datetime.now().strftime("%d/%m/%Y")))
659
            return object_list(request,
660
                                queryset,
661
                                template_name='im/astakosgroup_list.html',
662
                                extra_context=dict(form=form, is_search=True, join_forms=join_forms))
663
    return render_response(template='im/astakosgroup_list.html',
664
                            form = form,
665
                            context_instance=get_context(request))
666

    
667
@signed_terms_required
668
@login_required
669
def group_join(request, group_id):
670
    return create_object(request,
671
                         model=Membership,
672
                         template_name='im/astakosgroup_list.html',
673
                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
674

    
675
@signed_terms_required
676
@login_required
677
def group_leave(request, group_id):
678
    try:
679
        m = Membership.objects.select_related().get(group__id=group_id, person=request.user)
680
    except Membership.DoesNotExist:
681
        return HttpResponseBadRequest(_('Invalid membership.'))
682
    if request.user in m.group.owner.all():
683
        return HttpResponseForbidden(_('Owner can not leave the group.'))
684
    return delete_object(request,
685
                         model=Membership,
686
                         object_id = m.id,
687
                         template_name='im/astakosgroup_list.html',
688
                         post_delete_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
689

    
690
def handle_membership():
691
    def decorator(func):
692
        @wraps(func)
693
        def wrapper(request, group_id, user_id):
694
            try:
695
                m = Membership.objects.select_related().get(group__id=group_id, person__id=user_id)
696
            except Membership.DoesNotExist:
697
                return HttpResponseBadRequest(_('Invalid membership.'))
698
            else:
699
                if request.user not in m.group.owner.all():
700
                    return HttpResponseForbidden(_('User is not a group owner.'))
701
                func(request, m)
702
                return render_response(template='im/astakosgroup_detail.html',
703
                                       context_instance=get_context(request),
704
                                       object=m.group,
705
                                       quota=m.group.quota,
706
                                       more_policies=m.group.has_undefined_policies)
707
        return wrapper
708
    return decorator
709

    
710
@signed_terms_required
711
@login_required
712
@handle_membership()
713
def approve_member(request, membership):
714
    try:
715
        membership.approve()
716
        realname = membership.person.realname
717
        msg = _('%s has been successfully joined the group.' % realname)
718
        messages.success(request, msg)
719
    except BaseException, e:
720
        logger.exception(e)
721
        msg = _('Something went wrong during %s\'s approval.' % realname)
722
        messages.error(request, msg)
723
    
724
@signed_terms_required
725
@login_required
726
@handle_membership()
727
def disapprove_member(request, membership):
728
    try:
729
        membership.disapprove()
730
        realname = membership.person.realname
731
        msg = _('%s has been successfully removed from the group.' % realname)
732
        messages.success(request, msg)
733
    except BaseException, e:
734
        logger.exception(e)
735
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
736
        messages.error(request, msg)
737

    
738
@signed_terms_required
739
@login_required
740
def resource_list(request):
741
    return render_response(template='im/astakosuserquota_list.html',
742
                           context_instance=get_context(request),
743
                           quota=request.user.quota)