Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (30.7 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

    
36
from urllib import quote
37
from functools import wraps
38
from datetime import datetime, timedelta
39

    
40
from django.contrib import messages
41
from django.contrib.auth.decorators import login_required
42
from django.contrib.auth.views import password_change
43
from django.core.urlresolvers import reverse
44
from django.db import transaction
45
from django.db.models import Q
46
from django.db.utils import IntegrityError
47
from django.forms.fields import URLField
48
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
49
    HttpResponseRedirect, HttpResponseBadRequest
50
from django.shortcuts import redirect
51
from django.template import RequestContext, loader
52
from django.utils.http import urlencode
53
from django.utils.translation import ugettext as _
54
from django.views.generic.create_update import (create_object, delete_object,
55
    get_model_and_form_class
56
)
57
from django.views.generic.list_detail import object_list, object_detail
58

    
59
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup, Resource,
60
    EmailChange, GroupKind, Membership)
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 (LoginForm, InvitationForm, ProfileForm, FeedbackForm,
64
    SignApprovalTermsForm, ExtendedPasswordChangeForm, EmailChangeForm,
65
    AstakosGroupCreationForm, AstakosGroupSearchForm
66
)
67
from astakos.im.functions import (send_feedback, SendMailError,
68
    invite as invite_func, logout as auth_logout, activate as activate_func,
69
    switch_account_to_shibboleth, send_admin_notification, SendNotificationError
70
)
71
from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
72
    LOGGING_LEVEL
73
)
74

    
75
logger = logging.getLogger(__name__)
76

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

    
92

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

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

    
122
@signed_terms_required
123
def index(request, login_template_name='im/login.html', extra_context=None):
124
    """
125
    If there is logged on user renders the profile page otherwise renders login page.
126

127
    **Arguments**
128

129
    ``login_template_name``
130
        A custom login template to use. This is optional; if not specified,
131
        this will default to ``im/login.html``.
132

133
    ``profile_template_name``
134
        A custom profile template to use. This is optional; if not specified,
135
        this will default to ``im/profile.html``.
136

137
    ``extra_context``
138
        An dictionary of variables to add to the template context.
139

140
    **Template:**
141

142
    im/profile.html or im/login.html or ``template_name`` keyword argument.
143

144
    """
145
    template_name = login_template_name
146
    if request.user.is_authenticated():
147
        return HttpResponseRedirect(reverse('edit_profile'))
148
    return render_response(template_name,
149
                           login_form = LoginForm(request=request),
150
                           context_instance = get_context(request, extra_context))
151

    
152
@login_required
153
@signed_terms_required
154
@transaction.commit_manually
155
def invite(request, template_name='im/invitations.html', extra_context=None):
156
    """
157
    Allows a user to invite somebody else.
158

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

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

166
    If the user isn't logged in, redirects to settings.LOGIN_URL.
167

168
    **Arguments**
169

170
    ``template_name``
171
        A custom template to use. This is optional; if not specified,
172
        this will default to ``im/invitations.html``.
173

174
    ``extra_context``
175
        An dictionary of variables to add to the template context.
176

177
    **Template:**
178

179
    im/invitations.html or ``template_name`` keyword argument.
180

181
    **Settings:**
182

183
    The view expectes the following settings are defined:
184

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

    
218
    sent = [{'email': inv.username,
219
             'realname': inv.realname,
220
             'is_consumed': inv.is_consumed}
221
             for inv in request.user.invitations_sent.all()]
222
    kwargs = {'inviter': inviter,
223
              'sent':sent}
224
    context = get_context(request, extra_context, **kwargs)
225
    return render_response(template_name,
226
                           invitation_form = form,
227
                           context_instance = context)
228

    
229
@login_required
230
@signed_terms_required
231
def edit_profile(request, template_name='im/profile.html', extra_context=None):
232
    """
233
    Allows a user to edit his/her profile.
234

235
    In case of GET request renders a form for displaying the user information.
236
    In case of POST updates the user informantion and redirects to ``next``
237
    url parameter if exists.
238

239
    If the user isn't logged in, redirects to settings.LOGIN_URL.
240

241
    **Arguments**
242

243
    ``template_name``
244
        A custom template to use. This is optional; if not specified,
245
        this will default to ``im/profile.html``.
246

247
    ``extra_context``
248
        An dictionary of variables to add to the template context.
249

250
    **Template:**
251

252
    im/profile.html or ``template_name`` keyword argument.
253

254
    **Settings:**
255

256
    The view expectes the following settings are defined:
257

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

    
289
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
290
    """
291
    Allows a user to create a local account.
292

293
    In case of GET request renders a form for entering the user information.
294
    In case of POST handles the signup.
295

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

312
    ``on_success``
313
        A custom template to render in case of success. This is optional;
314
        if not specified, this will default to ``im/signup_complete.html``.
315

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

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

    
367
@login_required
368
@signed_terms_required
369
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
370
    """
371
    Allows a user to send feedback.
372

373
    In case of GET request renders a form for providing the feedback information.
374
    In case of POST sends an email to support team.
