Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 95a51cdc

History | View | Annotate | Download (30.6 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, Resource
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, \
66
    switch_account_to_shibboleth, send_admin_notification, SendNotificationError
67
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
68

    
69
logger = logging.getLogger(__name__)
70

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

    
86

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

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

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

121
    **Arguments**
122

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

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

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

134
    **Template:**
135

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

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

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

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

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

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

162
    **Arguments**
163

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

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

171
    **Template:**
172

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

175
    **Settings:**
176

177
    The view expectes the following settings are defined:
178

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

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

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

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

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

235
    **Arguments**
236

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

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

244
    **Template:**
245

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

248
    **Settings:**
249

250
    The view expectes the following settings are defined:
251

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

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

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

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

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

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

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

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

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

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

371
    **Arguments**
372

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

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

380
    **Template:**
381

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

384
    **Settings:**
385

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

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

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

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

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

    
508
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}):
509
    term = None
510
    terms = None
511
    if not term_id:
512
        try:
513
            term = ApprovalTerms.objects.order_by('-id')[0]
514
        except IndexError:
515
            pass
516
    else:
517
        try:
518
             term = ApprovalTerms.objects.get(id=term_id)
519
        except ApprovalTermDoesNotExist, e:
520
            pass
521

    
522
    if not term:
523
        return HttpResponseRedirect(reverse('index'))
524
    f = open(term.location, 'r')
525
    terms = f.read()
526

    
527
    if request.method == 'POST':
528
        next = request.POST.get('next')
529
        if not next:
530
            next = reverse('index')
531
        form = SignApprovalTermsForm(request.POST, instance=request.user)
532
        if not form.is_valid():
533
            return render_response(template_name,
534
                           terms = terms,
535
                           approval_terms_form = form,
536
                           context_instance = get_context(request, extra_context))
537
        user = form.save()
538
        return HttpResponseRedirect(next)
539
    else:
540
        form = None
541
        if request.user.is_authenticated() and not request.user.signed_terms():
542
            form = SignApprovalTermsForm(instance=request.user)
543
        return render_response(template_name,
544
                               terms = terms,
545
                               approval_terms_form = form,
546
                               context_instance = get_context(request, extra_context))
547

    
548
@signed_terms_required
549
def change_password(request):
550
    return password_change(request,
551
                            post_change_redirect=reverse('edit_profile'),
552
                            password_change_form=ExtendedPasswordChangeForm)
553

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

    
604
@signed_terms_required
605
@login_required
606
def group_add(request, kind_name='default'):
607
    try:
608
        kind = GroupKind.objects.get(name = kind_name)
609
    except:
610
        return HttpResponseBadRequest(_('No such group kind'))
611
    
612
    template_loader=loader
613
    post_save_redirect='/im/group/%(id)s/'
614
    context_processors=None
615
    model, form_class = get_model_and_form_class(
616
        model=None,
617
        form_class=AstakosGroupCreationForm
618
    )
619
    resources = dict( (str(r.id), r) for r in Resource.objects.select_related().all() )
620
    policies = []
621
    if request.method == 'POST':
622
        form = form_class(request.POST, request.FILES, resources=resources)
623
        if form.is_valid():
624
            new_object = form.save()
625
            
626
            # save owner
627
            new_object.owners = [request.user]
628
            
629
            # save quota policies
630
            for (rid, limit) in form.resources():
631
                try:
632
                    r = resources[rid]
633
                except KeyError, e:
634
                    logger.exception(e)
635
                    # TODO Should I stay or should I go???
636
                    continue
637
                else:
638
                    new_object.astakosgroupquota_set.create(
639
                        resource = r,
640
                        limit = limit
641
                    )
642
                policies.append('%s %d' % (r, limit))
643
            msg = _("The %(verbose_name)s was created successfully.") %\
644
                                    {"verbose_name": model._meta.verbose_name}
645
            messages.success(request, msg, fail_silently=True)
646
            
647
            # send notification
648
            try:
649
                send_admin_notification(
650
                    template_name='im/group_creation_notification.txt',
651
                    dictionary={
652
                        'group':new_object,
653
                        'owner':request.user,
654
                        'policies':policies,
655
                    },
656
                    subject='%s alpha2 testing group creation notification' % SITENAME
657
                )
658
            except SendNotificationError, e:
659
                messages.error(request, e, fail_silently=True)
660
            return redirect(post_save_redirect, new_object)
661
    else:
662
        now = datetime.now()
663
        data = {
664
            'kind':kind,
665
            'issue_date':now,
666
            'expiration_date':now + timedelta(days=30)
667
        }
668
        form = form_class(data, resources=resources)
669

    
670
    # Create the template, context, response
671
    template_name = "%s/%s_form.html" % (
672
        model._meta.app_label,
673
        model._meta.object_name.lower()
674
    )
675
    t = template_loader.get_template(template_name)
676
    c = RequestContext(request, {
677
        'form': form
678
    }, context_processors)
