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,
72 from astakos.im.functions import (send_feedback, SendMailError,
73 invite as invite_func, logout as auth_logout,
74 activate as activate_func,
75 switch_account_to_shibboleth,
76 send_admin_notification,
77 SendNotificationError)
78 from astakos.im.endpoints.quotaholder import timeline_charge
79 from astakos.im.settings import (
80 COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
81 LOGGING_LEVEL, PAGINATE_BY)
82 from astakos.im.tasks import request_billing
84 logger = logging.getLogger(__name__)
87 def render_response(template, tab=None, status=200, reset_cookie=False,
88 context_instance=None, **kwargs):
90 Calls ``django.template.loader.render_to_string`` with an additional ``tab``
91 keyword argument and returns an ``django.http.HttpResponse`` with the
95 tab = template.partition('_')[0].partition('.html')[0]
96 kwargs.setdefault('tab', tab)
97 html = loader.render_to_string(
98 template, kwargs, context_instance=context_instance)
99 response = HttpResponse(html, status=status)
101 set_cookie(response, context_instance['request'].user)
105 def requires_anonymous(func):
107 Decorator checkes whether the request.user is not Anonymous and in that case
108 redirects to `logout`.
111 def wrapper(request, *args):
112 if not request.user.is_anonymous():
113 next = urlencode({'next': request.build_absolute_uri()})
114 logout_uri = reverse(logout) + '?' + next
115 return HttpResponseRedirect(logout_uri)
116 return func(request, *args)
120 def signed_terms_required(func):
122 Decorator checkes whether the request.user is Anonymous and in that case
123 redirects to `logout`.
126 def wrapper(request, *args, **kwargs):
127 if request.user.is_authenticated() and not request.user.signed_terms:
128 params = urlencode({'next': request.build_absolute_uri(),
130 terms_uri = reverse('latest_terms') + '?' + params
131 return HttpResponseRedirect(terms_uri)
132 return func(request, *args, **kwargs)
136 @signed_terms_required
137 def index(request, login_template_name='im/login.html', extra_context=None):
139 If there is logged on user renders the profile page otherwise renders login page.
143 ``login_template_name``
144 A custom login template to use. This is optional; if not specified,
145 this will default to ``im/login.html``.
147 ``profile_template_name``
148 A custom profile template to use. This is optional; if not specified,
149 this will default to ``im/profile.html``.
152 An dictionary of variables to add to the template context.
156 im/profile.html or im/login.html or ``template_name`` keyword argument.
159 template_name = login_template_name
160 if request.user.is_authenticated():
161 return HttpResponseRedirect(reverse('edit_profile'))
162 return render_response(template_name,
163 login_form=LoginForm(request=request),
164 context_instance=get_context(request, extra_context))
168 @signed_terms_required
169 @transaction.commit_manually
170 def invite(request, template_name='im/invitations.html', extra_context=None):
172 Allows a user to invite somebody else.
174 In case of GET request renders a form for providing the invitee information.
175 In case of POST checks whether the user has not run out of invitations and then
176 sends an invitation email to singup to the service.
178 The view uses commit_manually decorator in order to ensure the number of the
179 user invitations is going to be updated only if the email has been successfully sent.
181 If the user isn't logged in, redirects to settings.LOGIN_URL.
186 A custom template to use. This is optional; if not specified,
187 this will default to ``im/invitations.html``.
190 An dictionary of variables to add to the template context.
194 im/invitations.html or ``template_name`` keyword argument.
198 The view expectes the following settings are defined:
200 * LOGIN_URL: login uri
201 * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
205 form = InvitationForm()
207 inviter = request.user
208 if request.method == 'POST':
209 form = InvitationForm(request.POST)
210 if inviter.invitations > 0:
213 invitation = form.save()
214 invite_func(invitation, inviter)
215 message = _('Invitation sent to %s' % invitation.username)
216 messages.success(request, message)
217 except SendMailError, e:
219 messages.error(request, message)
220 transaction.rollback()
221 except BaseException, e:
222 message = _('Something went wrong.')
223 messages.error(request, message)
225 transaction.rollback()
229 message = _('No invitations left')
230 messages.error(request, message)
232 sent = [{'email': inv.username,
233 'realname': inv.realname,
234 'is_consumed': inv.is_consumed}
235 for inv in request.user.invitations_sent.all()]
236 kwargs = {'inviter': inviter,
238 context = get_context(request, extra_context, **kwargs)
239 return render_response(template_name,
240 invitation_form=form,
241 context_instance=context)
245 @signed_terms_required
246 def edit_profile(request, template_name='im/profile.html', extra_context=None):
248 Allows a user to edit his/her profile.
250 In case of GET request renders a form for displaying the user information.
251 In case of POST updates the user informantion and redirects to ``next``
252 url parameter if exists.
254 If the user isn't logged in, redirects to settings.LOGIN_URL.
259 A custom template to use. This is optional; if not specified,
260 this will default to ``im/profile.html``.
263 An dictionary of variables to add to the template context.
267 im/profile.html or ``template_name`` keyword argument.
271 The view expectes the following settings are defined:
273 * LOGIN_URL: login uri
275 extra_context = extra_context or {}
276 form = ProfileForm(instance=request.user)
277 extra_context['next'] = request.GET.get('next')
279 if request.method == 'POST':
280 form = ProfileForm(request.POST, instance=request.user)
283 prev_token = request.user.auth_token
285 reset_cookie = user.auth_token != prev_token
286 form = ProfileForm(instance=user)
287 next = request.POST.get('next')
289 return redirect(next)
290 msg = _('Profile has been updated successfully')
291 messages.success(request, msg)
292 except ValueError, ve:
293 messages.success(request, ve)
294 elif request.method == "GET":
295 if not request.user.is_verified:
296 request.user.is_verified = True
298 return render_response(template_name,
299 reset_cookie=reset_cookie,
301 context_instance=get_context(request,
305 @transaction.commit_manually
306 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
308 Allows a user to create a local account.
310 In case of GET request renders a form for entering the user information.
311 In case of POST handles the signup.
313 The user activation will be delegated to the backend specified by the ``backend`` keyword argument
314 if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
315 if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
316 (see activation_backends);
318 Upon successful user creation, if ``next`` url parameter is present the user is redirected there
319 otherwise renders the same page with a success message.
321 On unsuccessful creation, renders ``template_name`` with an error message.
326 A custom template to render. This is optional;
327 if not specified, this will default to ``im/signup.html``.
330 A custom template to render in case of success. This is optional;
331 if not specified, this will default to ``im/signup_complete.html``.
334 An dictionary of variables to add to the template context.
338 im/signup.html or ``template_name`` keyword argument.
339 im/signup_complete.html or ``on_success`` keyword argument.
341 if request.user.is_authenticated():
342 return HttpResponseRedirect(reverse('edit_profile'))
344 provider = get_query(request).get('provider', 'local')
347 backend = get_backend(request)
348 form = backend.get_signup_form(provider)
350 form = SimpleBackend(request).get_signup_form(provider)
351 messages.error(request, e)
352 if request.method == 'POST':
354 user = form.save(commit=False)
356 result = backend.handle_activation(user)
357 status = messages.SUCCESS
358 message = result.message
360 if 'additional_email' in form.cleaned_data:
361 additional_email = form.cleaned_data['additional_email']
362 if additional_email != user.email:
363 user.additionalmail_set.create(email=additional_email)
364 msg = 'Additional email: %s saved for user %s.' % (
365 additional_email, user.email)
366 logger.log(LOGGING_LEVEL, msg)
367 if user and user.is_active:
368 next = request.POST.get('next', '')
370 return prepare_response(request, user, next=next)
371 messages.add_message(request, status, message)
373 return render_response(on_success,
374 context_instance=get_context(request, extra_context))
375 except SendMailError, e:
377 messages.error(request, message)
378 transaction.rollback()
379 except BaseException, e:
380 message = _('Something went wrong.')
381 messages.error(request, message)
383 transaction.rollback()
384 return render_response(template_name,
387 context_instance=get_context(request, extra_context))
391 @signed_terms_required
392 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
394 Allows a user to send feedback.
396 In case of GET request renders a form for providing the feedback information.
397 In case of POST sends an email to support team.
399 If the user isn't logged in, redirects to settings.LOGIN_URL.
404 A custom template to use. This is optional; if not specified,
405 this will default to ``im/feedback.html``.
408 An dictionary of variables to add to the template context.
412 im/signup.html or ``template_name`` keyword argument.
416 * LOGIN_URL: login uri
417 * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
419 if request.method == 'GET':
420 form = FeedbackForm()
421 if request.method == 'POST':
423 return HttpResponse('Unauthorized', status=401)
425 form = FeedbackForm(request.POST)
427 msg = form.cleaned_data['feedback_msg']
428 data = form.cleaned_data['feedback_data']
430 send_feedback(msg, data, request.user, email_template_name)
431 except SendMailError, e:
432 messages.error(request, message)
434 message = _('Feedback successfully sent')
435 messages.success(request, message)
436 return render_response(template_name,
438 context_instance=get_context(request, extra_context))
441 @signed_terms_required
442 def logout(request, template='registration/logged_out.html', extra_context=None):
444 Wraps `django.contrib.auth.logout` and delete the cookie.
446 response = HttpResponse()
447 if request.user.is_authenticated():
448 email = request.user.email
450 response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
451 msg = 'Cookie deleted for %s' % email
452 logger.log(LOGGING_LEVEL, msg)
453 next = request.GET.get('next')
455 response['Location'] = next
456 response.status_code = 302
459 response['Location'] = LOGOUT_NEXT
460 response.status_code = 301
462 messages.success(request, _('You have successfully logged out.'))
463 context = get_context(request, extra_context)
464 response.write(loader.render_to_string(template, context_instance=context))
468 @transaction.commit_manually
469 def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
471 Activates the user identified by the ``auth`` request parameter, sends a welcome email
472 and renews the user token.
474 The view uses commit_manually decorator in order to ensure the user state will be updated
475 only if the email will be send successfully.
477 token = request.GET.get('auth')
478 next = request.GET.get('next')
480 user = AstakosUser.objects.get(auth_token=token)
481 except AstakosUser.DoesNotExist:
482 return HttpResponseBadRequest(_('No such user'))
485 message = _('Account already active.')
486 messages.error(request, message)
487 return index(request)
490 local_user = AstakosUser.objects.get(
495 except AstakosUser.DoesNotExist:
499 greeting_email_template_name,
500 helpdesk_email_template_name,
503 response = prepare_response(request, user, next, renew=True)
506 except SendMailError, e:
508 messages.error(request, message)
509 transaction.rollback()
510 return index(request)
511 except BaseException, e:
512 message = _('Something went wrong.')
513 messages.error(request, message)
515 transaction.rollback()
516 return index(request)
519 user = switch_account_to_shibboleth(
522 greeting_email_template_name
524 response = prepare_response(request, user, next, renew=True)
527 except SendMailError, e:
529 messages.error(request, message)
530 transaction.rollback()
531 return index(request)
532 except BaseException, e:
533 message = _('Something went wrong.')
534 messages.error(request, message)
536 transaction.rollback()
537 return index(request)
540 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
545 term = ApprovalTerms.objects.order_by('-id')[0]
550 term = ApprovalTerms.objects.get(id=term_id)
551 except ApprovalTerms.DoesNotExist, e:
555 return HttpResponseRedirect(reverse('index'))
556 f = open(term.location, 'r')
559 if request.method == 'POST':
560 next = request.POST.get('next')
562 next = reverse('index')
563 form = SignApprovalTermsForm(request.POST, instance=request.user)
564 if not form.is_valid():
565 return render_response(template_name,
567 approval_terms_form=form,
568 context_instance=get_context(request, extra_context))
570 return HttpResponseRedirect(next)
573 if request.user.is_authenticated() and not request.user.signed_terms:
574 form = SignApprovalTermsForm(instance=request.user)
575 return render_response(template_name,
577 approval_terms_form=form,
578 context_instance=get_context(request, extra_context))
581 @signed_terms_required
582 def change_password(request):
583 return password_change(request,
584 post_change_redirect=reverse('edit_profile'),
585 password_change_form=ExtendedPasswordChangeForm)
588 @signed_terms_required
590 @transaction.commit_manually
591 def change_email(request, activation_key=None,
592 email_template_name='registration/email_change_email.txt',
593 form_template_name='registration/email_change_form.html',
594 confirm_template_name='registration/email_change_done.html',
598 user = EmailChange.objects.change_email(activation_key)
599 if request.user.is_authenticated() and request.user == user:
600 msg = _('Email changed successfully.')
601 messages.success(request, msg)
603 response = prepare_response(request, user)
606 except ValueError, e:
607 messages.error(request, e)
608 return render_response(confirm_template_name,
609 modified_user=user if 'user' in locals(
611 context_instance=get_context(request,
614 if not request.user.is_authenticated():
615 path = quote(request.get_full_path())
616 url = request.build_absolute_uri(reverse('index'))
617 return HttpResponseRedirect(url + '?next=' + path)
618 form = EmailChangeForm(request.POST or None)
619 if request.method == 'POST' and form.is_valid():
621 ec = form.save(email_template_name, request)
622 except SendMailError, e:
624 messages.error(request, msg)
625 transaction.rollback()
626 except IntegrityError, e:
627 msg = _('There is already a pending change email request.')
628 messages.error(request, msg)
630 msg = _('Change email request has been registered succefully.\
631 You are going to receive a verification email in the new address.')
632 messages.success(request, msg)
634 return render_response(form_template_name,
636 context_instance=get_context(request,
640 @signed_terms_required
642 def group_add(request, kind_name='default'):
644 kind = GroupKind.objects.get(name=kind_name)
646 return HttpResponseBadRequest(_('No such group kind'))
648 template_loader = loader
649 post_save_redirect = '/im/group/%(id)s/'
650 context_processors = None
651 model, form_class = get_model_and_form_class(
653 form_class=AstakosGroupCreationForm
656 (str(r.id), r) for r in Resource.objects.select_related().all())
658 if request.method == 'POST':
659 form = form_class(request.POST, request.FILES, resources=resources)
661 new_object = form.save()
664 new_object.owners = [request.user]
666 # save quota policies
667 for (rid, uplimit) in form.resources():
672 # TODO Should I stay or should I go???
675 new_object.astakosgroupquota_set.create(
679 policies.append('%s %d' % (r, uplimit))
680 msg = _("The %(verbose_name)s was created successfully.") %\
681 {"verbose_name": model._meta.verbose_name}
682 messages.success(request, msg, fail_silently=True)
686 send_admin_notification(
687 template_name='im/group_creation_notification.txt',
690 'owner': request.user,
691 'policies': policies,
693 subject='%s alpha2 testing group creation notification' % SITENAME
695 except SendNotificationError, e:
696 messages.error(request, e, fail_silently=True)
697 return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
703 form = form_class(data, resources=resources)
705 # Create the template, context, response
706 template_name = "%s/%s_form.html" % (
707 model._meta.app_label,
708 model._meta.object_name.lower()
710 t = template_loader.get_template(template_name)
711 c = RequestContext(request, {
714 }, context_processors)
715 return HttpResponse(t.render(c))
718 @signed_terms_required
720 def group_list(request):
721 q = request.user.astakos_groups.none()
722 list = request.user.astakos_groups.select_related().all()
724 d['own'] = [g for g in list if request.user in g.owner.all()]
725 d['other'] = list.exclude(id__in=(g.id for g in d['own']))
726 for k, queryset in d.iteritems():
727 paginator = Paginator(queryset, PAGINATE_BY)
728 page = request.GET.get('%s_page' % k, 1)
730 page_number = int(page)
733 page_number = paginator.num_pages
735 # Page is not 'last', nor can it be converted to an int.
738 page_obj = locals()['%s_page_obj' % k] = paginator.page(page_number)
741 return object_list(request, queryset=q,
742 extra_context={'is_search':False,
743 'mine': locals()['own_page_obj'],
744 'other': locals()['other_page_obj']})
747 @signed_terms_required
749 def group_detail(request, group_id):
751 group = AstakosGroup.objects.select_related().get(id=group_id)
752 except AstakosGroup.DoesNotExist:
753 return HttpResponseBadRequest(_('Invalid group.'))
754 form = AstakosGroupUpdateForm(instance=group)
755 search_form = AddGroupMembersForm()
756 return object_detail(request,
757 AstakosGroup.objects.all(),
759 extra_context={'quota': group.quota,
761 'search_form': search_form}
765 @signed_terms_required
767 def group_update(request, group_id):
768 if request.method != 'POST':
769 return HttpResponseBadRequest('Method not allowed.')
771 group = AstakosGroup.objects.select_related().get(id=group_id)
772 except AstakosGroup.DoesNotExist:
773 return HttpResponseBadRequest(_('Invalid group.'))
774 form = AstakosGroupUpdateForm(request.POST, instance=group)
777 search_form = AddGroupMembersForm()
778 return object_detail(request,
779 AstakosGroup.objects.all(),
781 extra_context={'quota': group.quota,
783 'search_form': search_form})
785 @signed_terms_required
787 def group_search(request, extra_context=None, **kwargs):
788 q = request.GET.get('q')
789 if request.method == 'GET':
790 form = AstakosGroupSearchForm({'q': q} if q else None)
792 form = AstakosGroupSearchForm(get_query(request))
794 q = form.cleaned_data['q'].strip()
796 queryset = AstakosGroup.objects.select_related(
797 ).filter(name__contains=q)
799 queryset = AstakosGroup.objects.none()
803 paginate_by=PAGINATE_BY,
804 page=request.GET.get('page') or 1,
805 template_name='im/astakosgroup_list.html',
806 extra_context=dict(form=form,
810 @signed_terms_required
812 def group_all(request, extra_context=None, **kwargs):
815 AstakosGroup.objects.select_related().all(),
816 paginate_by=PAGINATE_BY,
817 page=request.GET.get('page') or 1,
818 template_name='im/astakosgroup_list.html',
819 extra_context=dict(form=AstakosGroupSearchForm(),
823 @signed_terms_required
825 def group_join(request, group_id):
826 m = Membership(group_id=group_id,
828 date_requested=datetime.now()
832 post_save_redirect = reverse(
834 kwargs=dict(group_id=group_id)
836 return HttpResponseRedirect(post_save_redirect)
837 except IntegrityError, e:
839 msg = _('Failed to join group.')
840 messages.error(request, msg)
841 return group_search(request)
844 @signed_terms_required
846 def group_leave(request, group_id):
848 m = Membership.objects.select_related().get(
852 except Membership.DoesNotExist:
853 return HttpResponseBadRequest(_('Invalid membership.'))
854 if request.user in m.group.owner.all():
855 return HttpResponseForbidden(_('Owner can not leave the group.'))
856 return delete_object(
860 template_name='im/astakosgroup_list.html',
861 post_delete_redirect=reverse(
863 kwargs=dict(group_id=group_id)
868 def handle_membership(func):
870 def wrapper(request, group_id, user_id):
872 m = Membership.objects.select_related().get(
876 except Membership.DoesNotExist:
877 return HttpResponseBadRequest(_('Invalid membership.'))
879 if request.user not in m.group.owner.all():
880 return HttpResponseForbidden(_('User is not a group owner.'))
882 return render_response(
883 template='im/astakosgroup_detail.html',
884 context_instance=get_context(request),
891 @signed_terms_required
894 def approve_member(request, membership):
897 realname = membership.person.realname
898 msg = _('%s has been successfully joined the group.' % realname)
899 messages.success(request, msg)
900 except BaseException, e:
902 msg = _('Something went wrong during %s\'s approval.' % realname)
903 messages.error(request, msg)
906 @signed_terms_required
909 def disapprove_member(request, membership):
911 membership.disapprove()
912 realname = membership.person.realname
913 msg = _('%s has been successfully removed from the group.' % realname)
914 messages.success(request, msg)
915 except BaseException, e:
917 msg = _('Something went wrong during %s\'s disapproval.' % realname)
918 messages.error(request, msg)
923 @signed_terms_required
925 def add_members(request, group_id):
926 if request.method != 'POST':
927 return HttpResponseBadRequest(_('Bad method'))
929 group = AstakosGroup.objects.select_related().get(id=group_id)
930 except AstakosGroup.DoesNotExist:
931 return HttpResponseBadRequest(_('Invalid group.'))
932 search_form = AddGroupMembersForm(request.POST)
933 if search_form.is_valid():
934 users = search_form.get_valid_users()
935 map(group.approve_member, users)
936 search_form = AddGroupMembersForm()
937 form = AstakosGroupUpdateForm(instance=group)
938 return object_detail(request,
939 AstakosGroup.objects.all(),
941 extra_context={'quota': group.quota,
943 'search_form' : search_form}
947 @signed_terms_required
949 def resource_list(request):
950 return render_response(
951 template='im/astakosuserquota_list.html',
952 context_instance=get_context(request),
953 quota=request.user.quota
957 def group_create_list(request):
958 return render_response(
959 template='im/astakosgroup_create_list.html',
960 context_instance=get_context(request),
964 @signed_terms_required
966 def billing(request):
968 today = datetime.today()
969 month_last_day= calendar.monthrange(today.year, today.month)[1]
971 start = request.POST.get('datefrom', None)
973 today = datetime.fromtimestamp(int(start))
974 month_last_day= calendar.monthrange(today.year, today.month)[1]
976 start = datetime(today.year, today.month, 1).strftime("%s")
977 end = datetime(today.year, today.month, month_last_day).strftime("%s")
978 r = request_billing.apply(args=('pgerakios@grnet.gr',
984 status, data = r.result
985 data=clear_billing_data(data)
987 messages.error(request, _('Service response status: %d' % status))
989 messages.error(request, r.result)
993 return render_response(
994 template='im/billing.html',
995 context_instance=get_context(request),
997 zerodate=datetime(month=1,year=1970, day=1),
1000 month_last_day=month_last_day)
1002 def clear_billing_data(data):
1004 # remove addcredits entries
1006 return e['serviceName'] != "addcredits"
1011 def servicefilter(service_name):
1012 service = service_name
1014 return e['serviceName'] == service
1018 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1019 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1020 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1021 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1025 @signed_terms_required
1027 def timeline(request):
1028 data = {'entity':request.user.email}
1030 timeline_header = ()
1031 form = TimelineForm(data)
1032 if request.method == 'POST':
1034 form = TimelineForm(data)
1036 data = form.cleaned_data
1037 timeline_header = ('entity', 'resource',
1038 'event name', 'event date',
1039 'incremental cost', 'total cost')
1040 timeline_body = timeline_charge(
1041 data['entity'], data['resource'],
1042 data['start_date'], data['end_date'],
1043 data['details'], data['operation'])
1045 return render_response(template='im/timeline.html',
1046 context_instance=get_context(request),
1048 timeline_header=timeline_header,
1049 timeline_body=timeline_body)