Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (37.6 kB)

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

    
34
import logging
35
import calendar
36

    
37
from urllib import quote
38
from functools import wraps
39
from datetime import datetime, timedelta
40

    
41
from django.contrib import messages
42
from django.contrib.auth.decorators import login_required
43
from django.contrib.auth.views import password_change
44
from django.core.urlresolvers import reverse
45
from django.db import transaction
46
from django.db.models import Q
47
from django.db.utils import IntegrityError
48
from django.forms.fields import URLField
49
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
50
    HttpResponseRedirect, HttpResponseBadRequest
51
from django.shortcuts import redirect
52
from django.template import RequestContext, loader
53
from django.utils.http import urlencode
54
from django.utils.translation import ugettext as _
55
from django.views.generic.create_update import (create_object, delete_object,
56
                                                get_model_and_form_class)
57
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
                              TimelineForm)
72
from astakos.im.functions import (send_feedback, SendMailError,
73
                                  invite as invite_func, logout as auth_logout,
74
                                  activate as activate_func,
75
                                  switch_account_to_shibboleth,
76
                                  send_admin_notification,
77
                                  SendNotificationError)
78
from astakos.im.endpoints.quotaholder import timeline_charge
79
from astakos.im.settings import (
80
    COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
81
    LOGGING_LEVEL, PAGINATE_BY)
82
from astakos.im.tasks import request_billing
83

    
84
logger = logging.getLogger(__name__)
85

    
86

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

    
104

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

    
119

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

    
135

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

141
    **Arguments**
142

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

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

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

154
    **Template:**
155

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

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

    
166

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

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

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

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

183
    **Arguments**
184

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

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

192
    **Template:**
193

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

196
    **Settings:**
197

198
    The view expectes the following settings are defined:
199

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

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

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

    
243

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

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

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

256
    **Arguments**
257

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

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

265
    **Template:**
266

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

269
    **Settings:**
270

271
    The view expectes the following settings are defined:
272

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

    
304

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

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

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

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

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

323
    **Arguments**
324

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

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

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

336
    **Template:**
337

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

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

    
389

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

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

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

401
    **Arguments**
402

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

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

410
    **Template:**
411

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

414
    **Settings:**
415

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

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

    
440

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

    
467

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

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

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

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

    
539

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

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

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

    
580

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

    
587

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

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

    
639

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

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

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

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

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

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

    
717

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

    
746

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

    
764

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

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

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

    
822

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

    
843

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

    
867

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

    
890

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

    
905

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

    
920

    
921

    
922

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

    
946

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

    
956

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

    
963

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

    
1025
@signed_terms_required
1026
@login_required
1027
def timeline(request):
1028
    data = {'entity':request.user.email}
1029
    timeline_body = ()
1030
    timeline_header = ()
1031
    form = TimelineForm(data)
1032
    if request.method == 'POST':
1033
        data = request.POST
1034
        form = TimelineForm(data)
1035
        if form.is_valid():
1036
            data = form.cleaned_data
1037
            timeline_header = ('entity', 'resource',
1038
                               'event name', 'event date',
1039
                               'incremental cost', 'total cost')
1040
            timeline_body = timeline_charge(
1041
                                    data['entity'],     data['resource'],
1042
                                    data['start_date'], data['end_date'],
1043
                                    data['details'],    data['operation'])
1044
        
1045
    return render_response(template='im/timeline.html',
1046
                           context_instance=get_context(request),
1047
                           form=form,
1048
                           timeline_header=timeline_header,
1049
                           timeline_body=timeline_body)