Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (31.6 kB)

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

    
34
import logging
35
import calendar
36

    
37
from urllib import quote
38
from functools import wraps
39
from datetime import datetime, timedelta
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.urlresolvers import reverse
45
from django.db import transaction
46
from django.db.models import Q
47
from django.db.utils import IntegrityError
48
from django.forms.fields import URLField
49
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
50
    HttpResponseRedirect, HttpResponseBadRequest
51
from django.shortcuts import redirect
52
from django.template import RequestContext, loader
53
from django.utils.http import urlencode
54
from django.utils.translation import ugettext as _
55
from django.views.generic.create_update import (create_object, delete_object,
56
    get_model_and_form_class
57
)
58
from django.views.generic.list_detail import object_list, object_detail
59

    
60
from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup, Resource,
61
    EmailChange, GroupKind, Membership)
62
from astakos.im.activation_backends import get_backend, SimpleBackend
63
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
64
from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm, FeedbackForm,
65
    SignApprovalTermsForm, ExtendedPasswordChangeForm, EmailChangeForm,
66
    AstakosGroupCreationForm, AstakosGroupSearchForm
67
)
68
from astakos.im.functions import (send_feedback, SendMailError,
69
    invite as invite_func, logout as auth_logout, activate as activate_func,
70
    switch_account_to_shibboleth, send_admin_notification, SendNotificationError
71
)
72
from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
73
    LOGGING_LEVEL
74
)
75
from astakos.im.tasks import request_billing
76

    
77
logger = logging.getLogger(__name__)
78

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

    
94

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

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

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

129
    **Arguments**
130

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

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

139
    ``extra_context``
140
        An dictionary of variables to add to the template context.
141

142
    **Template:**
143

144
    im/profile.html or im/login.html or ``template_name`` keyword argument.
145

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

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

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

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

168
    If the user isn't logged in, redirects to settings.LOGIN_URL.
169

170
    **Arguments**
171

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

176
    ``extra_context``
177
        An dictionary of variables to add to the template context.
178

179
    **Template:**
180

181
    im/invitations.html or ``template_name`` keyword argument.
182

183
    **Settings:**
184

185
    The view expectes the following settings are defined:
186

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

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

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

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

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

242
    **Arguments**
243

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

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

251
    **Template:**
252

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

255
    **Settings:**
256

257
    The view expectes the following settings are defined:
258

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

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

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

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

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

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

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

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

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

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

379
    **Arguments**
380

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

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

388
    **Template:**
389

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

392
    **Settings:**
393

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

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

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

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

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

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

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

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

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

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

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

    
675
    # Create the template, context, response
676
    template_name = "%s/%s_form.html" % (
677
        model._meta.app_label,
678
        model._meta.object_name.lower()
679
    )
680
    t = template_loader.get_template(template_name)
681
    c = RequestContext(request, {
682
        'form': form,
683
        'kind': kind,
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
                extra_context=dict(
693
                    is_search=False
694
                )
695
    )
696

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

    
710
@signed_terms_required
711
@login_required
712
def group_approval_request(request, group_id):
713
    return HttpResponse()
714

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

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

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

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

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

    
836
@signed_terms_required
837
@login_required
838
def resource_list(request):
839
    return render_response(
840
        template='im/astakosuserquota_list.html',
841
        context_instance=get_context(request),
842
        quota=request.user.quota
843
    )
844
    
845
def group_create_list(request):
846
    return render_response(
847
        template='im/astakosgroup_create_list.html',
848
        context_instance=get_context(request),
849
    )
850

    
851
@signed_terms_required
852
@login_required
853
def billing(request):
854
    today = datetime.today()
855
    month_last_day = calendar.monthrange(today.year, today.month)[1]
856
    start = datetime(today.year, today.month, 1).strftime("%s")
857
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
858
    r = request_billing.apply(args=(request.user.email,
859
        int(start) * 1000,
860
        int(end) * 1000)
861
    )
862
    data = None
863
    try:
864
        status, data = r.result
865
        if status != 200:
866
            messages.error(request, _('Service response status: %d' % status))
867
    except:
868
        messages.error(request, r.result)
869
    return render_response(
870
        template='im/billing.html',
871
        context_instance=get_context(request),
872
        data = data
873
    )