375

376
    If the user isn't logged in, redirects to settings.LOGIN_URL.
377

378
    **Arguments**
379

380
    ``template_name``
381
        A custom template to use. This is optional; if not specified,
382
        this will default to ``im/feedback.html``.
383

384
    ``extra_context``
385
        An dictionary of variables to add to the template context.
386

387
    **Template:**
388

389
    im/signup.html or ``template_name`` keyword argument.
390

391
    **Settings:**
392

393
    * LOGIN_URL: login uri
394
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
395
    """
396
    if request.method == 'GET':
397
        form = FeedbackForm()
398
    if request.method == 'POST':
399
        if not request.user:
400
            return HttpResponse('Unauthorized', status=401)
401

    
402
        form = FeedbackForm(request.POST)
403
        if form.is_valid():
404
            msg = form.cleaned_data['feedback_msg']
405
            data = form.cleaned_data['feedback_data']
406
            try:
407
                send_feedback(msg, data, request.user, email_template_name)
408
            except SendMailError, e:
409
                messages.error(request, message)
410
            else:
411
                message = _('Feedback successfully sent')
412
                messages.success(request, message)
413
    return render_response(template_name,
414
                           feedback_form = form,
415
                           context_instance = get_context(request, extra_context))
416

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

    
443
@transaction.commit_manually
444
def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
445
    """
446
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
447
    and renews the user token.
448

449
    The view uses commit_manually decorator in order to ensure the user state will be updated
450
    only if the email will be send successfully.
451
    """
452
    token = request.GET.get('auth')
453
    next = request.GET.get('next')
454
    try:
455
        user = AstakosUser.objects.get(auth_token=token)
456
    except AstakosUser.DoesNotExist:
457
        return HttpResponseBadRequest(_('No such user'))
458
    
459
    if user.is_active:
460
        message = _('Account already active.')
461
        messages.error(request, message)
462
        return index(request)
463
        
464
    try:
465
        local_user = AstakosUser.objects.get(
466
            ~Q(id = user.id),
467
            email=user.email,
468
            is_active=True
469
        )
470
    except AstakosUser.DoesNotExist:
471
        try:
472
            activate_func(
473
                user,
474
                greeting_email_template_name,
475
                helpdesk_email_template_name,
476
                verify_email=True
477
            )
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
    else:
493
        try:
494
            user = switch_account_to_shibboleth(
495
                user,
496
                local_user,
497
                greeting_email_template_name
498
            )
499
            response = prepare_response(request, user, next, renew=True)
500
            transaction.commit()
501
            return response
502
        except SendMailError, e:
503
            message = e.message
504
            messages.error(request, message)
505
            transaction.rollback()
506
            return index(request)
507
        except BaseException, e:
508
            message = _('Something went wrong.')
509
            messages.error(request, message)
510
            logger.exception(e)
511
            transaction.rollback()
512
            return index(request)
513

    
514
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
515
    term = None
516
    terms = None
517
    if not term_id:
518
        try:
519
            term = ApprovalTerms.objects.order_by('-id')[0]
520
        except IndexError:
521
            pass
522
    else:
523
        try:
524
            term = ApprovalTerms.objects.get(id=term_id)
525
        except ApprovalTerms.DoesNotExist, e:
526
            pass
527

    
528
    if not term:
529
        return HttpResponseRedirect(reverse('index'))
530
    f = open(term.location, 'r')
531
    terms = f.read()
532

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

    
554
@signed_terms_required
555
def change_password(request):
556
    return password_change(request,
557
                            post_change_redirect=reverse('edit_profile'),
558
                            password_change_form=ExtendedPasswordChangeForm)
559

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

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

    
676
    # Create the template, context, response
677
    template_name = "%s/%s_form.html" % (
678
        model._meta.app_label,
679
        model._meta.object_name.lower()
680
    )
681
    t = template_loader.get_template(template_name)
682
    c = RequestContext(request, {
683
        'form': form
684
    }, context_processors)
685
    return HttpResponse(t.render(c))
686

    
687
@signed_terms_required
688
@login_required
689
def group_list(request):
690
    list = request.user.astakos_groups.select_related().all()
691
    return object_list(request, queryset=list)
692

    
693
@signed_terms_required
694
@login_required
695
def group_detail(request, group_id):
696
    try:
697
        group = AstakosGroup.objects.select_related().get(id=group_id)
698
    except AstakosGroup.DoesNotExist:
699
        return HttpResponseBadRequest(_('Invalid group.'))
700
    return object_detail(request,
701
         AstakosGroup.objects.all(),
702
         object_id=group_id,
703
         extra_context = {'quota':group.quota}
704
    )
705

    
706
@signed_terms_required
707
@login_required
708
def group_approval_request(request, group_id):
709
    return HttpResponse()
710

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

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

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

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

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

    
833
@signed_terms_required
834
@login_required
835
def resource_list(request):
836
    return render_response(
837
        template='im/astakosuserquota_list.html',
838
        context_instance=get_context(request),
839
        quota=request.user.quota
840
    )