Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (36.4 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
from django.views.generic.list_detail import object_list, object_detail
58
from django.http import HttpResponseBadRequest
59
from django.core.paginator import Paginator, InvalidPage
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, PAGINATE_BY)
80
from astakos.im.tasks import request_billing
81

    
82
logger = logging.getLogger(__name__)
83

    
84

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

    
102

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

    
117

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

    
133

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

139
    **Arguments**
140

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

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

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

152
    **Template:**
153

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

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

    
164

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

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

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

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

181
    **Arguments**
182

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

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

190
    **Template:**
191

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

194
    **Settings:**
195

196
    The view expectes the following settings are defined:
197

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

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

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

    
241

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

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

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

254
    **Arguments**
255

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

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

263
    **Template:**
264

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

267
    **Settings:**
268

269
    The view expectes the following settings are defined:
270

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

    
302

    
303
@transaction.commit_manually
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
                    transaction.commit()
368
                    return prepare_response(request, user, next=next)
369
                messages.add_message(request, status, message)
370
                transaction.commit()
371
                return render_response(on_success,
372
                                       context_instance=get_context(request, extra_context))
373
            except SendMailError, e:
374
                message = e.message
375
                messages.error(request, message)
376
                transaction.rollback()
377
            except BaseException, e:
378
                message = _('Something went wrong.')
379
                messages.error(request, message)
380
                logger.exception(e)
381
                transaction.rollback()
382
    return render_response(template_name,
383
                           signup_form=form,
384
                           provider=provider,
385
                           context_instance=get_context(request, extra_context))
386

    
387

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

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

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

399
    **Arguments**
400

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

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

408
    **Template:**
409

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

412
    **Settings:**
413

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

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

    
438

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

    
465

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

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

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

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

    
537

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

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

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

    
578

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

    
585

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

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

    
637

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

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

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

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

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

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

    
715

    
716
@signed_terms_required
717
@login_required
718
def group_list(request):
719
    q = request.user.astakos_groups.none()
720
    list = request.user.astakos_groups.select_related().all()
721
    d = {}
722
    d['own'] = [g for g in list if request.user in g.owner.all()]
723
    d['other'] = list.exclude(id__in=(g.id for g in d['own']))
724
    for k, queryset in d.iteritems():
725
        paginator = Paginator(queryset, PAGINATE_BY)
726
        page = request.GET.get('%s_page' % k, 1)
727
        try:
728
            page_number = int(page)
729
        except ValueError:
730
            if page == 'last':
731
                page_number = paginator.num_pages
732
            else:
733
                # Page is not 'last', nor can it be converted to an int.
734
                raise Http404
735
        try:
736
            page_obj = locals()['%s_page_obj' % k] = paginator.page(page_number)
737
        except InvalidPage:
738
            raise Http404
739
    return object_list(request, queryset=q,
740
                       extra_context={'is_search':False,
741
                                      'mine': locals()['own_page_obj'],
742
                                      'other': locals()['other_page_obj']})
743

    
744

    
745
@signed_terms_required
746
@login_required
747
def group_detail(request, group_id):
748
    try:
749
        group = AstakosGroup.objects.select_related().get(id=group_id)
750
    except AstakosGroup.DoesNotExist:
751
        return HttpResponseBadRequest(_('Invalid group.'))
752
    form = AstakosGroupUpdateForm(instance=group)
753
    search_form = AddGroupMembersForm()
754
    return object_detail(request,
755
                         AstakosGroup.objects.all(),
756
                         object_id=group_id,
757
                         extra_context={'quota': group.quota,
758
                                        'form': form,
759
                                        'search_form': search_form}
760
                         )
761

    
762

    
763
@signed_terms_required
764
@login_required
765
def group_update(request, group_id):
766
    if request.method != 'POST':
767
        return HttpResponseBadRequest('Method not allowed.')
768
    try:
