Statistics
| Branch: | Tag: | Revision:

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

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

    
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
        }
672
        form = form_class(data, resources=resources)
673

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

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

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

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

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

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

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

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

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

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