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
59 from django.core.paginator import Paginator, InvalidPage
61 from astakos.im.models import (
62 AstakosUser, ApprovalTerms, AstakosGroup, Resource,
63 EmailChange, GroupKind, Membership)
64 from astakos.im.activation_backends import get_backend, SimpleBackend
65 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
66 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
67 FeedbackForm, SignApprovalTermsForm,
68 ExtendedPasswordChangeForm, EmailChangeForm,
69 AstakosGroupCreationForm, AstakosGroupSearchForm,
70 AstakosGroupUpdateForm, AddGroupMembersForm)
71 from astakos.im.functions import (send_feedback, SendMailError,
72 invite as invite_func, logout as auth_logout,
73 activate as activate_func,
74 switch_account_to_shibboleth,
75 send_admin_notification,
76 SendNotificationError)
77 from astakos.im.settings import (
78 COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
79 LOGGING_LEVEL, PAGINATE_BY)
80 from astakos.im.tasks import request_billing
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', '')
368 return prepare_response(request, user, next=next)
369 messages.add_message(request, status, message)
371 return render_response(on_success,
372 context_instance=get_context(request, extra_context))
373 except SendMailError, e:
375 messages.error(request, message)
376 transaction.rollback()
377 except BaseException, e:
378 message = _('Something went wrong.')
379 messages.error(request, message)
381 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, uplimit) in form.resources():
670 # TODO Should I stay or should I go???
673 new_object.astakosgroupquota_set.create(
677 policies.append('%s %d' % (r, uplimit))
678 msg = _("The %(verbose_name)s was created successfully.") %\
679 {"verbose_name": model._meta.verbose_name}
680 messages.success(request, msg, fail_silently=True)
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 q = request.user.astakos_groups.none()
720 list = request.user.astakos_groups.select_related().all()
722 d['own'] = [g for g in list if request.user in g.owner.all()]
723 d['other'] = list.exclude(id__in=(g.id for g in d['own']))
724 for k, queryset in d.iteritems():
725 paginator = Paginator(queryset, PAGINATE_BY)
726 page = request.GET.get('%s_page' % k, 1)
728 page_number = int(page)
731 page_number = paginator.num_pages
733 # Page is not 'last', nor can it be converted to an int.
736 page_obj = locals()['%s_page_obj' % k] = paginator.page(page_number)
739 return object_list(request, queryset=q,
740 extra_context={'is_search':False,
741 'mine': locals()['own_page_obj'],
742 'other': locals()['other_page_obj']})
745 @signed_terms_required
747 def group_detail(request, group_id):
749 group = AstakosGroup.objects.select_related().get(id=group_id)
750 except AstakosGroup.DoesNotExist:
751 return HttpResponseBadRequest(_('Invalid group.'))
752 form = AstakosGroupUpdateForm(instance=group)
753 search_form = AddGroupMembersForm()
754 return object_detail(request,
755 AstakosGroup.objects.all(),
757 extra_context={'quota': group.quota,
759 'search_form': search_form}
763 @signed_terms_required
765 def group_update(request, group_id):
766 if request.method != 'POST':
767 return HttpResponseBadRequest('Method not allowed.')
769 group = AstakosGroup.objects.select_related().get(id=group_id)
770 except AstakosGroup.DoesNotExist:
771 return HttpResponseBadRequest(_('Invalid group.'))
772 form = AstakosGroupUpdateForm(request.POST, instance=group)
775 search_form = AddGroupMembersForm()
776 return object_detail(request,
777 AstakosGroup.objects.all(),
779 extra_context={'quota': group.quota,
781 'search_form': search_form})
783 @signed_terms_required
785 def group_search(request, extra_context=None, **kwargs):
786 q = request.GET.get('q')
787 if request.method == 'GET':
788 form = AstakosGroupSearchForm({'q': q} if q else None)
790 form = AstakosGroupSearchForm(get_query(request))
792 q = form.cleaned_data['q'].strip()
794 queryset = AstakosGroup.objects.select_related(
795 ).filter(name__contains=q)
797 queryset = AstakosGroup.objects.none()
801 paginate_by=PAGINATE_BY,
802 page=request.GET.get('page') or 1,
803 template_name='im/astakosgroup_list.html',
804 extra_context=dict(form=form,
808 @signed_terms_required
810 def group_all(request, extra_context=None, **kwargs):
813 AstakosGroup.objects.select_related().all(),
814 paginate_by=PAGINATE_BY,
815 page=request.GET.get('page') or 1,
816 template_name='im/astakosgroup_list.html',
817 extra_context=dict(form=AstakosGroupSearchForm(),
821 @signed_terms_required
823 def group_join(request, group_id):
824 m = Membership(group_id=group_id,
826 date_requested=datetime.now()
830 post_save_redirect = reverse(
832 kwargs=dict(group_id=group_id)
834 return HttpResponseRedirect(post_save_redirect)
835 except IntegrityError, e:
837 msg = _('Failed to join group.')
838 messages.error(request, msg)
839 return group_search(request)
842 @signed_terms_required
844 def group_leave(request, group_id):
846 m = Membership.objects.select_related().get(
850 except Membership.DoesNotExist:
851 return HttpResponseBadRequest(_('Invalid membership.'))
852 if request.user in m.group.owner.all():
853 return HttpResponseForbidden(_('Owner can not leave the group.'))
854 return delete_object(
858 template_name='im/astakosgroup_list.html',
859 post_delete_redirect=reverse(
861 kwargs=dict(group_id=group_id)
866 def handle_membership(func):
868 def wrapper(request, group_id, user_id):
870 m = Membership.objects.select_related().get(
874 except Membership.DoesNotExist:
875 return HttpResponseBadRequest(_('Invalid membership.'))
877 if request.user not in m.group.owner.all():
878 return HttpResponseForbidden(_('User is not a group owner.'))
880 return render_response(
881 template='im/astakosgroup_detail.html',
882 context_instance=get_context(request),
889 @signed_terms_required
892 def approve_member(request, membership):
895 realname = membership.person.realname
896 msg = _('%s has been successfully joined the group.' % realname)
897 messages.success(request, msg)
898 except BaseException, e:
900 msg = _('Something went wrong during %s\'s approval.' % realname)
901 messages.error(request, msg)
904 @signed_terms_required
907 def disapprove_member(request, membership):
909 membership.disapprove()
910 realname = membership.person.realname
911 msg = _('%s has been successfully removed from the group.' % realname)
912 messages.success(request, msg)
913 except BaseException, e:
915 msg = _('Something went wrong during %s\'s disapproval.' % realname)
916 messages.error(request, msg)
921 @signed_terms_required
923 def add_members(request, group_id):
924 if request.method != 'POST':
925 return HttpResponseBadRequest(_('Bad method'))
927 group = AstakosGroup.objects.select_related().get(id=group_id)
928 except AstakosGroup.DoesNotExist:
929 return HttpResponseBadRequest(_('Invalid group.'))
930 search_form = AddGroupMembersForm(request.POST)
931 if search_form.is_valid():
932 users = search_form.get_valid_users()
933 map(group.approve_member, users)
934 search_form = AddGroupMembersForm()
935 form = AstakosGroupUpdateForm(instance=group)
936 return object_detail(request,
937 AstakosGroup.objects.all(),
939 extra_context={'quota': group.quota,
941 'search_form' : search_form}
945 @signed_terms_required
947 def resource_list(request):
948 return render_response(
949 template='im/astakosuserquota_list.html',
950 context_instance=get_context(request),
951 quota=request.user.quota
955 def group_create_list(request):
956 return render_response(
957 template='im/astakosgroup_create_list.html',
958 context_instance=get_context(request),
962 @signed_terms_required
964 def billing(request):
966 today = datetime.today()
967 month_last_day= calendar.monthrange(today.year, today.month)[1]
969 start = request.POST.get('datefrom', None)
971 today = datetime.fromtimestamp(int(start))
972 month_last_day= calendar.monthrange(today.year, today.month)[1]
974 start = datetime(today.year, today.month, 1).strftime("%s")
975 end = datetime(today.year, today.month, month_last_day).strftime("%s")
976 r = request_billing.apply(args=('pgerakios@grnet.gr',
982 status, data = r.result
983 data=clear_billing_data(data)
985 messages.error(request, _('Service response status: %d' % status))
987 messages.error(request, r.result)
991 return render_response(
992 template='im/billing.html',
993 context_instance=get_context(request),
995 zerodate=datetime(month=1,year=1970, day=1),
998 month_last_day=month_last_day)
1000 def clear_billing_data(data):
1002 # remove addcredits entries
1004 return e['serviceName'] != "addcredits"
1009 def servicefilter(service_name):
1010 service = service_name
1012 return e['serviceName'] == service
1016 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1017 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1018 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1019 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])