Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (34.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, AddGroupMembersForm)
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
@transaction.commit_manually
305
def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
306
    """
307
    Allows a user to create a local account.
308

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

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

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

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

322
    **Arguments**
323

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

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

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

335
    **Template:**
336

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

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

    
388

    
389
@login_required
390
@signed_terms_required
391
def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
392
    """
393
    Allows a user to send feedback.
394

395
    In case of GET request renders a form for providing the feedback information.
396
    In case of POST sends an email to support team.
397

398
    If the user isn't logged in, redirects to settings.LOGIN_URL.
399

400
    **Arguments**
401

402
    ``template_name``
403
        A custom template to use. This is optional; if not specified,
404
        this will default to ``im/feedback.html``.
405

406
    ``extra_context``
407
        An dictionary of variables to add to the template context.
408

409
    **Template:**
410

411
    im/signup.html or ``template_name`` keyword argument.
412

413
    **Settings:**
414

415
    * LOGIN_URL: login uri
416
    * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
417
    """
418
    if request.method == 'GET':
419
        form = FeedbackForm()
420
    if request.method == 'POST':
421
        if not request.user:
422
            return HttpResponse('Unauthorized', status=401)
423

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

    
439

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

    
466

    
467
@transaction.commit_manually
468
def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
469
    """
470
    Activates the user identified by the ``auth`` request parameter, sends a welcome email
471
    and renews the user token.
472

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

    
483
    if user.is_active:
484
        message = _('Account already active.')
485
        messages.error(request, message)
486
        return index(request)
487

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

    
538

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

    
553
    if not term:
554
        return HttpResponseRedirect(reverse('index'))
555
    f = open(term.location, 'r')
556
    terms = f.read()
557

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

    
579

    
580
@signed_terms_required
581
def change_password(request):
582
    return password_change(request,
583
                           post_change_redirect=reverse('edit_profile'),
584
                           password_change_form=ExtendedPasswordChangeForm)
585

    
586

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

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

    
638

    
639
@signed_terms_required
640
@login_required
641
def group_add(request, kind_name='default'):
642
    try:
643
        kind = GroupKind.objects.get(name=kind_name)
644
    except:
645
        return HttpResponseBadRequest(_('No such group kind'))
646

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

    
662
            # save owner
663
            new_object.owners = [request.user]
664

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

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

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

    
716

    
717
@signed_terms_required
718
@login_required
719
def group_list(request):
720
    list = request.user.astakos_groups.select_related().all()
721
    return object_list(request, queryset=list,
722
                       extra_context=dict(
723
                       is_search=False
724
                       )
725
                       )
726

    
727

    
728
@signed_terms_required
729
@login_required
730
def group_detail(request, group_id):
731
    try:
732
        group = AstakosGroup.objects.select_related().get(id=group_id)
733
    except AstakosGroup.DoesNotExist:
734
        return HttpResponseBadRequest(_('Invalid group.'))
735
    form = AstakosGroupUpdateForm(instance=group)
736
    search_form = AddGroupMembersForm()
737
    return object_detail(request,
738
                         AstakosGroup.objects.all(),
739
                         object_id=group_id,
740
                         extra_context={'quota': group.quota,
741
                                        'form': form,
742
                                        'search_form': search_form}
743
                         )
744

    
745

    
746
@signed_terms_required
747
@login_required
748
def group_update(request, group_id):
749
    if request.method != 'POST':
750
        return HttpResponseBadRequest('Method not allowed.')
751
    try:
752
        group = AstakosGroup.objects.select_related().get(id=group_id)
753
    except AstakosGroup.DoesNotExist:
754
        return HttpResponseBadRequest(_('Invalid group.'))
755
    form = AstakosGroupUpdateForm(request.POST, instance=group)
756
    if form.is_valid():
757
        form.save()
758
    search_form = AddGroupMembersForm()
759
    return object_detail(request,
760
                         AstakosGroup.objects.all(),
761
                         object_id=group_id,
762
                         extra_context={'quota': group.quota,
763
                                        'form': form,
764
                                        'search_form': search_form})
765

    
766
@signed_terms_required
767
@login_required
768
def group_search(request, extra_context=None, **kwargs):
769
    if request.method == 'GET':
770
        form = AstakosGroupSearchForm()
771
    else:
772
        form = AstakosGroupSearchForm(get_query(request))
773
        if form.is_valid():
774
            q = form.cleaned_data['q'].strip()
775
            queryset = AstakosGroup.objects.select_related(
776
            ).filter(name__contains=q)
777
            return object_list(
778
                request,
779
                queryset,
780
                template_name='im/astakosgroup_list.html',
781
                extra_context=dict(form=form,
782
                                   is_search=True))
783
    return render_response(
784
        template='im/astakosgroup_list.html',
785
        form=form,
786
        context_instance=get_context(request, extra_context),
787
        is_search=False
788
    )
789

    
790
@signed_terms_required
791
@login_required
792
def group_all(request, extra_context=None, **kwargs):
793
    if request.method != 'POST':
794
        return HttpResponseBadRequest(_('Bad method'))
795
    return object_list(
796
                request,
797
                AstakosGroup.objects.select_related().all(),
798
                template_name='im/astakosgroup_list.html',
799
                extra_context=dict(form=AstakosGroupSearchForm(),
800
                                   is_search=True))