769
        group = AstakosGroup.objects.select_related().get(id=group_id)
770
    except AstakosGroup.DoesNotExist:
771
        return HttpResponseBadRequest(_('Invalid group.'))
772
    form = AstakosGroupUpdateForm(request.POST, instance=group)
773
    if form.is_valid():
774
        form.save()
775
    search_form = AddGroupMembersForm()
776
    return object_detail(request,
777
                         AstakosGroup.objects.all(),
778
                         object_id=group_id,
779
                         extra_context={'quota': group.quota,
780
                                        'form': form,
781
                                        'search_form': search_form})
782

    
783
@signed_terms_required
784
@login_required
785
def group_search(request, extra_context=None, **kwargs):
786
    q = request.GET.get('q')
787
    if request.method == 'GET':
788
        form = AstakosGroupSearchForm({'q': q} if q else None)
789
    else:
790
        form = AstakosGroupSearchForm(get_query(request))
791
        if form.is_valid():
792
            q = form.cleaned_data['q'].strip()
793
    if q:
794
        queryset = AstakosGroup.objects.select_related(
795
        ).filter(name__contains=q)
796
    else:
797
        queryset = AstakosGroup.objects.none()
798
    return object_list(
799
        request,
800
        queryset,
801
        paginate_by=PAGINATE_BY,
802
        page=request.GET.get('page') or 1,
803
        template_name='im/astakosgroup_list.html',
804
        extra_context=dict(form=form,
805
                           is_search=True,
806
                           q=q))
807

    
808
@signed_terms_required
809
@login_required
810
def group_all(request, extra_context=None, **kwargs):
811
    return object_list(
812
                request,
813
                AstakosGroup.objects.select_related().all(),
814
                paginate_by=PAGINATE_BY,
815
                page=request.GET.get('page') or 1,
816
                template_name='im/astakosgroup_list.html',
817
                extra_context=dict(form=AstakosGroupSearchForm(),
818
                                   is_search=True))
819

    
820

    
821
@signed_terms_required
822
@login_required
823
def group_join(request, group_id):
824
    m = Membership(group_id=group_id,
825
                   person=request.user,
826
                   date_requested=datetime.now()
827
                   )
828
    try:
829
        m.save()
830
        post_save_redirect = reverse(
831
            'group_detail',
832
            kwargs=dict(group_id=group_id)
833
        )
834
        return HttpResponseRedirect(post_save_redirect)
835
    except IntegrityError, e:
836
        logger.exception(e)
837
        msg = _('Failed to join group.')
838
        messages.error(request, msg)
839
        return group_search(request)
840

    
841

    
842
@signed_terms_required
843
@login_required
844
def group_leave(request, group_id):
845
    try:
846
        m = Membership.objects.select_related().get(
847
            group__id=group_id,
848
            person=request.user
849
        )
850
    except Membership.DoesNotExist:
851
        return HttpResponseBadRequest(_('Invalid membership.'))
852
    if request.user in m.group.owner.all():
853
        return HttpResponseForbidden(_('Owner can not leave the group.'))
854
    return delete_object(
855
        request,
856
        model=Membership,
857
        object_id=m.id,
858
        template_name='im/astakosgroup_list.html',
859
        post_delete_redirect=reverse(
860
            'group_detail',
861
            kwargs=dict(group_id=group_id)
862
        )
863
    )
864

    
865

    
866
def handle_membership(func):
867
    @wraps(func)
868
    def wrapper(request, group_id, user_id):
869
        try:
870
            m = Membership.objects.select_related().get(
871
                group__id=group_id,
872
                person__id=user_id
873
            )
874
        except Membership.DoesNotExist:
875
            return HttpResponseBadRequest(_('Invalid membership.'))
876
        else:
877
            if request.user not in m.group.owner.all():
878
                return HttpResponseForbidden(_('User is not a group owner.'))
879
            func(request, m)
