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.
38 engine = inflect.engine()
40 from urllib import quote
41 from functools import wraps
42 from datetime import datetime
44 from django.contrib import messages
45 from django.contrib.auth.decorators import login_required
46 from django.core.urlresolvers import reverse
47 from django.db import transaction
48 from django.db.utils import IntegrityError
49 from django.http import (HttpResponse, HttpResponseBadRequest,
50 HttpResponseForbidden, HttpResponseRedirect,
51 HttpResponseBadRequest, Http404)
52 from django.shortcuts import redirect
53 from django.template import RequestContext, loader as template_loader
54 from django.utils.http import urlencode
55 from django.utils.translation import ugettext as _
56 from django.views.generic.create_update import (delete_object,
57 get_model_and_form_class)
58 from django.views.generic.list_detail import object_list
59 from django.core.xheaders import populate_xheaders
60 from django.core.exceptions import ValidationError, PermissionDenied
62 from django.template.loader import render_to_string
63 from django.views.decorators.http import require_http_methods
64 from astakos.im.activation_backends import get_backend, SimpleBackend
66 from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
67 EmailChange, GroupKind, Membership,
68 RESOURCE_SEPARATOR, AstakosUserAuthProvider)
69 from astakos.im.util import get_context, prepare_response, get_query, restrict_next
70 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
71 FeedbackForm, SignApprovalTermsForm,
73 AstakosGroupCreationForm, AstakosGroupSearchForm,
74 AstakosGroupUpdateForm, AddGroupMembersForm,
75 MembersSortForm, AstakosGroupSortForm,
76 TimelineForm, PickResourceForm,
77 AstakosGroupCreationSummaryForm)
78 from astakos.im.functions import (send_feedback, SendMailError,
79 logout as auth_logout,
80 activate as activate_func,
81 send_activation as send_activation_func,
82 send_group_creation_notification,
83 SendNotificationError)
84 from astakos.im.endpoints.qh import timeline_charge
85 from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
86 LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
87 from astakos.im.tasks import request_billing
88 from astakos.im.api.callpoint import AstakosCallpoint
90 import astakos.im.messages as astakos_messages
91 from astakos.im import settings
92 from astakos.im import auth_providers
94 logger = logging.getLogger(__name__)
96 callpoint = AstakosCallpoint()
98 def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
100 Calls ``django.template.loader.render_to_string`` with an additional ``tab``
101 keyword argument and returns an ``django.http.HttpResponse`` with the
102 specified ``status``.
105 tab = template.partition('_')[0].partition('.html')[0]
106 kwargs.setdefault('tab', tab)
107 html = template_loader.render_to_string(
108 template, kwargs, context_instance=context_instance)
109 response = HttpResponse(html, status=status)
112 def requires_auth_provider(provider_id, **perms):
115 def decorator(func, *args, **kwargs):
117 def wrapper(request, *args, **kwargs):
118 provider = auth_providers.get_provider(provider_id)
120 if not provider or not provider.is_active():
121 raise PermissionDenied
124 for pkey, value in perms.iteritems():
125 attr = 'is_available_for_%s' % pkey.lower()
126 if getattr(provider, attr)() != value:
127 raise PermissionDenied
128 return func(request, *args)
133 def requires_anonymous(func):
135 Decorator checkes whether the request.user is not Anonymous and in that case
136 redirects to `logout`.
139 def wrapper(request, *args):
140 if not request.user.is_anonymous():
141 next = urlencode({'next': request.build_absolute_uri()})
142 logout_uri = reverse(logout) + '?' + next
143 return HttpResponseRedirect(logout_uri)
144 return func(request, *args)
148 def signed_terms_required(func):
150 Decorator checkes whether the request.user is Anonymous and in that case
151 redirects to `logout`.
154 def wrapper(request, *args, **kwargs):
155 if request.user.is_authenticated() and not request.user.signed_terms:
156 params = urlencode({'next': request.build_absolute_uri(),
158 terms_uri = reverse('latest_terms') + '?' + params
159 return HttpResponseRedirect(terms_uri)
160 return func(request, *args, **kwargs)
164 @require_http_methods(["GET", "POST"])
165 @signed_terms_required
166 def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
168 If there is logged on user renders the profile page otherwise renders login page.
172 ``login_template_name``
173 A custom login template to use. This is optional; if not specified,
174 this will default to ``im/login.html``.
176 ``profile_template_name``
177 A custom profile template to use. This is optional; if not specified,
178 this will default to ``im/profile.html``.
181 An dictionary of variables to add to the template context.
185 im/profile.html or im/login.html or ``template_name`` keyword argument.
188 extra_context = extra_context or {}
189 template_name = login_template_name
190 if request.user.is_authenticated():
191 return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
193 return render_response(
195 login_form = LoginForm(request=request),
196 context_instance = get_context(request, extra_context)
200 @require_http_methods(["GET", "POST"])
202 @signed_terms_required
203 @transaction.commit_manually
204 def invite(request, template_name='im/invitations.html', extra_context=None):
206 Allows a user to invite somebody else.
208 In case of GET request renders a form for providing the invitee information.
209 In case of POST checks whether the user has not run out of invitations and then
210 sends an invitation email to singup to the service.
212 The view uses commit_manually decorator in order to ensure the number of the
213 user invitations is going to be updated only if the email has been successfully sent.
215 If the user isn't logged in, redirects to settings.LOGIN_URL.
220 A custom template to use. This is optional; if not specified,
221 this will default to ``im/invitations.html``.
224 An dictionary of variables to add to the template context.
228 im/invitations.html or ``template_name`` keyword argument.
232 The view expectes the following settings are defined:
234 * LOGIN_URL: login uri
236 extra_context = extra_context or {}
239 form = InvitationForm()
241 inviter = request.user
242 if request.method == 'POST':
243 form = InvitationForm(request.POST)
244 if inviter.invitations > 0:
247 email = form.cleaned_data.get('username')
248 realname = form.cleaned_data.get('realname')
249 inviter.invite(email, realname)
250 message = _(astakos_messages.INVITATION_SENT) % locals()
251 messages.success(request, message)
252 except SendMailError, e:
254 messages.error(request, message)
255 transaction.rollback()
256 except BaseException, e:
257 message = _(astakos_messages.GENERIC_ERROR)
258 messages.error(request, message)
260 transaction.rollback()
264 message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
265 messages.error(request, message)
267 sent = [{'email': inv.username,
268 'realname': inv.realname,
269 'is_consumed': inv.is_consumed}
270 for inv in request.user.invitations_sent.all()]
271 kwargs = {'inviter': inviter,
273 context = get_context(request, extra_context, **kwargs)
274 return render_response(template_name,
275 invitation_form=form,
276 context_instance=context)
279 @require_http_methods(["GET", "POST"])
281 @signed_terms_required
282 def edit_profile(request, template_name='im/profile.html', extra_context=None):
284 Allows a user to edit his/her profile.
286 In case of GET request renders a form for displaying the user information.
287 In case of POST updates the user informantion and redirects to ``next``
288 url parameter if exists.
290 If the user isn't logged in, redirects to settings.LOGIN_URL.
295 A custom template to use. This is optional; if not specified,
296 this will default to ``im/profile.html``.
299 An dictionary of variables to add to the template context.
303 im/profile.html or ``template_name`` keyword argument.
307 The view expectes the following settings are defined:
309 * LOGIN_URL: login uri
311 extra_context = extra_context or {}
313 instance=request.user,
314 session_key=request.session.session_key
316 extra_context['next'] = request.GET.get('next')
317 if request.method == 'POST':
320 instance=request.user,
321 session_key=request.session.session_key
325 prev_token = request.user.auth_token
329 session_key=request.session.session_key
331 next = restrict_next(
332 request.POST.get('next'),
336 return redirect(next)
337 msg = _(astakos_messages.PROFILE_UPDATED)
338 messages.success(request, msg)
339 except ValueError, ve:
340 messages.success(request, ve)
341 elif request.method == "GET":
342 request.user.is_verified = True
346 user_providers = request.user.get_active_auth_providers()
348 # providers that user can add
349 user_available_providers = request.user.get_available_auth_providers()
351 return render_response(template_name,
353 user_providers = user_providers,
354 user_available_providers = user_available_providers,
355 context_instance = get_context(request,
359 @transaction.commit_manually
360 @require_http_methods(["GET", "POST"])
361 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
363 Allows a user to create a local account.
365 In case of GET request renders a form for entering the user information.
366 In case of POST handles the signup.
368 The user activation will be delegated to the backend specified by the ``backend`` keyword argument
369 if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
370 if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
371 (see activation_backends);
373 Upon successful user creation, if ``next`` url parameter is present the user is redirected there
374 otherwise renders the same page with a success message.
376 On unsuccessful creation, renders ``template_name`` with an error message.
381 A custom template to render. This is optional;
382 if not specified, this will default to ``im/signup.html``.
385 A custom template to render in case of success. This is optional;
386 if not specified, this will default to ``im/signup_complete.html``.
389 An dictionary of variables to add to the template context.
393 im/signup.html or ``template_name`` keyword argument.
394 im/signup_complete.html or ``on_success`` keyword argument.
396 extra_context = extra_context or {}
397 if request.user.is_authenticated():
398 return HttpResponseRedirect(reverse('edit_profile'))
400 provider = get_query(request).get('provider', 'local')
401 if not auth_providers.get_provider(provider).is_available_for_create():
402 raise PermissionDenied
404 id = get_query(request).get('id')
406 instance = AstakosUser.objects.get(id=id) if id else None
407 except AstakosUser.DoesNotExist:
412 backend = get_backend(request)
413 form = backend.get_signup_form(provider, instance)
415 form = SimpleBackend(request).get_signup_form(provider)
416 messages.error(request, e)
417 if request.method == 'POST':
419 user = form.save(commit=False)
421 result = backend.handle_activation(user)
422 status = messages.SUCCESS
423 message = result.message
425 form.store_user(user, request)
427 if 'additional_email' in form.cleaned_data:
428 additional_email = form.cleaned_data['additional_email']
429 if additional_email != user.email:
430 user.additionalmail_set.create(email=additional_email)
431 msg = 'Additional email: %s saved for user %s.' % (
435 logger._log(LOGGING_LEVEL, msg, [])
436 if user and user.is_active:
437 next = request.POST.get('next', '')
438 response = prepare_response(request, user, next=next)
441 messages.add_message(request, status, message)
443 return render_response(
445 context_instance=get_context(
450 except SendMailError, e:
452 status = messages.ERROR
454 messages.error(request, message)
455 transaction.rollback()
456 except BaseException, e:
458 message = _(astakos_messages.GENERIC_ERROR)
459 messages.error(request, message)
461 transaction.rollback()
462 return render_response(template_name,
465 context_instance=get_context(request, extra_context))
468 @require_http_methods(["GET", "POST"])
470 @signed_terms_required
471 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
473 Allows a user to send feedback.
475 In case of GET request renders a form for providing the feedback information.
476 In case of POST sends an email to support team.
478 If the user isn't logged in, redirects to settings.LOGIN_URL.
483 A custom template to use. This is optional; if not specified,
484 this will default to ``im/feedback.html``.
487 An dictionary of variables to add to the template context.
491 im/signup.html or ``template_name`` keyword argument.
495 * LOGIN_URL: login uri
497 extra_context = extra_context or {}
498 if request.method == 'GET':
499 form = FeedbackForm()
500 if request.method == 'POST':
502 return HttpResponse('Unauthorized', status=401)
504 form = FeedbackForm(request.POST)
506 msg = form.cleaned_data['feedback_msg']
507 data = form.cleaned_data['feedback_data']
509 send_feedback(msg, data, request.user, email_template_name)
510 except SendMailError, e:
511 messages.error(request, message)
513 message = _(astakos_messages.FEEDBACK_SENT)
514 messages.success(request, message)
515 return render_response(template_name,
517 context_instance=get_context(request, extra_context))
520 @require_http_methods(["GET"])
521 @signed_terms_required
522 def logout(request, template='registration/logged_out.html', extra_context=None):
524 Wraps `django.contrib.auth.logout`.
526 extra_context = extra_context or {}
527 response = HttpResponse()
528 if request.user.is_authenticated():
529 email = request.user.email
531 next = restrict_next(
532 request.GET.get('next'),
536 response['Location'] = next
537 response.status_code = 302
539 response['Location'] = LOGOUT_NEXT
540 response.status_code = 301
542 messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
543 context = get_context(request, extra_context)
544 response.write(render_to_string(template, context_instance=context))
548 @require_http_methods(["GET", "POST"])
549 @transaction.commit_manually
550 def activate(request, greeting_email_template_name='im/welcome_email.txt',
551 helpdesk_email_template_name='im/helpdesk_notification.txt'):
553 Activates the user identified by the ``auth`` request parameter, sends a welcome email
554 and renews the user token.
556 The view uses commit_manually decorator in order to ensure the user state will be updated
557 only if the email will be send successfully.
559 token = request.GET.get('auth')
560 next = request.GET.get('next')
562 user = AstakosUser.objects.get(auth_token=token)
563 except AstakosUser.DoesNotExist:
564 return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
567 message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
568 messages.error(request, message)
569 return index(request)
572 activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
573 response = prepare_response(request, user, next, renew=True)
576 except SendMailError, e:
578 messages.add_message(request, messages.ERROR, message)
579 transaction.rollback()
580 return index(request)
581 except BaseException, e:
582 status = messages.ERROR
583 message = _(astakos_messages.GENERIC_ERROR)
584 messages.add_message(request, messages.ERROR, message)
586 transaction.rollback()
587 return index(request)
590 @require_http_methods(["GET", "POST"])
591 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
592 extra_context = extra_context or {}
597 term = ApprovalTerms.objects.order_by('-id')[0]
602 term = ApprovalTerms.objects.get(id=term_id)
603 except ApprovalTerms.DoesNotExist, e:
607 messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
608 return HttpResponseRedirect(reverse('index'))
609 f = open(term.location, 'r')
612 if request.method == 'POST':
613 next = restrict_next(
614 request.POST.get('next'),
618 next = reverse('index')
619 form = SignApprovalTermsForm(request.POST, instance=request.user)
620 if not form.is_valid():
621 return render_response(template_name,
623 approval_terms_form=form,
624 context_instance=get_context(request, extra_context))
626 return HttpResponseRedirect(next)
629 if request.user.is_authenticated() and not request.user.signed_terms:
630 form = SignApprovalTermsForm(instance=request.user)
631 return render_response(template_name,
633 approval_terms_form=form,
634 context_instance=get_context(request, extra_context))
637 @require_http_methods(["GET", "POST"])
639 @signed_terms_required
640 @transaction.commit_manually
641 def change_email(request, activation_key=None,
642 email_template_name='registration/email_change_email.txt',
643 form_template_name='registration/email_change_form.html',
644 confirm_template_name='registration/email_change_done.html',
646 extra_context = extra_context or {}
649 user = EmailChange.objects.change_email(activation_key)
650 if request.user.is_authenticated() and request.user == user:
651 msg = _(astakos_messages.EMAIL_CHANGED)
652 messages.success(request, msg)
654 response = prepare_response(request, user)
657 except ValueError, e:
658 messages.error(request, e)
659 return render_response(confirm_template_name,
660 modified_user=user if 'user' in locals(
662 context_instance=get_context(request,
665 if not request.user.is_authenticated():
666 path = quote(request.get_full_path())
667 url = request.build_absolute_uri(reverse('index'))
668 return HttpResponseRedirect(url + '?next=' + path)
669 form = EmailChangeForm(request.POST or None)
670 if request.method == 'POST' and form.is_valid():
672 ec = form.save(email_template_name, request)
673 except SendMailError, e:
675 messages.error(request, msg)
676 transaction.rollback()
677 except IntegrityError, e:
678 msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
679 messages.error(request, msg)
681 msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
682 messages.success(request, msg)
684 return render_response(
687 context_instance=get_context(request, extra_context)
691 def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
693 if settings.MODERATION_ENABLED:
694 raise PermissionDenied
696 extra_context = extra_context or {}
698 u = AstakosUser.objects.get(id=user_id)
699 except AstakosUser.DoesNotExist:
700 messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
703 send_activation_func(u)
704 msg = _(astakos_messages.ACTIVATION_SENT)
705 messages.success(request, msg)
706 except SendMailError, e:
707 messages.error(request, e)
708 return render_response(
710 login_form = LoginForm(request=request),
711 context_instance = get_context(
717 class ResourcePresentation():
719 def __init__(self, data):
722 def update_from_result(self, result):
723 if result.is_success:
724 for r in result.data:
725 rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
726 if not rname in self.data['resources']:
727 self.data['resources'][rname] = {}
729 self.data['resources'][rname].update(r)
730 self.data['resources'][rname]['id'] = rname
731 group = r.get('group')
732 if not group in self.data['groups']:
733 self.data['groups'][group] = {}
735 self.data['groups'][r.get('group')].update({'name': r.get('group')})
737 def test(self, quota_dict):
738 for k, v in quota_dict.iteritems():
741 if not rname in self.data['resources']:
742 self.data['resources'][rname] = {}
745 self.data['resources'][rname]['value'] = value
748 def update_from_result_report(self, result):
749 if result.is_success:
750 for r in result.data:
751 rname = r.get('name')
752 if not rname in self.data['resources']:
753 self.data['resources'][rname] = {}
755 self.data['resources'][rname].update(r)
756 self.data['resources'][rname]['id'] = rname
757 group = r.get('group')
758 if not group in self.data['groups']:
759 self.data['groups'][group] = {}
761 self.data['groups'][r.get('group')].update({'name': r.get('group')})
763 def get_group_resources(self, group):
764 return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
766 def get_groups_resources(self):
767 for g in self.data['groups']:
768 yield g, self.get_group_resources(g)
770 def get_quota(self, group_quotas):
771 for r, v in group_quotas.iteritems():
773 quota = self.data['resources'].get(rname)
778 def get_policies(self, policies_data):
779 for policy in policies_data:
780 rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
781 policy.update(self.data['resources'].get(rname))
785 return self.data.__repr__()
787 def __iter__(self, *args, **kwargs):
788 return self.data.__iter__(*args, **kwargs)
790 def __getitem__(self, *args, **kwargs):
791 return self.data.__getitem__(*args, **kwargs)
793 def get(self, *args, **kwargs):
794 return self.data.get(*args, **kwargs)
798 @require_http_methods(["GET", "POST"])
799 @signed_terms_required
801 def group_add(request, kind_name='default'):
803 result = callpoint.list_resources()
804 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
805 resource_catalog.update_from_result(result)
807 if not result.is_success:
810 'Unable to retrieve system resources: %s' % result.reason
814 kind = GroupKind.objects.get(name=kind_name)
816 return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
820 post_save_redirect = '/im/group/%(id)s/'
821 context_processors = None
822 model, form_class = get_model_and_form_class(
824 form_class=AstakosGroupCreationForm
827 if request.method == 'POST':
828 form = form_class(request.POST, request.FILES)
830 return render_response(
831 template='im/astakosgroup_form_summary.html',
832 context_instance=get_context(request),
833 form = AstakosGroupCreationSummaryForm(form.cleaned_data),
834 policies = resource_catalog.get_policies(form.policies()),
835 resource_catalog= resource_catalog,
843 for group, resources in resource_catalog.get_groups_resources():
844 data['is_selected_%s' % group] = False
845 for resource in resources:
846 data['%s_uplimit' % resource] = ''
848 form = form_class(data)
850 # Create the template, context, response
851 template_name = "%s/%s_form.html" % (
852 model._meta.app_label,
853 model._meta.object_name.lower()
855 t = template_loader.get_template(template_name)
856 c = RequestContext(request, {
859 'resource_catalog':resource_catalog,
860 }, context_processors)
861 return HttpResponse(t.render(c))
864 #@require_http_methods(["POST"])
865 @require_http_methods(["GET", "POST"])
866 @signed_terms_required
868 def group_add_complete(request):
870 form = AstakosGroupCreationSummaryForm(request.POST)
872 d = form.cleaned_data
873 d['owners'] = [request.user]
874 result = callpoint.create_groups((d,)).next()
875 if result.is_success:
876 new_object = result.data[0]
877 msg = _(astakos_messages.OBJECT_CREATED) %\
878 {"verbose_name": model._meta.verbose_name}
879 messages.success(request, msg, fail_silently=True)
883 send_group_creation_notification(
884 template_name='im/group_creation_notification.txt',
887 'owner': request.user,
888 'policies': d.get('policies', [])
891 except SendNotificationError, e:
892 messages.error(request, e, fail_silently=True)
893 post_save_redirect = '/im/group/%(id)s/'
894 return HttpResponseRedirect(post_save_redirect % new_object)
896 d = {"verbose_name": model._meta.verbose_name,
897 "reason":result.reason}
898 msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
899 messages.error(request, msg, fail_silently=True)
900 return render_response(
901 template='im/astakosgroup_form_summary.html',
902 context_instance=get_context(request),
906 #@require_http_methods(["GET"])
907 @require_http_methods(["GET", "POST"])
908 @signed_terms_required
910 def group_list(request):
911 none = request.user.astakos_groups.none()
913 SELECT auth_group.id,
914 auth_group.name AS groupname,
915 im_groupkind.name AS kindname,
917 owner.email AS groupowner,
918 (SELECT COUNT(*) FROM im_membership
919 WHERE group_id = im_astakosgroup.group_ptr_id
920 AND date_joined IS NOT NULL) AS approved_members_num,
922 SELECT date_joined FROM im_membership
923 WHERE group_id = im_astakosgroup.group_ptr_id
924 AND person_id = %(userid)s) IS NULL
925 THEN 0 ELSE 1 END) AS membership_status
927 INNER JOIN im_membership ON (
928 im_astakosgroup.group_ptr_id = im_membership.group_id)
929 INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
930 INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
931 LEFT JOIN im_astakosuser_owner ON (
932 im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
933 LEFT JOIN auth_user as owner ON (
934 im_astakosuser_owner.astakosuser_id = owner.id)
935 WHERE im_membership.person_id = %(userid)s
937 params = {'userid':request.user.id}
940 sorting = 'groupname'
941 sort_form = AstakosGroupSortForm(request.GET)
942 if sort_form.is_valid():
943 sorting = sort_form.cleaned_data.get('sorting')
944 query = query+" ORDER BY %s ASC" %sorting
946 q = AstakosGroup.objects.raw(query, params=params)
948 # Create the template, context, response
949 template_name = "%s/%s_list.html" % (
950 q.model._meta.app_label,
951 q.model._meta.object_name.lower()
953 extra_context = dict(
957 page=request.GET.get('page', 1)
959 return render_response(template_name,
960 context_instance=get_context(request, extra_context)
964 @require_http_methods(["GET", "POST"])
965 @signed_terms_required
967 def group_detail(request, group_id):
968 q = AstakosGroup.objects.select_related().filter(pk=group_id)
970 'is_member': """SELECT CASE WHEN EXISTS(
971 SELECT id FROM im_membership
972 WHERE group_id = im_astakosgroup.group_ptr_id
974 THEN 1 ELSE 0 END""" % request.user.id,
975 'is_owner': """SELECT CASE WHEN EXISTS(
976 SELECT id FROM im_astakosuser_owner
977 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
978 AND astakosuser_id = %s)
979 THEN 1 ELSE 0 END""" % request.user.id,
980 'kindname': """SELECT name FROM im_groupkind
981 WHERE id = im_astakosgroup.kind_id"""})
984 context_processors = None
988 except AstakosGroup.DoesNotExist:
989 raise Http404("No %s found matching the query" % (
990 model._meta.verbose_name))
992 update_form = AstakosGroupUpdateForm(instance=obj)
993 addmembers_form = AddGroupMembersForm()
994 if request.method == 'POST':
997 for k, v in request.POST.iteritems():
998 if k in update_form.fields:
1000 if k in addmembers_form.fields:
1001 addmembers_data[k] = v
1002 update_data = update_data or None
1003 addmembers_data = addmembers_data or None
1004 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
1005 addmembers_form = AddGroupMembersForm(addmembers_data)
1006 if update_form.is_valid():
1008 if addmembers_form.is_valid():
1010 map(obj.approve_member, addmembers_form.valid_users)
1011 except AssertionError:
1012 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1013 messages.error(request, msg)
1014 addmembers_form = AddGroupMembersForm()
1016 template_name = "%s/%s_detail.html" % (
1017 model._meta.app_label, model._meta.object_name.lower())
1018 t = template_loader.get_template(template_name)
1019 c = RequestContext(request, {
1021 }, context_processors)
1024 sorting = request.GET.get('sorting')
1026 form = MembersSortForm({'sort_by': sorting})
1028 sorting = form.cleaned_data.get('sort_by')
1031 form = MembersSortForm({'sort_by': 'person_first_name'})
1033 result = callpoint.list_resources()
1034 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1035 resource_catalog.update_from_result(result)
1038 if not result.is_success:
1041 'Unable to retrieve system resources: %s' % result.reason
1044 extra_context = {'update_form': update_form,
1045 'addmembers_form': addmembers_form,
1046 'page': request.GET.get('page', 1),
1048 'resource_catalog':resource_catalog,
1049 'quota':resource_catalog.get_quota(obj.quota)}
1050 for key, value in extra_context.items():
1055 response = HttpResponse(t.render(c), mimetype=mimetype)
1057 request, response, model, getattr(obj, obj._meta.pk.name))
1061 @require_http_methods(["GET", "POST"])
1062 @signed_terms_required
1064 def group_search(request, extra_context=None, **kwargs):
1065 q = request.GET.get('q')
1066 if request.method == 'GET':
1067 form = AstakosGroupSearchForm({'q': q} if q else None)
1069 form = AstakosGroupSearchForm(get_query(request))
1071 q = form.cleaned_data['q'].strip()
1073 sorting = 'groupname'
1075 queryset = AstakosGroup.objects.select_related()
1076 queryset = queryset.filter(name__contains=q)
1077 queryset = queryset.filter(approval_date__isnull=False)
1078 queryset = queryset.extra(select={
1079 'groupname': "auth_group.name",
1080 'kindname': "im_groupkind.name",
1081 'approved_members_num': """
1082 SELECT COUNT(*) FROM im_membership
1083 WHERE group_id = im_astakosgroup.group_ptr_id
1084 AND date_joined IS NOT NULL""",
1085 'membership_approval_date': """
1086 SELECT date_joined FROM im_membership
1087 WHERE group_id = im_astakosgroup.group_ptr_id
1088 AND person_id = %s""" % request.user.id,
1090 SELECT CASE WHEN EXISTS(
1091 SELECT date_joined FROM im_membership
1092 WHERE group_id = im_astakosgroup.group_ptr_id
1094 THEN 1 ELSE 0 END""" % request.user.id,
1096 SELECT CASE WHEN EXISTS(
1097 SELECT id FROM im_astakosuser_owner
1098 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1099 AND astakosuser_id = %s)
1100 THEN 1 ELSE 0 END""" % request.user.id,
1101 'is_owner': """SELECT CASE WHEN EXISTS(
1102 SELECT id FROM im_astakosuser_owner
1103 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1104 AND astakosuser_id = %s)
1105 THEN 1 ELSE 0 END""" % request.user.id,
1109 sort_form = AstakosGroupSortForm(request.GET)
1110 if sort_form.is_valid():
1111 sorting = sort_form.cleaned_data.get('sorting')
1112 queryset = queryset.order_by(sorting)
1115 queryset = AstakosGroup.objects.none()
1119 paginate_by=PAGINATE_BY_ALL,
1120 page=request.GET.get('page') or 1,
1121 template_name='im/astakosgroup_list.html',
1122 extra_context=dict(form=form,
1128 @require_http_methods(["GET", "POST"])
1129 @signed_terms_required
1131 def group_all(request, extra_context=None, **kwargs):
1132 q = AstakosGroup.objects.select_related()
1133 q = q.filter(approval_date__isnull=False)
1134 q = q.extra(select={
1135 'groupname': "auth_group.name",
1136 'kindname': "im_groupkind.name",
1137 'approved_members_num': """
1138 SELECT COUNT(*) FROM im_membership
1139 WHERE group_id = im_astakosgroup.group_ptr_id
1140 AND date_joined IS NOT NULL""",
1141 'membership_approval_date': """
1142 SELECT date_joined FROM im_membership
1143 WHERE group_id = im_astakosgroup.group_ptr_id
1144 AND person_id = %s""" % request.user.id,
1146 SELECT CASE WHEN EXISTS(
1147 SELECT date_joined FROM im_membership
1148 WHERE group_id = im_astakosgroup.group_ptr_id
1150 THEN 1 ELSE 0 END""" % request.user.id,
1151 'is_owner': """SELECT CASE WHEN EXISTS(
1152 SELECT id FROM im_astakosuser_owner
1153 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1154 AND astakosuser_id = %s)
1155 THEN 1 ELSE 0 END""" % request.user.id, })
1158 sorting = 'groupname'
1159 print '>>>', sorting, request.GET
1160 sort_form = AstakosGroupSortForm(request.GET)
1161 if sort_form.is_valid():
1162 sorting = sort_form.cleaned_data.get('sorting')
1163 print '<<<', sorting
1164 q = q.order_by(sorting)
1169 paginate_by=PAGINATE_BY_ALL,
1170 page=request.GET.get('page') or 1,
1171 template_name='im/astakosgroup_list.html',
1172 extra_context=dict(form=AstakosGroupSearchForm(),
1177 #@require_http_methods(["POST"])
1178 @require_http_methods(["POST", "GET"])
1179 @signed_terms_required
1181 def group_join(request, group_id):
1182 m = Membership(group_id=group_id,
1183 person=request.user,
1184 date_requested=datetime.now())
1187 post_save_redirect = reverse(
1189 kwargs=dict(group_id=group_id))
1190 return HttpResponseRedirect(post_save_redirect)
1191 except IntegrityError, e:
1193 msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1194 messages.error(request, msg)
1195 return group_search(request)
1198 @require_http_methods(["POST"])
1199 @signed_terms_required
1201 def group_leave(request, group_id):
1203 m = Membership.objects.select_related().get(
1205 person=request.user)
1206 except Membership.DoesNotExist:
1207 return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1208 if request.user in m.group.owner.all():
1209 return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1210 return delete_object(
1214 template_name='im/astakosgroup_list.html',
1215 post_delete_redirect=reverse(
1217 kwargs=dict(group_id=group_id)))
1220 def handle_membership(func):
1222 def wrapper(request, group_id, user_id):
1224 m = Membership.objects.select_related().get(
1227 except Membership.DoesNotExist:
1228 return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1230 if request.user not in m.group.owner.all():
1231 return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1233 return group_detail(request, group_id)
1237 #@require_http_methods(["POST"])
1238 @require_http_methods(["POST", "GET"])
1239 @signed_terms_required
1242 def approve_member(request, membership):
1244 membership.approve()
1245 realname = membership.person.realname
1246 msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1247 messages.success(request, msg)
1248 except AssertionError:
1249 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1250 messages.error(request, msg)
1251 except BaseException, e:
1253 realname = membership.person.realname
1254 msg = _(astakos_messages.GENERIC_ERROR)
1255 messages.error(request, msg)
1258 @signed_terms_required
1261 def disapprove_member(request, membership):
1263 membership.disapprove()
1264 realname = membership.person.realname
1265 msg = astakos_messages.MEMBER_REMOVED % realname
1266 messages.success(request, msg)
1267 except BaseException, e:
1269 msg = _(astakos_messages.GENERIC_ERROR)
1270 messages.error(request, msg)
1273 #@require_http_methods(["GET"])
1274 @require_http_methods(["POST", "GET"])
1275 @signed_terms_required
1277 def resource_list(request):
1278 def with_class(entry):
1279 entry['load_class'] = 'red'
1280 max_value = float(entry['maxValue'])
1281 curr_value = float(entry['currValue'])
1283 entry['ratio'] = (curr_value / max_value) * 100
1286 if entry['ratio'] < 66:
1287 entry['load_class'] = 'yellow'
1288 if entry['ratio'] < 33:
1289 entry['load_class'] = 'green'
1292 def pluralize(entry):
1293 entry['plural'] = engine.plural(entry.get('name'))
1296 result = callpoint.get_user_status(request.user.id)
1297 if result.is_success:
1298 backenddata = map(with_class, result.data)
1299 data = map(pluralize, result.data)
1302 messages.error(request, result.reason)
1303 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1304 resource_catalog.update_from_result_report(result)
1308 return render_response('im/resource_list.html',
1310 context_instance=get_context(request),
1311 resource_catalog=resource_catalog,
1315 def group_create_list(request):
1316 form = PickResourceForm()
1317 return render_response(
1318 template='im/astakosgroup_create_list.html',
1319 context_instance=get_context(request),)
1322 #@require_http_methods(["GET"])
1323 @require_http_methods(["POST", "GET"])
1324 @signed_terms_required
1326 def billing(request):
1328 today = datetime.today()
1329 month_last_day = calendar.monthrange(today.year, today.month)[1]
1330 start = request.POST.get('datefrom', None)
1332 today = datetime.fromtimestamp(int(start))
1333 month_last_day = calendar.monthrange(today.year, today.month)[1]
1335 start = datetime(today.year, today.month, 1).strftime("%s")
1336 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1337 r = request_billing.apply(args=('pgerakios@grnet.gr',
1343 status, data = r.result
1344 data = _clear_billing_data(data)
1346 messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1348 messages.error(request, r.result)
1350 return render_response(
1351 template='im/billing.html',
1352 context_instance=get_context(request),
1354 zerodate=datetime(month=1, year=1970, day=1),
1357 month_last_day=month_last_day)
1360 def _clear_billing_data(data):
1362 # remove addcredits entries
1364 return e['serviceName'] != "addcredits"
1367 def servicefilter(service_name):
1368 service = service_name
1371 return e['serviceName'] == service
1374 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1375 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1376 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1377 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1382 #@require_http_methods(["GET"])
1383 @require_http_methods(["POST", "GET"])
1384 @signed_terms_required
1386 def timeline(request):
1387 # data = {'entity':request.user.email}
1389 timeline_header = ()
1390 # form = TimelineForm(data)
1391 form = TimelineForm()
1392 if request.method == 'POST':
1394 form = TimelineForm(data)
1396 data = form.cleaned_data
1397 timeline_header = ('entity', 'resource',
1398 'event name', 'event date',
1399 'incremental cost', 'total cost')
1400 timeline_body = timeline_charge(
1401 data['entity'], data['resource'],
1402 data['start_date'], data['end_date'],
1403 data['details'], data['operation'])
1405 return render_response(template='im/timeline.html',
1406 context_instance=get_context(request),
1408 timeline_header=timeline_header,
1409 timeline_body=timeline_body)
1412 # TODO: action only on POST and user should confirm the removal
1413 @require_http_methods(["GET", "POST"])
1415 @signed_terms_required
1416 def remove_auth_provider(request, pk):
1418 provider = request.user.auth_providers.get(pk=pk)
1419 except AstakosUserAuthProvider.DoesNotExist:
1422 if provider.can_remove():
1424 return HttpResponseRedirect(reverse('edit_profile'))
1426 raise PermissionDenied