801

    
802

    
803
@signed_terms_required
804
@login_required
805
def group_join(request, group_id):
806
    m = Membership(group_id=group_id,
807
                   person=request.user,
808
                   date_requested=datetime.now()
809
                   )
810
    try:
811
        m.save()
812
        post_save_redirect = reverse(
813
            'group_detail',
814
            kwargs=dict(group_id=group_id)
815
        )
816
        return HttpResponseRedirect(post_save_redirect)
817
    except IntegrityError, e:
818
        logger.exception(e)
819
        msg = _('Failed to join group.')
820
        messages.error(request, msg)
821
        return group_search(request)
822

    
823

    
824
@signed_terms_required
825
@login_required
826
def group_leave(request, group_id):
827
    try:
828
        m = Membership.objects.select_related().get(
829
            group__id=group_id,
830
            person=request.user
831
        )
832
    except Membership.DoesNotExist:
833
        return HttpResponseBadRequest(_('Invalid membership.'))
834
    if request.user in m.group.owner.all():
835
        return HttpResponseForbidden(_('Owner can not leave the group.'))
836
    return delete_object(
837
        request,
838
        model=Membership,
839
        object_id=m.id,
840
        template_name='im/astakosgroup_list.html',
841
        post_delete_redirect=reverse(
842
            'group_detail',
843
            kwargs=dict(group_id=group_id)
844
        )
845
    )
846

    
847

    
848
def handle_membership():
849
    def decorator(func):
850
        @wraps(func)
851
        def wrapper(request, group_id, user_id):
852
            try:
853
                m = Membership.objects.select_related().get(
854
                    group__id=group_id,
855
                    person__id=user_id
856
                )
857
            except Membership.DoesNotExist:
858
                return HttpResponseBadRequest(_('Invalid membership.'))
859
            else:
860
                if request.user not in m.group.owner.all():
861
                    return HttpResponseForbidden(_('User is not a group owner.'))
862
                func(request, m)
863
                return render_response(
864
                    template='im/astakosgroup_detail.html',
865
                    context_instance=get_context(request),
866
                    object=m.group,
867
                    quota=m.group.quota
868
                )
869
        return wrapper
870
    return decorator
871

    
872

    
873
@signed_terms_required
874
@login_required
875
@handle_membership()
876
def approve_member(request, membership):
877
    try:
878
        membership.approve()
879
        realname = membership.person.realname
880
        msg = _('%s has been successfully joined the group.' % realname)
881
        messages.success(request, msg)
882
    except BaseException, e:
883
        logger.exception(e)
884
        msg = _('Something went wrong during %s\'s approval.' % realname)
885
        messages.error(request, msg)
886

    
887

    
888
@signed_terms_required
889
@login_required
890
@handle_membership()
891
def disapprove_member(request, membership):
892
    try:
893
        membership.disapprove()
894
        realname = membership.person.realname
895
        msg = _('%s has been successfully removed from the group.' % realname)
896
        messages.success(request, msg)
897
    except BaseException, e:
898
        logger.exception(e)
899
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
900
        messages.error(request, msg)
901

    
902

    
903

    
904

    
905
@signed_terms_required
906
@login_required
907
def add_members(request, group_id):
908
    if request.method != 'POST':
909
        return HttpResponseBadRequest(_('Bad method'))
910
    try:
911
        group = AstakosGroup.objects.select_related().get(id=group_id)
912
    except AstakosGroup.DoesNotExist:
913
        return HttpResponseBadRequest(_('Invalid group.'))
914
    search_form = AddGroupMembersForm(request.POST)
915
    if search_form.is_valid():
916
        users = search_form.get_valid_users()
917
        map(group.approve_member, users)
918
        search_form = AddGroupMembersForm()
919
    form = AstakosGroupUpdateForm(instance=group)
920
    return object_detail(request,
921
                         AstakosGroup.objects.all(),
922
                         object_id=group_id,
923
                         extra_context={'quota': group.quota,
924
                                        'form': form,
925
                                        'search_form' : search_form}
926
                         )
927

    
928

    
929
@signed_terms_required
930
@login_required
931
def resource_list(request):
932
    return render_response(
933
        template='im/astakosuserquota_list.html',
934
        context_instance=get_context(request),
935
        quota=request.user.quota
936
    )
937

    
938

    
939
def group_create_list(request):
940
    return render_response(
941
        template='im/astakosgroup_create_list.html',
942
        context_instance=get_context(request),
943
    )
944

    
945

    
946
@signed_terms_required
947
@login_required
948
def billing(request):
949
    today = datetime.today()
950
    month_last_day = calendar.monthrange(today.year, today.month)[1]
951
    start = datetime(today.year, today.month, 1).strftime("%s")
952
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
953
    r = request_billing.apply(args=(request.user.email,
954
                                    int(start) * 1000,
955
                                    int(end) * 1000)
956
                              )
957
    data = None
958
    try:
959
        status, data = r.result
960
        if status != 200:
961
            messages.error(request, _('Service response status: %d' % status))
962
    except:
963
        messages.error(request, r.result)
964
    return render_response(
965
        template='im/billing.html',
966
        context_instance=get_context(request),
967
        data=data
968
    )