679
    return HttpResponse(t.render(c))
680

    
681
@signed_terms_required
682
@login_required
683
def group_list(request):
684
    list = request.user.astakos_groups.select_related().all()
685
    return object_list(request, queryset=list)
686

    
687
@signed_terms_required
688
@login_required
689
def group_detail(request, group_id):
690
    try:
691
        group = AstakosGroup.objects.select_related().get(id=group_id)
692
    except AstakosGroup.DoesNotExist:
693
        return HttpResponseBadRequest(_('Invalid group.'))
694
    return object_detail(request,
695
         AstakosGroup.objects.all(),
696
         object_id=group_id,
697
         extra_context = {'quota':group.quota}
698
    )
699

    
700
@signed_terms_required
701
@login_required
702
def group_approval_request(request, group_id):
703
    return HttpResponse()
704

    
705
@signed_terms_required
706
@login_required
707
def group_search(request, extra_context=None, **kwargs):
708
    if request.method == 'GET':
709
        form = AstakosGroupSearchForm()
710
    else:
711
        form = AstakosGroupSearchForm(get_query(request))
712
        if form.is_valid():
713
            q = form.cleaned_data['q'].strip()
714
            q = URLField().to_python(q)
715
            queryset = AstakosGroup.objects.select_related().filter(name=q)
716
            return object_list(
717
                request,
718
                queryset,
719
                template_name='im/astakosgroup_list.html',
720
                extra_context=dict(
721
                    form=form,
722
                    is_search=True
723
                )
724
            )
725
    return render_response(
726
        template='im/astakosgroup_list.html',
727
        form = form,
728
        context_instance=get_context(request, extra_context)
729
    )
730

    
731
@signed_terms_required
732
@login_required
733
def group_join(request, group_id):
734
    m = Membership(group_id=group_id,
735
        person=request.user,
736
        date_requested=datetime.now()
737
    )
738
    try:
739
        m.save()
740
        post_save_redirect = reverse(
741
            'group_detail',
742
            kwargs=dict(group_id=group_id)
743
        )
744
        return HttpResponseRedirect(post_save_redirect)
745
    except IntegrityError, e:
746
        logger.exception(e)
747
        msg = _('Failed to join group.')
748
        messages.error(request, msg)
749
        return group_search(request)
750

    
751
@signed_terms_required
752
@login_required
753
def group_leave(request, group_id):
754
    try:
755
        m = Membership.objects.select_related().get(
756
            group__id=group_id,
757
            person=request.user
758
        )
759
    except Membership.DoesNotExist:
760
        return HttpResponseBadRequest(_('Invalid membership.'))
761
    if request.user in m.group.owner.all():
762
        return HttpResponseForbidden(_('Owner can not leave the group.'))
763
    return delete_object(
764
        request,
765
        model=Membership,
766
        object_id = m.id,
767
        template_name='im/astakosgroup_list.html',
768
        post_delete_redirect = reverse(
769
            'group_detail',
770
            kwargs=dict(group_id=group_id)
771
        )
772
    )
773

    
774
def handle_membership():
775
    def decorator(func):
776
        @wraps(func)
777
        def wrapper(request, group_id, user_id):
778
            try:
779
                m = Membership.objects.select_related().get(
780
                    group__id=group_id,
781
                    person__id=user_id
782
                )
783
            except Membership.DoesNotExist:
784
                return HttpResponseBadRequest(_('Invalid membership.'))
785
            else:
786
                if request.user not in m.group.owner.all():
787
                    return HttpResponseForbidden(_('User is not a group owner.'))
788
                func(request, m)
789
                return render_response(
790
                    template='im/astakosgroup_detail.html',
791
                    context_instance=get_context(request),
792
                    object=m.group,
793
                    quota=m.group.quota,
794
                    more_policies=m.group.has_undefined_policies
795
                )
796
        return wrapper
797
    return decorator
798

    
799
@signed_terms_required
800
@login_required
801
@handle_membership()
802
def approve_member(request, membership):
803
    try:
804
        membership.approve()
805
        realname = membership.person.realname
806
        msg = _('%s has been successfully joined the group.' % realname)
807
        messages.success(request, msg)
808
    except BaseException, e:
809
        logger.exception(e)
810
        msg = _('Something went wrong during %s\'s approval.' % realname)
811
        messages.error(request, msg)
812
    
813
@signed_terms_required
814
@login_required
815
@handle_membership()
816
def disapprove_member(request, membership):
817
    try:
818
        membership.disapprove()
819
        realname = membership.person.realname
820
        msg = _('%s has been successfully removed from the group.' % realname)
821
        messages.success(request, msg)
822
    except BaseException, e:
823
        logger.exception(e)
824
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
825
        messages.error(request, msg)
826

    
827
@signed_terms_required
828
@login_required
829
def resource_list(request):
830
    return render_response(
831
        template='im/astakosuserquota_list.html',
832
        context_instance=get_context(request),
833
        quota=request.user.quota
834
    )