Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28.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 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
        request.user.is_verified = True
273
        request.user.save()
274
    return render_response(template_name,
275
                           reset_cookie = reset_cookie,
276
                           profile_form = form,
277
                           context_instance = get_context(request,
278
                                                          extra_context))
279

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

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

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

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

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

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

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

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

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

369
    **Arguments**
370

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

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

378
    **Template:**
379

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

382
    **Settings:**
383

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
734
@signed_terms_required
735
@login_required
736
def resource_list(request):
737
    return render_response(template='im/astakosuserquota_list.html',
738
                           context_instance=get_context(request),
739
                           quota=request.user.quota)