Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (38.8 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.settings import (
79
    COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
80
    LOGGING_LEVEL, PAGINATE_BY)
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
                    transaction.commit()
369
                    return prepare_response(request, user, next=next)
370
                messages.add_message(request, status, message)
371
                transaction.commit()
372
                return render_response(on_success,
373
                                       context_instance=get_context(request, extra_context))
374
            except SendMailError, e:
375
                message = e.message
376
                messages.error(request, message)
377
                transaction.rollback()
378
            except BaseException, e:
379
                message = _('Something went wrong.')
380
                messages.error(request, message)
381
                logger.exception(e)
382
                transaction.rollback()
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, uplimit) 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
                        uplimit=uplimit
677
                    )
678
                policies.append('%s %d' % (r, uplimit))
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
    q = request.user.astakos_groups.none()
721
    list = request.user.astakos_groups.select_related().all()
722
    d = {}
723
    d['own'] = [g for g in list if request.user in g.owner.all()]
724
    d['other'] = list.exclude(id__in=(g.id for g in d['own']))
725
    for k, queryset in d.iteritems():
726
        paginator = Paginator(queryset, PAGINATE_BY)
727
        page = request.GET.get('%s_page' % k, 1)
728
        try:
729
            page_number = int(page)
730
        except ValueError:
731
            if page == 'last':
732
                page_number = paginator.num_pages
733
            else:
734
                # Page is not 'last', nor can it be converted to an int.
735
                raise Http404
736
        try:
737
            page_obj = locals()['%s_page_obj' % k] = paginator.page(page_number)
738
        except InvalidPage:
739
            raise Http404
740
    return object_list(request, queryset=q,
741
                       extra_context={'is_search':False,
742
                                      'mine': locals()['own_page_obj'],
743
                                      'other': locals()['other_page_obj']})
744

    
745

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

    
763

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

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

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

    
821

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

    
842

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

    
866

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

    
889

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

    
904

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

    
919

    
920

    
921

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

    
945

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

    
955

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

    
962

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

    
1024
@signed_terms_required
1025
@login_required
1026
def timeline(request):
1027
    data = {'entity':request.user.email}
1028
    l = []
1029
    if request.method == 'POST':
1030
        data = request.POST
1031
        #l =[(u'test/apples', u'2012-09-20T11:39:53.1797', 192172055040L, 25569215632749L),
1032
        #    (u'test/pears/kate.jpg', u'2012-09-20T11:45:14.1153', 381640896L, 25626036449581L),
1033
        #    (u'test/pears/kate_beckinsale.jpg', u'2012-09-20T11:45:14.5830', 0L, 25626036449581L),
1034
        #    (u'test/pears/How To Win Friends And Influence People.pdf', u'2012-09-20T11:45:15.0694', 0L, 25626036449581L),
1035
        #    (u'test/pears/moms_birthday.jpg', u'2012-09-20T11:45:15.5615', 0L, 25626036449581L),
1036
        #    (u'test/pears/poodle_strut.mov', u'2012-09-20T11:45:16.0290', 0L, 25626036449581L),
1037
        #    (u'test/pears/Disturbed - Down With The Sickness.mp3', u'2012-09-20T11:45:16.4950', 0L, 25626036449581L),
1038
        #    (u'test/pears/army_of_darkness.avi', u'2012-09-20T11:45:16.9844', 0L, 25626036449581L),
1039
        #    (u'test/pears/the_mad.avi', u'2012-09-20T11:45:17.4516', 0L, 25626036449581L),
1040
        #    (u'test/apples/photos/animals/dogs/poodle.jpg', u'2012-09-20T11:45:17.9281', 0L, 25626036449581L),
1041
        #    (u'test/apples/photos/animals/dogs/terrier.jpg', u'2012-09-20T11:45:18.3918', 0L, 25626036449581L),
1042
        #    (u'test/apples/photos/animals/cats/persian.jpg', u'2012-09-20T11:45:18.8626', 0L, 25626036449581L),
1043
        #    (u'test/apples/photos/animals/cats/siamese.jpg', u'2012-09-20T11:45:19.3686', 0L, 25626036449581L),
1044
        #    (u'test/apples/photos/plants/fern.jpg', u'2012-09-20T11:45:19.8464', 0L, 25626036449581L),
1045
        #    (u'test/apples/photos/plants/rose.jpg', u'2012-09-20T11:45:20.7334', 0L, 25626036449581L)]
1046
        timeline_header = (data['entity'], data['resource'],
1047
                           data['start_date'], data['end_date'],
1048
                           data['details'], data['operation'])
1049
        timeline_body = [timeline_header] * 2
1050
    form = TimelineForm(data)
1051
    if form.is_valid():
1052
        print '>>>', form.cleaned_data
1053
    return render_response(template='im/timeline.html',
1054
                           context_instance=get_context(request),
1055
                           form=form,
1056
                           timeline_header=timeline_header,
1057
                           timeline_body=timeline_body)