Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / views.py @ 51c57c9c

History | View | Annotate | Download (32.7 kB)

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

    
34
import logging
35
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
from django.http import HttpResponseBadRequest
60

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

    
83
logger = logging.getLogger(__name__)
84

    
85

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

    
103

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

    
118

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

    
134

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

140
    **Arguments**
141

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

146
    ``profile_template_name``
147
        A custom profile template to use. This is optional; if not specified,
148
        this will default to ``im/profile.html``.
149

150
    ``extra_context``
151
        An dictionary of variables to add to the template context.
152

153
    **Template:**
154

155
    im/profile.html or im/login.html or ``template_name`` keyword argument.
156

157
    """
158
    template_name = login_template_name
159
    if request.user.is_authenticated():
160
        return HttpResponseRedirect(reverse('edit_profile'))
161
    return render_response(template_name,
162
                           login_form=LoginForm(request=request),
163
                           context_instance=get_context(request, extra_context))
164

    
165

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

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

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

180
    If the user isn't logged in, redirects to settings.LOGIN_URL.
181

182
    **Arguments**
183

184
    ``template_name``
185
        A custom template to use. This is optional; if not specified,
186
        this will default to ``im/invitations.html``.
187

188
    ``extra_context``
189
        An dictionary of variables to add to the template context.
190

191
    **Template:**
192

193
    im/invitations.html or ``template_name`` keyword argument.
194

195
    **Settings:**
196

197
    The view expectes the following settings are defined:
198

199
    * LOGIN_URL: login uri
200
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
201
    """
202
    status = None
203
    message = None
204
    form = InvitationForm()
205

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

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

    
242

    
243
@login_required
244
@signed_terms_required
245
def edit_profile(request, template_name='im/profile.html', extra_context=None):
246
    """
247
    Allows a user to edit his/her profile.
248

249
    In case of GET request renders a form for displaying the user information.
250
    In case of POST updates the user informantion and redirects to ``next``
251
    url parameter if exists.
252

253
    If the user isn't logged in, redirects to settings.LOGIN_URL.
254

255
    **Arguments**
256

257
    ``template_name``
258
        A custom template to use. This is optional; if not specified,
259
        this will default to ``im/profile.html``.
260

261
    ``extra_context``
262
        An dictionary of variables to add to the template context.
263

264
    **Template:**
265

266
    im/profile.html or ``template_name`` keyword argument.
267

268
    **Settings:**
269

270
    The view expectes the following settings are defined:
271

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

    
303

    
304
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
305
    """
306
    Allows a user to create a local account.
307

308
    In case of GET request renders a form for entering the user information.
309
    In case of POST handles the signup.
310

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

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

319
    On unsuccessful creation, renders ``template_name`` with an error message.
320

321
    **Arguments**
322

323
    ``template_name``
324
        A custom template to render. This is optional;
325
        if not specified, this will default to ``im/signup.html``.
326

327
    ``on_success``
328
        A custom template to render in case of success. This is optional;
329
        if not specified, this will default to ``im/signup_complete.html``.
330

331
    ``extra_context``
332
        An dictionary of variables to add to the template context.
333

334
    **Template:**
335

336
    im/signup.html or ``template_name`` keyword argument.
337
    im/signup_complete.html or ``on_success`` keyword argument.
338
    """
339
    if request.user.is_authenticated():
340
        return HttpResponseRedirect(reverse('edit_profile'))
341

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

    
383

    
384
@login_required
385
@signed_terms_required
386
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
387
    """
388
    Allows a user to send feedback.
389

390
    In case of GET request renders a form for providing the feedback information.
391
    In case of POST sends an email to support team.
392

393
    If the user isn't logged in, redirects to settings.LOGIN_URL.
394

395
    **Arguments**
396

397
    ``template_name``
398
        A custom template to use. This is optional; if not specified,
399
        this will default to ``im/feedback.html``.
400

401
    ``extra_context``
402
        An dictionary of variables to add to the template context.
403

404
    **Template:**
405

406
    im/signup.html or ``template_name`` keyword argument.
407

408
    **Settings:**
409

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

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

    
434

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

    
461

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

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

    
478
    if user.is_active:
479
        message = _('Account already active.')
480
        messages.error(request, message)
481
        return index(request)
482

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

    
533

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

    
548
    if not term:
549
        return HttpResponseRedirect(reverse('index'))
550
    f = open(term.location, 'r')
551
    terms = f.read()
552

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

    
574

    
575
@signed_terms_required
576
def change_password(request):
577
    return password_change(request,
578
                           post_change_redirect=reverse('edit_profile'),
579
                           password_change_form=ExtendedPasswordChangeForm)
580

    
581

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

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

    
633

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

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

    
657
            # save owner
658
            new_object.owners = [request.user]
659

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

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

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

    
711

    
712
@signed_terms_required
713
@login_required
714
def group_list(request):
715
    list = request.user.astakos_groups.select_related().all()
716
    return object_list(request, queryset=list,
717
                       extra_context=dict(
718
                       is_search=False
719
                       )
720
                       )
721

    
722

    
723
@signed_terms_required
724
@login_required
725
def group_detail(request, group_id):
726
    try:
727
        group = AstakosGroup.objects.select_related().get(id=group_id)
728
    except AstakosGroup.DoesNotExist:
729
        return HttpResponseBadRequest(_('Invalid group.'))
730
    form = AstakosGroupUpdateForm(instance=group)
731
    return object_detail(request,
732
                         AstakosGroup.objects.all(),
733
                         object_id=group_id,
734
                         extra_context={'quota': group.quota,
735
                                        'form': form}
736
                         )
