Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 0f4fa26d

History | View | Annotate | Download (29.9 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import logging
35
import socket
36

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

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

    
60
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup, 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, switch_account_to_shibboleth
66
from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
67

    
68
logger = logging.getLogger(__name__)
69

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

    
85

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

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

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

120
    **Arguments**
121

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

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

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

133
    **Template:**
134

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

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

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

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

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

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

161
    **Arguments**
162

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

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

170
    **Template:**
171

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

174
    **Settings:**
175

176
    The view expectes the following settings are defined:
177

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

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

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

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

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

234
    **Arguments**
235

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

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

243
    **Template:**
244

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

247
    **Settings:**
248

249
    The view expectes the following settings are defined:
250

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

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

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

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

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

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

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

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

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

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

370
    **Arguments**
371

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

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

379
    **Template:**
380

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

383
    **Settings:**
384

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

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

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

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

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

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

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

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

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

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

    
590
@signed_terms_required
591
@login_required
592
def group_add(request, kind_name='default'):
593
    try:
594
        kind = GroupKind.objects.get(name = kind_name)
595
    except:
596
        return HttpResponseBadRequest(_('No such group kind'))
597
    
598
    template_name=None,
599
    template_loader=loader
600
    extra_context=None
601
    post_save_redirect='/im/group/%(id)s/'
602
    login_required=False
603
    context_processors=None
604
    model, form_class = get_model_and_form_class(
605
        model=None,
606
        form_class=AstakosGroupCreationForm
607
    )
608
    # TODO better approach???
609
    resources = dict( (str(r.id), r) for r in Resource.objects.select_related().all() )
610
    if request.method == 'POST':
611
        form = form_class(request.POST, request.FILES, resources=resources)
612
        if form.is_valid():
613
            new_object = form.save()
614
            new_object.owners = [request.user]
615
            for (rid, limit) in form.resources():
616
                try:
617
                    r = resources[rid]
618
                except KeyError, e:
619
                    logger.exception(e)
620
                    # Should I stay or should I go???
621
                    continue
622
                else:
623
                    new_object.astakosgroupquota_set.create(
624
                        resource = r,
625
                        limit = limit
626
                    )
627
            msg = _("The %(verbose_name)s was created successfully.") %\
628
                                    {"verbose_name": model._meta.verbose_name}
629
            messages.success(request, msg, fail_silently=True)
630
            return redirect(post_save_redirect, new_object)
631
    else:
632
        now = datetime.now()
633
        data = {
634
            'kind':kind,
635
            'issue_date':now,
636
            'expiration_date':now + timedelta(days=30)
637
        }
638
        form = form_class(data, resources=resources)
639

    
640
    # Create the template, context, response
641
    template_name = "%s/%s_form.html" % (
642
        model._meta.app_label,
643
        model._meta.object_name.lower()
644
    )
645
    t = template_loader.get_template(template_name)
646
    c = RequestContext(request, {
647
        'form': form
648
    }, context_processors)
649
    return HttpResponse(t.render(c))
650

    
651
@signed_terms_required
652
@login_required
653
def group_list(request):
654
    list = request.user.astakos_groups.select_related().all()
655
    return object_list(request, queryset=list)
656

    
657
@signed_terms_required
658
@login_required
659
def group_detail(request, group_id):
660
    try:
661
        group = AstakosGroup.objects.select_related().get(id=group_id)
662
    except AstakosGroup.DoesNotExist:
663
        return HttpResponseBadRequest(_('Invalid group.'))
664
    return object_detail(request,
665
         AstakosGroup.objects.all(),
666
         object_id=group_id,
667
         extra_context = {'quota':group.quota}
668
    )
669

    
670
@signed_terms_required
671
@login_required
672
def group_approval_request(request, group_id):
673
    return HttpResponse()
674

    
675
@signed_terms_required
676
@login_required
677
def group_search(request, extra_context={}, **kwargs):
678
    join_forms = {}
679
    if request.method == 'GET':
680
        form = AstakosGroupSearchForm()
681
    else:
682
        form = AstakosGroupSearchForm(get_query(request))
683
        if form.is_valid():
684
            q = form.cleaned_data['q'].strip()
685
            q = URLField().to_python(q)
686
            queryset = AstakosGroup.objects.select_related().filter(name=q)
687
            f = MembershipCreationForm
688
            for g in queryset:
689
                join_forms[g.name] = f(
690
                    dict(
691
                        group=g,
692
                        person=request.user,
693
                        date_requested=datetime.now().strftime("%d/%m/%Y")
694
                    )
695
                )
696
            return object_list(
697
                request,
698
                queryset,
699
                template_name='im/astakosgroup_list.html',
700
                extra_context=dict(
701
                    form=form,
702
                    is_search=True,
703
                    join_forms=join_forms
704
                )
705
            )
706
    return render_response(
707
        template='im/astakosgroup_list.html',
708
        form = form,
709
        context_instance=get_context(request)
710
    )
711

    
712
@signed_terms_required
713
@login_required
714
def group_join(request, group_id):
715
    return create_object(
716
        request,
717
        model=Membership,
718
        template_name='im/astakosgroup_list.html',
719
        post_save_redirect = reverse(
720
            'group_detail',
721
            kwargs=dict(group_id=group_id)
722
        )
723
    )
724

    
725
@signed_terms_required
726
@login_required
727
def group_leave(request, group_id):
728
    try:
729
        m = Membership.objects.select_related().get(
730
            group__id=group_id,
731
            person=request.user
732
        )
733
    except Membership.DoesNotExist:
734
        return HttpResponseBadRequest(_('Invalid membership.'))
735
    if request.user in m.group.owner.all():
736
        return HttpResponseForbidden(_('Owner can not leave the group.'))
737
    return delete_object(
738
        request,
739
        model=Membership,
740
        object_id = m.id,
741
        template_name='im/astakosgroup_list.html',
742
        post_delete_redirect = reverse(
743
            'group_detail',
744
            kwargs=dict(group_id=group_id)
745
        )
746
    )
747

    
748
def handle_membership():
749
    def decorator(func):
750
        @wraps(func)
751
        def wrapper(request, group_id, user_id):
752
            try:
753
                m = Membership.objects.select_related().get(
754
                    group__id=group_id,
755
                    person__id=user_id
756
                )
757
            except Membership.DoesNotExist:
758
                return HttpResponseBadRequest(_('Invalid membership.'))
759
            else:
760
                if request.user not in m.group.owner.all():
761
                    return HttpResponseForbidden(_('User is not a group owner.'))
762
                func(request, m)
763
                return render_response(
764
                    template='im/astakosgroup_detail.html',
765
                    context_instance=get_context(request),
766
                    object=m.group,
767
                    quota=m.group.quota,
768
                    more_policies=m.group.has_undefined_policies
769
                )
770
        return wrapper
771
    return decorator
772

    
773
@signed_terms_required
774
@login_required
775
@handle_membership()
776
def approve_member(request, membership):
777
    try:
778
        membership.approve()
779
        realname = membership.person.realname
780
        msg = _('%s has been successfully joined the group.' % realname)
781
        messages.success(request, msg)
782
    except BaseException, e:
783
        logger.exception(e)
784
        msg = _('Something went wrong during %s\'s approval.' % realname)
785
        messages.error(request, msg)
786
    
787
@signed_terms_required
788
@login_required
789
@handle_membership()
790
def disapprove_member(request, membership):
791
    try:
792
        membership.disapprove()
793
        realname = membership.person.realname
794
        msg = _('%s has been successfully removed from the group.' % realname)
795
        messages.success(request, msg)
796
    except BaseException, e:
797
        logger.exception(e)
798
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
799
        messages.error(request, msg)
800

    
801
@signed_terms_required
802
@login_required
803
def resource_list(request):
804
    return render_response(
805
        template='im/astakosuserquota_list.html',
806
        context_instance=get_context(request),
807
        quota=request.user.quota
808
    )