1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
37 from urllib import quote
38 from functools import wraps
39 from datetime import datetime, timedelta
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
60 from astakos.im.models import (
61 AstakosUser, ApprovalTerms, AstakosGroup, Resource,
62 EmailChange, GroupKind, Membership)
63 from astakos.im.activation_backends import get_backend, SimpleBackend
64 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
65 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
66 FeedbackForm, SignApprovalTermsForm,
67 ExtendedPasswordChangeForm, EmailChangeForm,
68 AstakosGroupCreationForm, AstakosGroupSearchForm,
69 AstakosGroupUpdateForm, AddGroupMembersForm)
70 from astakos.im.functions import (send_feedback, SendMailError,
71 invite as invite_func, logout as auth_logout,
72 activate as activate_func,
73 switch_account_to_shibboleth,
74 send_admin_notification,
75 SendNotificationError)
76 from astakos.im.settings import (
77 COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
80 from astakos.im.tasks import request_billing
82 logger = logging.getLogger(__name__)
85 def render_response(template, tab=None, status=200, reset_cookie=False,
86 context_instance=None, **kwargs):
88 Calls ``django.template.loader.render_to_string`` with an additional ``tab``
89 keyword argument and returns an ``django.http.HttpResponse`` with the
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)
99 set_cookie(response, context_instance['request'].user)
103 def requires_anonymous(func):
105 Decorator checkes whether the request.user is not Anonymous and in that case
106 redirects to `logout`.
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)
118 def signed_terms_required(func):
120 Decorator checkes whether the request.user is Anonymous and in that case
121 redirects to `logout`.
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(),
128 terms_uri = reverse('latest_terms') + '?' + params
129 return HttpResponseRedirect(terms_uri)
130 return func(request, *args, **kwargs)
134 @signed_terms_required
135 def index(request, login_template_name='im/login.html', extra_context=None):
137 If there is logged on user renders the profile page otherwise renders login page.
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``.
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``.
150 An dictionary of variables to add to the template context.
154 im/profile.html or im/login.html or ``template_name`` keyword argument.
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))
166 @signed_terms_required
167 @transaction.commit_manually
168 def invite(request, template_name='im/invitations.html', extra_context=None):
170 Allows a user to invite somebody else.
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.
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.
179 If the user isn't logged in, redirects to settings.LOGIN_URL.
184 A custom template to use. This is optional; if not specified,
185 this will default to ``im/invitations.html``.
188 An dictionary of variables to add to the template context.
192 im/invitations.html or ``template_name`` keyword argument.
196 The view expectes the following settings are defined:
198 * LOGIN_URL: login uri
199 * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
203 form = InvitationForm()
205 inviter = request.user
206 if request.method == 'POST':
207 form = InvitationForm(request.POST)
208 if inviter.invitations > 0:
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:
217 messages.error(request, message)
218 transaction.rollback()
219 except BaseException, e:
220 message = _('Something went wrong.')
221 messages.error(request, message)
223 transaction.rollback()
227 message = _('No invitations left')
228 messages.error(request, message)
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,
236 context = get_context(request, extra_context, **kwargs)
237 return render_response(template_name,
238 invitation_form=form,
239 context_instance=context)
243 @signed_terms_required
244 def edit_profile(request, template_name='im/profile.html', extra_context=None):
246 Allows a user to edit his/her profile.
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.
252 If the user isn't logged in, redirects to settings.LOGIN_URL.
257 A custom template to use. This is optional; if not specified,
258 this will default to ``im/profile.html``.
261 An dictionary of variables to add to the template context.
265 im/profile.html or ``template_name`` keyword argument.
269 The view expectes the following settings are defined:
271 * LOGIN_URL: login uri
273 extra_context = extra_context or {}
274 form = ProfileForm(instance=request.user)
275 extra_context['next'] = request.GET.get('next')
277 if request.method == 'POST':
278 form = ProfileForm(request.POST, instance=request.user)
281 prev_token = request.user.auth_token
283 reset_cookie = user.auth_token != prev_token
284 form = ProfileForm(instance=user)
285 next = request.POST.get('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
296 return render_response(template_name,
297 reset_cookie=reset_cookie,
299 context_instance=get_context(request,
303 @transaction.commit_manually
304 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
306 Allows a user to create a local account.
308 In case of GET request renders a form for entering the user information.
309 In case of POST handles the signup.
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);
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.
319 On unsuccessful creation, renders ``template_name`` with an error message.
324 A custom template to render. This is optional;
325 if not specified, this will default to ``im/signup.html``.
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``.
332 An dictionary of variables to add to the template context.
336 im/signup.html or ``template_name`` keyword argument.
337 im/signup_complete.html or ``on_success`` keyword argument.
339 if request.user.is_authenticated():
340 return HttpResponseRedirect(reverse('edit_profile'))
342 provider = get_query(request).get('provider', 'local')
345 backend = get_backend(request)
346 form = backend.get_signup_form(provider)
348 form = SimpleBackend(request).get_signup_form(provider)
349 messages.error(request, e)
350 if request.method == 'POST':
352 user = form.save(commit=False)
354 result = backend.handle_activation(user)
355 status = messages.SUCCESS
356 message = result.message
358 if 'additional_email' in form.cleaned_data:
359 additional_email = form.cleaned_data['additional_email']
360 if additional_email != user.email:
361 user.additionalmail_set.create(email=additional_email)
362 msg = 'Additional email: %s saved for user %s.' % (
363 additional_email, user.email)
364 logger.log(LOGGING_LEVEL, msg)
365 if user and user.is_active:
366 next = request.POST.get('next', '')
367 return prepare_response(request, user, next=next)
368 messages.add_message(request, status, message)
369 return render_response(on_success,
370 context_instance=get_context(request, extra_context))
371 except SendMailError, e:
373 messages.error(request, message)
374 transaction.rollback()
375 except BaseException, e:
376 message = _('Something went wrong.')
377 messages.error(request, message)
379 transaction.rollback()
382 return render_response(template_name,
385 context_instance=get_context(request, extra_context))
389 @signed_terms_required
390 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
392 Allows a user to send feedback.
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.
397 If the user isn't logged in, redirects to settings.LOGIN_URL.
402 A custom template to use. This is optional; if not specified,
403 this will default to ``im/feedback.html``.
406 An dictionary of variables to add to the template context.
410 im/signup.html or ``template_name`` keyword argument.
414 * LOGIN_URL: login uri
415 * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
417 if request.method == 'GET':
418 form = FeedbackForm()
419 if request.method == 'POST':
421 return HttpResponse('Unauthorized', status=401)
423 form = FeedbackForm(request.POST)
425 msg = form.cleaned_data['feedback_msg']
426 data = form.cleaned_data['feedback_data']
428 send_feedback(msg, data, request.user, email_template_name)
429 except SendMailError, e:
430 messages.error(request, message)
432 message = _('Feedback successfully sent')
433 messages.success(request, message)
434 return render_response(template_name,
436 context_instance=get_context(request, extra_context))
439 @signed_terms_required
440 def logout(request, template='registration/logged_out.html', extra_context=None):
442 Wraps `django.contrib.auth.logout` and delete the cookie.
444 response = HttpResponse()
445 if request.user.is_authenticated():
446 email = request.user.email
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')
453 response['Location'] = next
454 response.status_code = 302
457 response['Location'] = LOGOUT_NEXT
458 response.status_code = 301
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))
466 @transaction.commit_manually
467 def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
469 Activates the user identified by the ``auth`` request parameter, sends a welcome email
470 and renews the user token.
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.
475 token = request.GET.get('auth')
476 next = request.GET.get('next')
478 user = AstakosUser.objects.get(auth_token=token)
479 except AstakosUser.DoesNotExist:
480 return HttpResponseBadRequest(_('No such user'))
483 message = _('Account already active.')
484 messages.error(request, message)
485 return index(request)
488 local_user = AstakosUser.objects.get(
493 except AstakosUser.DoesNotExist:
497 greeting_email_template_name,
498 helpdesk_email_template_name,
501 response = prepare_response(request, user, next, renew=True)
504 except SendMailError, e:
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)
513 transaction.rollback()
514 return index(request)
517 user = switch_account_to_shibboleth(
520 greeting_email_template_name
522 response = prepare_response(request, user, next, renew=True)
525 except SendMailError, e:
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)
534 transaction.rollback()
535 return index(request)
538 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
543 term = ApprovalTerms.objects.order_by('-id')[0]
548 term = ApprovalTerms.objects.get(id=term_id)
549 except ApprovalTerms.DoesNotExist, e:
553 return HttpResponseRedirect(reverse('index'))
554 f = open(term.location, 'r')
557 if request.method == 'POST':
558 next = request.POST.get('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,
565 approval_terms_form=form,
566 context_instance=get_context(request, extra_context))
568 return HttpResponseRedirect(next)
571 if request.user.is_authenticated() and not request.user.signed_terms:
572 form = SignApprovalTermsForm(instance=request.user)
573 return render_response(template_name,
575 approval_terms_form=form,
576 context_instance=get_context(request, extra_context))
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)
586 @signed_terms_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',
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)
601 response = prepare_response(request, user)
604 except ValueError, e:
605 messages.error(request, e)
606 return render_response(confirm_template_name,
607 modified_user=user if 'user' in locals(
609 context_instance=get_context(request,
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():
619 ec = form.save(email_template_name, request)
620 except SendMailError, 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)
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)
632 return render_response(form_template_name,
634 context_instance=get_context(request,
638 @signed_terms_required
640 def group_add(request, kind_name='default'):
642 kind = GroupKind.objects.get(name=kind_name)
644 return HttpResponseBadRequest(_('No such group kind'))
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(
651 form_class=AstakosGroupCreationForm
654 (str(r.id), r) for r in Resource.objects.select_related().all())
656 if request.method == 'POST':
657 form = form_class(request.POST, request.FILES, resources=resources)
659 new_object = form.save()
662 new_object.owners = [request.user]
664 # save quota policies
665 for (rid, limit) in form.resources():
670 # TODO Should I stay or should I go???
673 new_object.astakosgroupquota_set.create(
677 policies.append('%s %d' % (r, limit))
678 msg = _("The %(verbose_name)s was created successfully.") %\
679 {"verbose_name": model._meta.verbose_name}
680 messages.success(request, msg, fail_silently=True)
684 send_admin_notification(
685 template_name='im/group_creation_notification.txt',
688 'owner': request.user,
689 'policies': policies,
691 subject='%s alpha2 testing group creation notification' % SITENAME
693 except SendNotificationError, e:
694 messages.error(request, e, fail_silently=True)
695 return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
701 form = form_class(data, resources=resources)
703 # Create the template, context, response
704 template_name = "%s/%s_form.html" % (
705 model._meta.app_label,
706 model._meta.object_name.lower()
708 t = template_loader.get_template(template_name)
709 c = RequestContext(request, {
712 }, context_processors)
713 return HttpResponse(t.render(c))
716 @signed_terms_required
718 def group_list(request):
719 list = request.user.astakos_groups.select_related().all()
720 return object_list(request, queryset=list,
727 @signed_terms_required
729 def group_detail(request, group_id):
731 group = AstakosGroup.objects.select_related().get(id=group_id)
732 except AstakosGroup.DoesNotExist:
733 return HttpResponseBadRequest(_('Invalid group.'))
734 form = AstakosGroupUpdateForm(instance=group)
735 search_form = AddGroupMembersForm()
736 return object_detail(request,
737 AstakosGroup.objects.all(),
739 extra_context={'quota': group.quota,
741 'search_form': search_form}
745 @signed_terms_required
747 def group_update(request, group_id):
748 if request.method != 'POST':
749 return HttpResponseBadRequest('Method not allowed.')
751 group = AstakosGroup.objects.select_related().get(id=group_id)
752 except AstakosGroup.DoesNotExist:
753 return HttpResponseBadRequest(_('Invalid group.'))
754 form = AstakosGroupUpdateForm(request.POST, instance=group)
757 search_form = AddGroupMembersForm()
758 return object_detail(request,
759 AstakosGroup.objects.all(),
761 extra_context={'quota': group.quota,
763 'search_form': search_form})
765 @signed_terms_required
767 def group_search(request, extra_context=None, **kwargs):
768 if request.method == 'GET':
769 form = AstakosGroupSearchForm()
771 form = AstakosGroupSearchForm(get_query(request))
773 q = form.cleaned_data['q'].strip()
774 queryset = AstakosGroup.objects.select_related(
775 ).filter(name__contains=q)
779 template_name='im/astakosgroup_list.html',
780 extra_context=dict(form=form,
782 return render_response(
783 template='im/astakosgroup_list.html',
785 context_instance=get_context(request, extra_context),
789 @signed_terms_required
791 def group_all(request, extra_context=None, **kwargs):
792 if request.method != 'POST':
793 return HttpResponseBadRequest(_('Bad method'))
796 AstakosGroup.objects.select_related().all(),
797 template_name='im/astakosgroup_list.html',
798 extra_context=dict(form=AstakosGroupSearchForm(),
802 @signed_terms_required
804 def group_join(request, group_id):
805 m = Membership(group_id=group_id,
807 date_requested=datetime.now()
811 post_save_redirect = reverse(
813 kwargs=dict(group_id=group_id)
815 return HttpResponseRedirect(post_save_redirect)
816 except IntegrityError, e:
818 msg = _('Failed to join group.')
819 messages.error(request, msg)
820 return group_search(request)
823 @signed_terms_required
825 def group_leave(request, group_id):
827 m = Membership.objects.select_related().get(
831 except Membership.DoesNotExist:
832 return HttpResponseBadRequest(_('Invalid membership.'))
833 if request.user in m.group.owner.all():
834 return HttpResponseForbidden(_('Owner can not leave the group.'))
835 return delete_object(
839 template_name='im/astakosgroup_list.html',
840 post_delete_redirect=reverse(
842 kwargs=dict(group_id=group_id)
847 def handle_membership(func):
849 def wrapper(request, group_id, user_id):
851 m = Membership.objects.select_related().get(
855 except Membership.DoesNotExist:
856 return HttpResponseBadRequest(_('Invalid membership.'))
858 if request.user not in m.group.owner.all():
859 return HttpResponseForbidden(_('User is not a group owner.'))
861 return render_response(
862 template='im/astakosgroup_detail.html',
863 context_instance=get_context(request),
870 @signed_terms_required
873 def approve_member(request, membership):
876 realname = membership.person.realname
877 msg = _('%s has been successfully joined the group.' % realname)
878 messages.success(request, msg)
879 except BaseException, e:
881 msg = _('Something went wrong during %s\'s approval.' % realname)
882 messages.error(request, msg)
885 @signed_terms_required
888 def disapprove_member(request, membership):
890 membership.disapprove()
891 realname = membership.person.realname
892 msg = _('%s has been successfully removed from the group.' % realname)
893 messages.success(request, msg)
894 except BaseException, e:
896 msg = _('Something went wrong during %s\'s disapproval.' % realname)
897 messages.error(request, msg)
902 @signed_terms_required
904 def add_members(request, group_id):
905 if request.method != 'POST':
906 return HttpResponseBadRequest(_('Bad method'))
908 group = AstakosGroup.objects.select_related().get(id=group_id)
909 except AstakosGroup.DoesNotExist:
910 return HttpResponseBadRequest(_('Invalid group.'))
911 search_form = AddGroupMembersForm(request.POST)
912 if search_form.is_valid():
913 users = search_form.get_valid_users()
914 map(group.approve_member, users)
915 search_form = AddGroupMembersForm()
916 form = AstakosGroupUpdateForm(instance=group)
917 return object_detail(request,
918 AstakosGroup.objects.all(),
920 extra_context={'quota': group.quota,
922 'search_form' : search_form}
926 @signed_terms_required
928 def resource_list(request):
929 return render_response(
930 template='im/astakosuserquota_list.html',
931 context_instance=get_context(request),
932 quota=request.user.quota
936 def group_create_list(request):
937 return render_response(
938 template='im/astakosgroup_create_list.html',
939 context_instance=get_context(request),
943 @signed_terms_required
945 def billing(request):
947 today = datetime.today()
948 month_last_day= calendar.monthrange(today.year, today.month)[1]
950 start = request.POST.get('datefrom', None)
952 today = datetime.fromtimestamp(int(start))
953 month_last_day= calendar.monthrange(today.year, today.month)[1]
955 start = datetime(today.year, today.month, 1).strftime("%s")
956 end = datetime(today.year, today.month, month_last_day).strftime("%s")
957 r = request_billing.apply(args=('pgerakios@grnet.gr',
963 status, data = r.result
964 data=clear_billing_data(data)
966 messages.error(request, _('Service response status: %d' % status))
968 messages.error(request, r.result)
972 return render_response(
973 template='im/billing.html',
974 context_instance=get_context(request),
976 zerodate=datetime(month=1,year=1970, day=1),
979 month_last_day=month_last_day)
981 def clear_billing_data(data):
983 # remove addcredits entries
985 return e['serviceName'] != "addcredits"
990 def servicefilter(service_name):
991 service = service_name
993 return e['serviceName'] == service
997 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
998 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
999 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1000 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])