880
            return render_response(
881
                template='im/astakosgroup_detail.html',
882
                context_instance=get_context(request),
883
                object=m.group,
884
                quota=m.group.quota
885
            )
886
    return wrapper
887

    
888

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

    
903

    
904
@signed_terms_required
905
@login_required
906
@handle_membership
907
def disapprove_member(request, membership):
908
    try:
909
        membership.disapprove()
910
        realname = membership.person.realname
911
        msg = _('%s has been successfully removed from the group.' % realname)
912
        messages.success(request, msg)
913
    except BaseException, e:
914
        logger.exception(e)
915
        msg = _('Something went wrong during %s\'s disapproval.' % realname)
916
        messages.error(request, msg)
917

    
918

    
919

    
920

    
921
@signed_terms_required
922
@login_required
923
def add_members(request, group_id):
924
    if request.method != 'POST':
925
        return HttpResponseBadRequest(_('Bad method'))
926
    try:
927
        group = AstakosGroup.objects.select_related().get(id=group_id)
928
    except AstakosGroup.DoesNotExist:
929
        return HttpResponseBadRequest(_('Invalid group.'))
930
    search_form = AddGroupMembersForm(request.POST)
931
    if search_form.is_valid():
932
        users = search_form.get_valid_users()
933
        map(group.approve_member, users)
934
        search_form = AddGroupMembersForm()
935
    form = AstakosGroupUpdateForm(instance=group)
936
    return object_detail(request,
937
                         AstakosGroup.objects.all(),
938
                         object_id=group_id,
939
                         extra_context={'quota': group.quota,
940
                                        'form': form,
941
                                        'search_form' : search_form}
942
                         )
943

    
944

    
945
@signed_terms_required
946
@login_required
947
def resource_list(request):
948
    return render_response(
949
        template='im/astakosuserquota_list.html',
950
        context_instance=get_context(request),
951
        quota=request.user.quota
952
    )
953

    
954

    
955
def group_create_list(request):
956
    return render_response(
957
        template='im/astakosgroup_create_list.html',
958
        context_instance=get_context(request),
959
    )
960

    
961

    
962
@signed_terms_required
963
@login_required
964
def billing(request):
965
    
966
    today = datetime.today()
967
    month_last_day= calendar.monthrange(today.year, today.month)[1]
968
    
969
    start = request.POST.get('datefrom', None)
970
    if start:
971
        today = datetime.fromtimestamp(int(start))
972
        month_last_day= calendar.monthrange(today.year, today.month)[1]
973
    
974
    start = datetime(today.year, today.month, 1).strftime("%s")
975
    end = datetime(today.year, today.month, month_last_day).strftime("%s")
976
    r = request_billing.apply(args=('pgerakios@grnet.gr',
977
                                    int(start) * 1000,
978
                                    int(end) * 1000))
979
    data = {}
980
    
981
    try:
982
        status, data = r.result
983
        data=clear_billing_data(data)
984
        if status != 200:
985
            messages.error(request, _('Service response status: %d' % status))
986
    except:
987
        messages.error(request, r.result)
988
    
989
    print type(start)
990
    
991
    return render_response(
992
        template='im/billing.html',
993
        context_instance=get_context(request),
994
        data=data,
995
        zerodate=datetime(month=1,year=1970, day=1),
996
        today=today,
997
        start=int(start),
998
        month_last_day=month_last_day)  
999
    
1000
def clear_billing_data(data):
1001
    
1002
    # remove addcredits entries
1003
    def isnotcredit(e):
1004
        return e['serviceName'] != "addcredits"
1005
    
1006
    
1007
    
1008
    # separate services    
1009
    def servicefilter(service_name):
1010
        service = service_name
1011
        def fltr(e):
1012
            return e['serviceName'] == service
1013
        return fltr
1014
        
1015
    
1016
    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1017
    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1018
    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1019
    data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1020
        
1021
    return data