Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 5ce3ce4f

History | View | Annotate | Download (32 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 (
61
    AstakosUser, ApprovalTerms, AstakosGroup, Resource,
62
    EmailChange, GroupKind, Membership)
63
from astakos.im.activation_backends import get_backend, SimpleBackend
64
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
65
from astakos.im.forms import (
66
    LoginForm, InvitationForm, ProfileForm, FeedbackForm,
67
    SignApprovalTermsForm, ExtendedPasswordChangeForm, EmailChangeForm,
68
    AstakosGroupCreationForm, AstakosGroupSearchForm
69
)
70
from astakos.im.functions import (send_feedback, SendMailError,
71
                                  invite as invite_func, logout as auth_logout, activate as activate_func,
72
                                  switch_account_to_shibboleth, send_admin_notification, SendNotificationError
73
                                  )
74
from astakos.im.settings import (
75
    COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
76
    LOGGING_LEVEL
77
)
78
from astakos.im.tasks import request_billing
79

    
80
logger = logging.getLogger(__name__)
81

    
82

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

    
99

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

    
114

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

    
130

    
131
@signed_terms_required
132
def index(request, login_template_name='im/login.html', extra_context=None):
133
    """
134
    If there is logged on user renders the profile page otherwise renders login page.
135

136
    **Arguments**
137

138
    ``login_template_name``
139
        A custom login template to use. This is optional; if not specified,
140
        this will default to ``im/login.html``.
141

142
    ``profile_template_name``
143
        A custom profile template to use. This is optional; if not specified,
144
        this will default to ``im/profile.html``.
145

146
    ``extra_context``
147
        An dictionary of variables to add to the template context.
148

149
    **Template:**
150

151
    im/profile.html or im/login.html or ``template_name`` keyword argument.
152

153
    """
154
    template_name = login_template_name
155
    if request.user.is_authenticated():
156
        return HttpResponseRedirect(reverse('edit_profile'))
157
    return render_response(template_name,
158
                           login_form=LoginForm(request=request),
159
                           context_instance=get_context(request, extra_context))
160

    
161

    
162
@login_required
163
@signed_terms_required
164
@transaction.commit_manually
165
def invite(request, template_name='im/invitations.html', extra_context=None):
166
    """
167
    Allows a user to invite somebody else.
168

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

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

176
    If the user isn't logged in, redirects to settings.LOGIN_URL.
177

178
    **Arguments**
179

180
    ``template_name``
181
        A custom template to use. This is optional; if not specified,
182
        this will default to ``im/invitations.html``.
183

184
    ``extra_context``
185
        An dictionary of variables to add to the template context.
186

187
    **Template:**
188

189
    im/invitations.html or ``template_name`` keyword argument.
190

191
    **Settings:**
192

193
    The view expectes the following settings are defined:
194

195
    * LOGIN_URL: login uri
196
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
197
    """
198
    status = None
199
    message = None
200
    form = InvitationForm()
201

    
202
    inviter = request.user
203
    if request.method == 'POST':
204
        form = InvitationForm(request.POST)
205
        if inviter.invitations > 0:
206
            if form.is_valid():
207
                try:
208
                    invitation = form.save()
209
                    invite_func(invitation, inviter)
210
                    message = _('Invitation sent to %s' % invitation.username)
211
                    messages.success(request, message)
212
                except SendMailError, e:
213
                    message = e.message
214
                    messages.error(request, message)
215
                    transaction.rollback()
216
                except BaseException, e:
217
                    message = _('Something went wrong.')
218
                    messages.error(request, message)
219
                    logger.exception(e)
220
                    transaction.rollback()
221
                else:
222
                    transaction.commit()
223
        else:
224
            message = _('No invitations left')
225
            messages.error(request, message)
226

    
227
    sent = [{'email': inv.username,
228
             'realname': inv.realname,
229
             'is_consumed': inv.is_consumed}
230
            for inv in request.user.invitations_sent.all()]
231
    kwargs = {'inviter': inviter,
232
              'sent': sent}
233
    context = get_context(request, extra_context, **kwargs)
234
    return render_response(template_name,
235
                           invitation_form=form,
236
                           context_instance=context)
237

    
238

    
239
@login_required
240
@signed_terms_required
241
def edit_profile(request, template_name='im/profile.html', extra_context=None):
242
    """
243
    Allows a user to edit his/her profile.
244

245
    In case of GET request renders a form for displaying the user information.
246
    In case of POST updates the user informantion and redirects to ``next``
247
    url parameter if exists.
248

249
    If the user isn't logged in, redirects to settings.LOGIN_URL.
250

251
    **Arguments**
252

253
    ``template_name``
254
        A custom template to use. This is optional; if not specified,
255
        this will default to ``im/profile.html``.
256

257
    ``extra_context``
258
        An dictionary of variables to add to the template context.
259

260
    **Template:**
261

262
    im/profile.html or ``template_name`` keyword argument.
263

264
    **Settings:**
265

266
    The view expectes the following settings are defined:
267

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

    
299

    
300
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
301
    """
302
    Allows a user to create a local account.
303

304
    In case of GET request renders a form for entering the user information.
305
    In case of POST handles the signup.
306

307
    The user activation will be delegated to the backend specified by the ``backend`` keyword argument
308
    if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
309
    if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
310
    (see activation_backends);
311

312
    Upon successful user creation, if ``next`` url parameter is present the user is redirected there
313
    otherwise renders the same page with a success message.
314

315
    On unsuccessful creation, renders ``template_name`` with an error message.
316

317
    **Arguments**
318

319
    ``template_name``
320
        A custom template to render. This is optional;
321
        if not specified, this will default to ``im/signup.html``.
322

323
    ``on_success``
324
        A custom template to render in case of success. This is optional;
325
        if not specified, this will default to ``im/signup_complete.html``.
326

327
    ``extra_context``
328
        An dictionary of variables to add to the template context.
329

330
    **Template:**
331

332
    im/signup.html or ``template_name`` keyword argument.
333
    im/signup_complete.html or ``on_success`` keyword argument.
334
    """
335
    if request.user.is_authenticated():
336
        return HttpResponseRedirect(reverse('edit_profile'))
337

    
338
    provider = get_query(request).get('provider', 'local')
339
    try:
340
        if not backend:
341
            backend = get_backend(request)
342
        form = backend.get_signup_form(provider)
343
    except Exception, e:
344
        form = SimpleBackend(request).get_signup_form(provider)
345
        messages.error(request, e)
346
    if request.method == 'POST':
347
        if form.is_valid():
348
            user = form.save(commit=False)
349
            try:
350
                result = backend.handle_activation(user)
351
                status = messages.SUCCESS
352
                message = result.message
353
                user.save()
354
                if 'additional_email' in form.cleaned_data:
355
                    additional_email = form.cleaned_data['additional_email']
356
                    if additional_email != user.email:
357
                        user.additionalmail_set.create(email=additional_email)
358
                        msg = 'Additional email: %s saved for user %s.' % (
359
                            additional_email, user.email)
360
                        logger.log(LOGGING_LEVEL, msg)
361
                if user and user.is_active:
362
                    next = request.POST.get('next', '')
363
                    return prepare_response(request, user, next=next)
364
                messages.add_message(request, status, message)
365
                return render_response(on_success,
366
                                       context_instance=get_context(request, extra_context))
367
            except SendMailError, e:
368
                message = e.message
369
                messages.error(request, message)
370
            except BaseException, e:
371
                message = _('Something went wrong.')
372
                messages.error(request, message)
373
                logger.exception(e)
374
    return render_response(template_name,
375
                           signup_form=form,
376
                           provider=provider,
377
                           context_instance=get_context(request, extra_context))
378

    
379

    
380
@login_required
381
@signed_terms_required
382
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
383
    """
384
    Allows a user to send feedback.
385

386
    In case of GET request renders a form for providing the feedback information.
387
    In case of POST sends an email to support team.
388

389
    If the user isn't logged in, redirects to settings.LOGIN_URL.
390

391
    **Arguments**
392

393
    ``template_name``
394
        A custom template to use. This is optional; if not specified,
395
        this will default to ``im/feedback.html``.
396

397
    ``extra_context``
398
        An dictionary of variables to add to the template context.
399

400
    **Template:**
401

402
    im/signup.html or ``template_name`` keyword argument.
403

404
    **Settings:**
405

406
    * LOGIN_URL: login uri
407
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
408
    """
409
    if request.method == 'GET':
410
        form = FeedbackForm()
411
    if request.method == 'POST':
412
        if not request.user:
413
            return HttpResponse('Unauthorized', status=401)
414

    
415
        form = FeedbackForm(request.POST)
416
        if form.is_valid():
417
            msg = form.cleaned_data['feedback_msg']
418
            data = form.cleaned_data['feedback_data']
419
            try:
420
                send_feedback(msg, data, request.user, email_template_name)
421
            except SendMailError, e:
422
                messages.error(request, message)
423
            else:
424
                message = _('Feedback successfully sent')
425
                messages.success(request, message)
426
    return render_response(template_name,
427
                           feedback_form=form,
428
                           context_instance=get_context(request, extra_context))
429

    
430

    
431
@signed_terms_required
432
def logout(request, template='registration/logged_out.html', extra_context=None):
433
    """
434
    Wraps `django.contrib.auth.logout` and delete the cookie.
435
    """
436
    response = HttpResponse()
437
    if request.user.is_authenticated():
438
        email = request.user.email
439
        auth_logout(request)
440
        response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
441
        msg = 'Cookie deleted for %s' % email
442
        logger.log(LOGGING_LEVEL, msg)
443
    next = request.GET.get('next')
444
    if next:
445
        response['Location'] = next
446
        response.status_code = 302
447
        return response
448
    elif LOGOUT_NEXT:
449
        response['Location'] = LOGOUT_NEXT
450
        response.status_code = 301
451
        return response
452
    messages.success(request, _('You have successfully logged out.'))
453
    context = get_context(request, extra_context)
454
    response.write(loader.render_to_string(template, context_instance=context))
455
    return response
456

    
457

    
458
@transaction.commit_manually
459
def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
460
    """
461
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
462
    and renews the user token.
463

464
    The view uses commit_manually decorator in order to ensure the user state will be updated
465
    only if the email will be send successfully.
466
    """
467
    token = request.GET.get('auth')
468
    next = request.GET.get('next')
469
    try:
470
        user = AstakosUser.objects.get(auth_token=token)
471
    except AstakosUser.DoesNotExist:
472
        return HttpResponseBadRequest(_('No such user'))
473

    
474
    if user.is_active:
475
        message = _('Account already active.')
476
        messages.error(request, message)
477
        return index(request)
478

    
479
    try:
480
        local_user = AstakosUser.objects.get(
481
            ~Q(id=user.id),
482
            email=user.email,
483
            is_active=True
484
        )
485
    except AstakosUser.DoesNotExist:
486
        try:
487
            activate_func(
488
                user,
489
                greeting_email_template_name,
490
                helpdesk_email_template_name,
491
                verify_email=True
492
            )
493
            response = prepare_response(request, user, next, renew=True)
494
            transaction.commit()
495
            return response
496
        except SendMailError, e:
497
            message = e.message
498
            messages.error(request, message)
499
            transaction.rollback()
500
            return index(request)
501
        except BaseException, e:
502
            message = _('Something went wrong.')
503
            messages.error(request, message)
504
            logger.exception(e)
505
            transaction.rollback()
506
            return index(request)
507
    else:
508
        try:
509
            user = switch_account_to_shibboleth(
510
                user,
511
                local_user,
512
                greeting_email_template_name
513
            )
514
            response = prepare_response(request, user, next, renew=True)
515
            transaction.commit()
516
            return response
517
        except SendMailError, e:
518
            message = e.message
519
            messages.error(request, message)
520
            transaction.rollback()
521
            return index(request)
522
        except BaseException, e:
523
            message = _('Something went wrong.')
524
            messages.error(request, message)
525
            logger.exception(e)
526
            transaction.rollback()
527
            return index(request)
528

    
529

    
530
def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
531
    term = None
532
    terms = None
533
    if not term_id:
534
        try:
535
            term = ApprovalTerms.objects.order_by('-id')[0]
536
        except IndexError:
537
            pass
538
    else:
539
        try:
540
            term = ApprovalTerms.objects.get(id=term_id)
541
        except ApprovalTerms.DoesNotExist, e:
542
            pass
543

    
544
    if not term:
545
        return HttpResponseRedirect(reverse('index'))
546
    f = open(term.location, 'r')
547
    terms = f.read()
548

    
549
    if request.method == 'POST':
550
        next = request.POST.get('next')
551
        if not next:
552
            next = reverse('index')
553
        form = SignApprovalTermsForm(request.POST, instance=request.user)
554
        if not form.is_valid():
555
            return render_response(template_name,
556
                                   terms=terms,
557
                                   approval_terms_form=form,
558
                                   context_instance=get_context(request, extra_context))
559
        user = form.save()
560
        return HttpResponseRedirect(next)
561
    else:
562
        form = None
563
        if request.user.is_authenticated() and not request.user.signed_terms:
564
            form = SignApprovalTermsForm(instance=request.user)
565
        return render_response(template_name,
566
                               terms=terms,
567
                               approval_terms_form=form,
568
                               context_instance=get_context(request, extra_context))
569

    
570

    
571
@signed_terms_required
572
def change_password(request):
573
    return password_change(request,
574
                           post_change_redirect=reverse('edit_profile'),
575
                           password_change_form=ExtendedPasswordChangeForm)
576

    
577

    
578
@signed_terms_required
579
@login_required
580
@transaction.commit_manually
581
def change_email(request, activation_key=None,
582
                 email_template_name='registration/email_change_email.txt',
583
                 form_template_name='registration/email_change_form.html',
584
                 confirm_template_name='registration/email_change_done.html',
585
                 extra_context=None):
586
    if activation_key:
587
        try:
588
            user = EmailChange.objects.change_email(activation_key)
589
            if request.user.is_authenticated() and request.user == user:
590
                msg = _('Email changed successfully.')
591
                messages.success(request, msg)
592
                auth_logout(request)
593
                response = prepare_response(request, user)
594
                transaction.commit()
595
                return response
596
        except ValueError, e:
597
            messages.error(request, e)
598
        return render_response(confirm_template_name,
599
                               modified_user=user if 'user' in locals(
600
                               ) else None,
601
                               context_instance=get_context(request,
602
                                                            extra_context))
603

    
604
    if not request.user.is_authenticated():
605
        path = quote(request.get_full_path())
606
        url = request.build_absolute_uri(reverse('index'))
607
        return HttpResponseRedirect(url + '?next=' + path)
608
    form = EmailChangeForm(request.POST or None)
609
    if request.method == 'POST' and form.is_valid():
610
        try:
611
            ec = form.save(email_template_name, request)
612
        except SendMailError, e:
613
            msg = e
614
            messages.error(request, msg)
615
            transaction.rollback()
616
        except IntegrityError, e:
617
            msg = _('There is already a pending change email request.')
618
            messages.error(request, msg)
619
        else:
620
            msg = _('Change email request has been registered succefully.\
621
                    You are going to receive a verification email in the new address.')
622
            messages.success(request, msg)
623
            transaction.commit()
624
    return render_response(form_template_name,
625
                           form=form,
626
                           context_instance=get_context(request,
627
                                                        extra_context))
628

    
629

    
630
@signed_terms_required
631
@login_required
632
def group_add(request, kind_name='default'):
633
    try:
634
        kind = GroupKind.objects.get(name=kind_name)
635
    except:
636
        return HttpResponseBadRequest(_('No such group kind'))
637

    
638
    template_loader = loader
639
    post_save_redirect = '/im/group/%(id)s/'
640
    context_processors = None
641
    model, form_class = get_model_and_form_class(
642
        model=None,
643
        form_class=AstakosGroupCreationForm
644
    )
645
    resources = dict(
646
        (str(r.id), r) for r in Resource.objects.select_related().all())
647
    policies = []
648
    if request.method == 'POST':
649
        form = form_class(request.POST, request.FILES, resources=resources)
650
        if form.is_valid():
651
            new_object = form.save()
652

    
653
            # save owner
654
            new_object.owners = [request.user]
655

    
656
            # save quota policies
657
            for (rid, limit) in form.resources():
658
                try:
659
                    r = resources[rid]
660
                except KeyError, e:
661
                    logger.exception(e)
662
                    # TODO Should I stay or should I go???
663
                    continue
664
                else:
665
                    new_object.astakosgroupquota_set.create(
666
                        resource=r,
667
                        limit=limit
668
                    )
669
                policies.append('%s %d' % (r, limit))
670
            msg = _("The %(verbose_name)s was created successfully.") %\
671
                {"verbose_name": model._meta.verbose_name}
672
            messages.success(request, msg, fail_silently=True)
673

    
674
            # send notification
675
            try:
676
                send_admin_notification(
677
                    template_name='im/group_creation_notification.txt',
678
                    dictionary={
679
                        'group': new_object,
680
                        'owner': request.user,
681
                        'policies': policies,
682
                    },
683
                    subject='%s alpha2 testing group creation notification' % SITENAME
684
                )
685
            except SendNotificationError, e:
686
                messages.error(request, e, fail_silently=True)
687
            return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
688
    else:
689
        now = datetime.now()
690
        data = {
691
            'kind': kind
692
        }
693
        form = form_class(data, resources=resources)
694

    
695
    # Create the template, context, response
696
    template_name = "%s/%s_form.html" % (
697
        model._meta.app_label,
698
        model._meta.object_name.lower()
699
    )
700
    t = template_loader.get_template(template_name)
701
    c = RequestContext(request, {
702
        'form': form
703
    }, context_processors)
704
    return HttpResponse(t.render(c))
705

    
706

    
707
@signed_terms_required
708
@login_required
709
def group_list(request):
710
    list = request.user.astakos_groups.select_related().all()
711
    return object_list(request, queryset=list,
712
                       extra_context=dict(
713
                       is_search=False
714
                       )
715
                       )
716

    
717

    
718
@signed_terms_required
719
@login_required
720
def group_detail(request, group_id):
721
    try:
722
        group = AstakosGroup.objects.select_related().get(id=group_id)
723
    except AstakosGroup.DoesNotExist:
724
        return HttpResponseBadRequest(_('Invalid group.'))
725
    return object_detail(request,
726
                         AstakosGroup.objects.all(),
727
                         object_id=group_id,
728
                         extra_context={'quota': group.quota}
729
                         )
730

    
731

    
732
@signed_terms_required
733
@login_required
734
def group_approval_request(request, group_id):
735
    return HttpResponse()
736

    
737

    
738
@signed_terms_required
739
@login_required
740
def group_search(request, extra_context=None, **kwargs):
741
    if request.method == 'GET':
742
        form = AstakosGroupSearchForm()
743
    else:
744
        form = AstakosGroupSearchForm(get_query(request))
745
        if form.is_valid():
746
            q = form.cleaned_data['q'].strip()
747
            queryset = AstakosGroup.objects.select_related(
748
            ).filter(name__contains=q)
749
            return object_list(
750
                request,
751
                queryset,
752
                template_name='im/astakosgroup_list.html',
753
                extra_context=dict(
754
                    form=form,
755
                    is_search=True
756
                )
757
            )
758
    return render_response(
759
        template='im/astakosgroup_list.html',
760
        form=form,
761
        context_instance=get_context(request, extra_context),
762
        is_search=False
763
    )
764

    
765

    
766
@signed_terms_required
767
@login_required
768
def group_join(request, group_id):
769
    m = Membership(group_id=group_id,
770
                   person=request.user,
771
                   date_requested=datetime.now()
772
                   )
773
    try:
774
        m.save()
775
        post_save_redirect = reverse(
776
            'group_detail',
777
            kwargs=dict(group_id=group_id)
778
        )
779
        return HttpResponseRedirect(post_save_redirect)
780
    except IntegrityError, e:
781
        logger.exception(e)
782
        msg = _('Failed to join group.')
783
        messages.error(request, msg)
784
        return group_search(request)
785

    
786

    
787
@signed_terms_required
788
@login_required
789
def group_leave(request, group_id):
790
    try:
791
        m = Membership.objects.select_related().get(
792
            group__id=group_id,
793
            person=request.user
794
        )
795
    except Membership.DoesNotExist:
796
        return HttpResponseBadRequest(_('Invalid membership.'))
797
    if request.user in m.group.owner.all():
798
        return HttpResponseForbidden(_('Owner can not leave the group.'))
799
    return delete_object(
800
        request,
801
        model=Membership,
802
        object_id=m.id,
803
        template_name='im/astakosgroup_list.html',
804
        post_delete_redirect=reverse(
805
            'group_detail',
806
            kwargs=dict(group_id=group_id)
807
        )
808
    )
809

    
810

    
811
def handle_membership():
812
    def decorator(func):
813
        @wraps(func)
814
        def wrapper(request, group_id, user_id):
815
            try:
816
                m = Membership.objects.select_related().get(
817
                    group__id=group_id,
818
                    person__id=user_id
819
                )
820
            except Membership.DoesNotExist:
821
                return HttpResponseBadRequest(_('Invalid membership.'))
822
            else:
823
                if request.user not in m.group.owner.all():
824
                    return HttpResponseForbidden(_('User is not a group owner.'))
825
                func(request, m)
826
                return render_response(
827
                    template='im/astakosgroup_detail.html',
828
                    context_instance=get_context(request),
829
                    object=m.group,
830
                    quota=m.group.quota
831
                )
832
        return wrapper
833
    return decorator
834

    
835

    
836
@signed_terms_required
837
@login_required
838
@handle_membership()
839
def approve_member(request, membership):
840
    try:
841
        membership.approve()
842
        realname = membership.person.realname
843
        msg = _('%s has been successfully joined the group.' % realname)
844
        messages.success(request, msg)
845
    except BaseException, e:
846
        logger.exception(e)
847
        msg = _('Something went wrong during %s\'s approval.' % realname)
848
        messages.error(request, msg)
849

    
850

    
851
@signed_terms_required
852
@login_required
853
@handle_membership()
854
def disapprove_member(request, membership):
855
    try:
856
        membership.disapprove()
857
        realname = membership.person.realname
858
        msg = _('%s has been successfully removed from the group.' % realname)
859
        messages.success(request, msg)
860
    except BaseException, e:
861
        logger.exception(e)
862
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
863
        messages.error(request, msg)
864

    
865

    
866
@signed_terms_required
867
@login_required
868
def resource_list(request):
869
    return render_response(
870
        template='im/astakosuserquota_list.html',
871
        context_instance=get_context(request),
872
        quota=request.user.quota
873
    )
874

    
875

    
876
def group_create_list(request):
877
    return render_response(
878
        template='im/astakosgroup_create_list.html',
879
        context_instance=get_context(request),
880
    )
881

    
882

    
883
@signed_terms_required
884
@login_required
885
def billing(request):
886
    today = datetime.today()
887
    month_last_day = calendar.monthrange(today.year, today.month)[1]
888
    start = datetime(today.year, today.month, 1).strftime("%s")
889
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
890
    r = request_billing.apply(args=(request.user.email,
891
                                    int(start) * 1000,
892
                                    int(end) * 1000)
893
                              )
894
    data = None
895
    try:
896
        status, data = r.result
897
        if status != 200:
898
            messages.error(request, _('Service response status: %d' % status))
899
    except:
900
        messages.error(request, r.result)
901
    return render_response(
902
        template='im/billing.html',
903
        context_instance=get_context(request),
904
        data=data
905
    )