737

    
738

    
739
def group_update(request, group_id):
740
    if request.method != 'POST':
741
        return HttpResponseBadRequest('Method not allowed.')
742
    try:
743
        group = AstakosGroup.objects.select_related().get(id=group_id)
744
    except AstakosGroup.DoesNotExist:
745
        return HttpResponseBadRequest(_('Invalid group.'))
746
    form = AstakosGroupUpdateForm(request.POST, instance=group)
747
    if form.is_valid():
748
        form.save()
749
    return group_detail(request, group_id)
750

    
751
@signed_terms_required
752
@login_required
753
def group_search(request, extra_context=None, **kwargs):
754
    if request.method == 'GET':
755
        form = AstakosGroupSearchForm()
756
    else:
757
        form = AstakosGroupSearchForm(get_query(request))
758
        if form.is_valid():
759
            q = form.cleaned_data['q'].strip()
760
            queryset = AstakosGroup.objects.select_related(
761
            ).filter(name__contains=q)
762
            return object_list(
763
                request,
764
                queryset,
765
                template_name='im/astakosgroup_list.html',
766
                extra_context=dict(
767
                    form=form,
768
                    is_search=True
769
                )
770
            )
771
    return render_response(
772
        template='im/astakosgroup_list.html',
773
        form=form,
774
        context_instance=get_context(request, extra_context),
775
        is_search=False
776
    )
777

    
778

    
779
@signed_terms_required
780
@login_required
781
def group_join(request, group_id):
782
    m = Membership(group_id=group_id,
783
                   person=request.user,
784
                   date_requested=datetime.now()
785
                   )
786
    try:
787
        m.save()
788
        post_save_redirect = reverse(
789
            'group_detail',
790
            kwargs=dict(group_id=group_id)
791
        )
792
        return HttpResponseRedirect(post_save_redirect)
793
    except IntegrityError, e:
794
        logger.exception(e)
795
        msg = _('Failed to join group.')
796
        messages.error(request, msg)
797
        return group_search(request)
798

    
799

    
800
@signed_terms_required
801
@login_required
802
def group_leave(request, group_id):
803
    try:
804
        m = Membership.objects.select_related().get(
805
            group__id=group_id,
806
            person=request.user
807
        )
808
    except Membership.DoesNotExist:
809
        return HttpResponseBadRequest(_('Invalid membership.'))
810
    if request.user in m.group.owner.all():
811
        return HttpResponseForbidden(_('Owner can not leave the group.'))
812
    return delete_object(
813
        request,
814
        model=Membership,
815
        object_id=m.id,
816
        template_name='im/astakosgroup_list.html',
817
        post_delete_redirect=reverse(
818
            'group_detail',
819
            kwargs=dict(group_id=group_id)
820
        )
821
    )
822

    
823

    
824
def handle_membership():
825
    def decorator(func):
826
        @wraps(func)
827
        def wrapper(request, group_id, user_id):
828
            try:
829
                m = Membership.objects.select_related().get(
830
                    group__id=group_id,
831
                    person__id=user_id
832
                )
833
            except Membership.DoesNotExist:
834
                return HttpResponseBadRequest(_('Invalid membership.'))
835
            else:
836
                if request.user not in m.group.owner.all():
837
                    return HttpResponseForbidden(_('User is not a group owner.'))
838
                func(request, m)
839
                return render_response(
840
                    template='im/astakosgroup_detail.html',
841
                    context_instance=get_context(request),
842
                    object=m.group,
843
                    quota=m.group.quota
844
                )
845
        return wrapper
846
    return decorator
847

    
848

    
849
@signed_terms_required
850
@login_required
851
@handle_membership()
852
def approve_member(request, membership):
853
    try:
854
        membership.approve()
855
        realname = membership.person.realname
856
        msg = _('%s has been successfully joined the group.' % realname)
857
        messages.success(request, msg)
858
    except BaseException, e:
859
        logger.exception(e)
860
        msg = _('Something went wrong during %s\'s approval.' % realname)
861
        messages.error(request, msg)
862

    
863

    
864
@signed_terms_required
865
@login_required
866
@handle_membership()
867
def disapprove_member(request, membership):
868
    try:
869
        membership.disapprove()
870
        realname = membership.person.realname
871
        msg = _('%s has been successfully removed from the group.' % realname)
872
        messages.success(request, msg)
873
    except BaseException, e:
874
        logger.exception(e)
875
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
876
        messages.error(request, msg)
877

    
878

    
879
@signed_terms_required
880
@login_required
881
def resource_list(request):
882
    return render_response(
883
        template='im/astakosuserquota_list.html',
884
        context_instance=get_context(request),
885
        quota=request.user.quota
886
    )
887

    
888

    
889
def group_create_list(request):
890
    return render_response(
891
        template='im/astakosgroup_create_list.html',
892
        context_instance=get_context(request),
893
    )
894

    
895

    
896
@signed_terms_required
897
@login_required
898
def billing(request):
899
    today = datetime.today()
900
    month_last_day = calendar.monthrange(today.year, today.month)[1]
901
    start = datetime(today.year, today.month, 1).strftime("%s")
902
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
903
    r = request_billing.apply(args=(request.user.email,
904
                                    int(start) * 1000,
905
                                    int(end) * 1000)
906
                              )
907
    data = None
908
    try:
909
        status, data = r.result
910
        if status != 200:
911
            messages.error(request, _('Service response status: %d' % status))
912
    except:
913
        messages.error(request, r.result)
914
    return render_response(
915
        template='im/billing.html',
916
        context_instance=get_context(request),
917
        data=data
918
    )