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
61 from django.template.loader import render_to_string
62 from django.views.decorators.http import require_http_methods
63 from django.db.models import Q
65 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:
410 third_party_token = request.REQUEST.get('third_party_token', None)
414 backend = get_backend(request)
415 form = backend.get_signup_form(provider, instance)
417 form = SimpleBackend(request).get_signup_form(provider)
418 messages.error(request, e)
419 if request.method == 'POST':
421 user = form.save(commit=False)
423 result = backend.handle_activation(user)
424 status = messages.SUCCESS
425 message = result.message
427 form.store_user(user, request)
429 if 'additional_email' in form.cleaned_data:
430 additional_email = form.cleaned_data['additional_email']
431 if additional_email != user.email:
432 user.additionalmail_set.create(email=additional_email)
433 msg = 'Additional email: %s saved for user %s.' % (
437 logger._log(LOGGING_LEVEL, msg, [])
438 if user and user.is_active:
439 next = request.POST.get('next', '')
440 response = prepare_response(request, user, next=next)
443 messages.add_message(request, status, message)
445 return render_response(
447 context_instance=get_context(
452 except SendMailError, e:
454 status = messages.ERROR
456 messages.error(request, message)
457 transaction.rollback()
458 except BaseException, e:
460 message = _(astakos_messages.GENERIC_ERROR)
461 messages.error(request, message)
463 transaction.rollback()
464 return render_response(template_name,
466 third_party_token=third_party_token,
468 context_instance=get_context(request, extra_context))
471 @require_http_methods(["GET", "POST"])
473 @signed_terms_required
474 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
476 Allows a user to send feedback.
478 In case of GET request renders a form for providing the feedback information.
479 In case of POST sends an email to support team.
481 If the user isn't logged in, redirects to settings.LOGIN_URL.
486 A custom template to use. This is optional; if not specified,
487 this will default to ``im/feedback.html``.
490 An dictionary of variables to add to the template context.
494 im/signup.html or ``template_name`` keyword argument.
498 * LOGIN_URL: login uri
500 extra_context = extra_context or {}
501 if request.method == 'GET':
502 form = FeedbackForm()
503 if request.method == 'POST':
505 return HttpResponse('Unauthorized', status=401)
507 form = FeedbackForm(request.POST)
509 msg = form.cleaned_data['feedback_msg']
510 data = form.cleaned_data['feedback_data']
512 send_feedback(msg, data, request.user, email_template_name)
513 except SendMailError, e:
514 messages.error(request, message)
516 message = _(astakos_messages.FEEDBACK_SENT)
517 messages.success(request, message)
518 return render_response(template_name,
520 context_instance=get_context(request, extra_context))
523 @require_http_methods(["GET"])
524 @signed_terms_required
525 def logout(request, template='registration/logged_out.html', extra_context=None):
527 Wraps `django.contrib.auth.logout`.
529 extra_context = extra_context or {}
530 response = HttpResponse()
531 if request.user.is_authenticated():
532 email = request.user.email
534 next = restrict_next(
535 request.GET.get('next'),
539 response['Location'] = next
540 response.status_code = 302
542 response['Location'] = LOGOUT_NEXT
543 response.status_code = 301
545 messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
546 context = get_context(request, extra_context)
547 response.write(render_to_string(template, context_instance=context))
551 @require_http_methods(["GET", "POST"])
552 @transaction.commit_manually
553 def activate(request, greeting_email_template_name='im/welcome_email.txt',
554 helpdesk_email_template_name='im/helpdesk_notification.txt'):
556 Activates the user identified by the ``auth`` request parameter, sends a welcome email
557 and renews the user token.
559 The view uses commit_manually decorator in order to ensure the user state will be updated
560 only if the email will be send successfully.
562 token = request.GET.get('auth')
563 next = request.GET.get('next')
565 user = AstakosUser.objects.get(auth_token=token)
566 except AstakosUser.DoesNotExist:
567 return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
570 message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
571 messages.error(request, message)
572 return index(request)
575 activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
576 response = prepare_response(request, user, next, renew=True)
579 except SendMailError, e:
581 messages.add_message(request, messages.ERROR, message)
582 transaction.rollback()
583 return index(request)
584 except BaseException, e:
585 status = messages.ERROR
586 message = _(astakos_messages.GENERIC_ERROR)
587 messages.add_message(request, messages.ERROR, message)
589 transaction.rollback()
590 return index(request)
593 @require_http_methods(["GET", "POST"])
594 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
595 extra_context = extra_context or {}
600 term = ApprovalTerms.objects.order_by('-id')[0]
605 term = ApprovalTerms.objects.get(id=term_id)
606 except ApprovalTerms.DoesNotExist, e:
610 messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
611 return HttpResponseRedirect(reverse('index'))
612 f = open(term.location, 'r')
615 if request.method == 'POST':
616 next = restrict_next(
617 request.POST.get('next'),
621 next = reverse('index')
622 form = SignApprovalTermsForm(request.POST, instance=request.user)
623 if not form.is_valid():
624 return render_response(template_name,
626 approval_terms_form=form,
627 context_instance=get_context(request, extra_context))
629 return HttpResponseRedirect(next)
632 if request.user.is_authenticated() and not request.user.signed_terms:
633 form = SignApprovalTermsForm(instance=request.user)
634 return render_response(template_name,
636 approval_terms_form=form,
637 context_instance=get_context(request, extra_context))
640 @require_http_methods(["GET", "POST"])
642 @signed_terms_required
643 @transaction.commit_manually
644 def change_email(request, activation_key=None,
645 email_template_name='registration/email_change_email.txt',
646 form_template_name='registration/email_change_form.html',
647 confirm_template_name='registration/email_change_done.html',
649 extra_context = extra_context or {}
652 user = EmailChange.objects.change_email(activation_key)
653 if request.user.is_authenticated() and request.user == user:
654 msg = _(astakos_messages.EMAIL_CHANGED)
655 messages.success(request, msg)
657 response = prepare_response(request, user)
660 except ValueError, e:
661 messages.error(request, e)
662 return render_response(confirm_template_name,
663 modified_user=user if 'user' in locals(
665 context_instance=get_context(request,
668 if not request.user.is_authenticated():
669 path = quote(request.get_full_path())
670 url = request.build_absolute_uri(reverse('index'))
671 return HttpResponseRedirect(url + '?next=' + path)
672 form = EmailChangeForm(request.POST or None)
673 if request.method == 'POST' and form.is_valid():
675 ec = form.save(email_template_name, request)
676 except SendMailError, e:
678 messages.error(request, msg)
679 transaction.rollback()
680 except IntegrityError, e:
681 msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
682 messages.error(request, msg)
684 msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
685 messages.success(request, msg)
687 return render_response(
690 context_instance=get_context(request, extra_context)
694 def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
696 if settings.MODERATION_ENABLED:
697 raise PermissionDenied
699 extra_context = extra_context or {}
701 u = AstakosUser.objects.get(id=user_id)
702 except AstakosUser.DoesNotExist:
703 messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
706 send_activation_func(u)
707 msg = _(astakos_messages.ACTIVATION_SENT)
708 messages.success(request, msg)
709 except SendMailError, e:
710 messages.error(request, e)
711 return render_response(
713 login_form = LoginForm(request=request),
714 context_instance = get_context(
720 class ResourcePresentation():
722 def __init__(self, data):
725 def update_from_result(self, result):
726 if result.is_success:
727 for r in result.data:
728 rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
729 if not rname in self.data['resources']:
730 self.data['resources'][rname] = {}
732 self.data['resources'][rname].update(r)
733 self.data['resources'][rname]['id'] = rname
734 group = r.get('group')
735 if not group in self.data['groups']:
736 self.data['groups'][group] = {}
738 self.data['groups'][r.get('group')].update({'name': r.get('group')})
740 def test(self, quota_dict):
741 for k, v in quota_dict.iteritems():
744 if not rname in self.data['resources']:
745 self.data['resources'][rname] = {}
748 self.data['resources'][rname]['value'] = value
751 def update_from_result_report(self, result):
752 if result.is_success:
753 for r in result.data:
754 rname = r.get('name')
755 if not rname in self.data['resources']:
756 self.data['resources'][rname] = {}
758 self.data['resources'][rname].update(r)
759 self.data['resources'][rname]['id'] = rname
760 group = r.get('group')
761 if not group in self.data['groups']:
762 self.data['groups'][group] = {}
764 self.data['groups'][r.get('group')].update({'name': r.get('group')})
766 def get_group_resources(self, group):
767 return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
769 def get_groups_resources(self):
770 for g in self.data['groups']:
771 yield g, self.get_group_resources(g)
773 def get_quota(self, group_quotas):
774 for r, v in group_quotas.iteritems():
776 quota = self.data['resources'].get(rname)
781 def get_policies(self, policies_data):
782 for policy in policies_data:
783 rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
784 policy.update(self.data['resources'].get(rname))
788 return self.data.__repr__()
790 def __iter__(self, *args, **kwargs):
791 return self.data.__iter__(*args, **kwargs)
793 def __getitem__(self, *args, **kwargs):
794 return self.data.__getitem__(*args, **kwargs)
796 def get(self, *args, **kwargs):
797 return self.data.get(*args, **kwargs)
801 @require_http_methods(["GET", "POST"])
802 @signed_terms_required
804 def group_add(request, kind_name='default'):
806 result = callpoint.list_resources()
807 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
808 resource_catalog.update_from_result(result)
810 if not result.is_success:
813 'Unable to retrieve system resources: %s' % result.reason
817 kind = GroupKind.objects.get(name=kind_name)
819 return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
823 post_save_redirect = '/im/group/%(id)s/'
824 context_processors = None
825 model, form_class = get_model_and_form_class(
827 form_class=AstakosGroupCreationForm
830 if request.method == 'POST':
831 form = form_class(request.POST, request.FILES)
833 policies = form.policies()
834 return render_response(
835 template='im/astakosgroup_form_summary.html',
836 context_instance=get_context(request),
837 form=AstakosGroupCreationSummaryForm(form.cleaned_data),
838 policies=resource_catalog.get_policies(policies)
845 for group, resources in resource_catalog.get_groups_resources():
846 data['is_selected_%s' % group] = False
847 for resource in resources:
848 data['%s_uplimit' % resource] = ''
850 form = form_class(data)
852 # Create the template, context, response
853 template_name = "%s/%s_form.html" % (
854 model._meta.app_label,
855 model._meta.object_name.lower()
857 t = template_loader.get_template(template_name)
858 c = RequestContext(request, {
861 'resource_catalog':resource_catalog,
862 }, context_processors)
863 return HttpResponse(t.render(c))
866 #@require_http_methods(["POST"])
867 @require_http_methods(["GET", "POST"])
868 @signed_terms_required
870 def group_add_complete(request):
872 form = AstakosGroupCreationSummaryForm(request.POST)
874 d = form.cleaned_data
875 d['owners'] = [request.user]
876 result = callpoint.create_groups((d,)).next()
877 if result.is_success:
878 new_object = result.data[0]
879 msg = _(astakos_messages.OBJECT_CREATED) %\
880 {"verbose_name": model._meta.verbose_name}
881 messages.success(request, msg, fail_silently=True)
885 send_group_creation_notification(
886 template_name='im/group_creation_notification.txt',
889 'owner': request.user,
890 'policies': d.get('policies', [])
893 except SendNotificationError, e:
894 messages.error(request, e, fail_silently=True)
895 post_save_redirect = '/im/group/%(id)s/'
896 return HttpResponseRedirect(post_save_redirect % new_object)
898 d = {"verbose_name": model._meta.verbose_name,
899 "reason":result.reason}
900 msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
901 messages.error(request, msg, fail_silently=True)
902 return render_response(
903 template='im/astakosgroup_form_summary.html',
904 context_instance=get_context(request),
906 policies=form.cleaned_data.get('policies')
910 #@require_http_methods(["GET"])
911 @require_http_methods(["GET", "POST"])
912 @signed_terms_required
914 def group_list(request):
915 none = request.user.astakos_groups.none()
917 SELECT auth_group.id,
918 auth_group.name AS groupname,
919 im_groupkind.name AS kindname,
921 owner.email AS groupowner,
922 (SELECT COUNT(*) FROM im_membership
923 WHERE group_id = im_astakosgroup.group_ptr_id
924 AND date_joined IS NOT NULL) AS approved_members_num,
926 SELECT date_joined FROM im_membership
927 WHERE group_id = im_astakosgroup.group_ptr_id
928 AND person_id = %(userid)s) IS NULL
929 THEN 0 ELSE 1 END) AS membership_status
931 INNER JOIN im_membership ON (
932 im_astakosgroup.group_ptr_id = im_membership.group_id)
933 INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
934 INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
935 LEFT JOIN im_astakosuser_owner ON (
936 im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
937 LEFT JOIN auth_user as owner ON (
938 im_astakosuser_owner.astakosuser_id = owner.id)
939 WHERE im_membership.person_id = %(userid)s
940 AND im_groupkind.name != 'default'
942 params = {'userid':request.user.id}
945 sorting = 'groupname'
946 sort_form = AstakosGroupSortForm(request.GET)
947 if sort_form.is_valid():
948 sorting = sort_form.cleaned_data.get('sorting')
949 query = query+" ORDER BY %s ASC" %sorting
951 q = AstakosGroup.objects.raw(query, params=params)
953 # Create the template, context, response
954 template_name = "%s/%s_list.html" % (
955 q.model._meta.app_label,
956 q.model._meta.object_name.lower()
958 extra_context = dict(
962 page=request.GET.get('page', 1)
964 return render_response(template_name,
965 context_instance=get_context(request, extra_context)
969 @require_http_methods(["GET", "POST"])
970 @signed_terms_required
972 def group_detail(request, group_id):
973 q = AstakosGroup.objects.select_related().filter(pk=group_id)
975 'is_member': """SELECT CASE WHEN EXISTS(
976 SELECT id FROM im_membership
977 WHERE group_id = im_astakosgroup.group_ptr_id
979 THEN 1 ELSE 0 END""" % request.user.id,
980 'is_owner': """SELECT CASE WHEN EXISTS(
981 SELECT id FROM im_astakosuser_owner
982 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
983 AND astakosuser_id = %s)
984 THEN 1 ELSE 0 END""" % request.user.id,
985 'kindname': """SELECT name FROM im_groupkind
986 WHERE id = im_astakosgroup.kind_id"""})
989 context_processors = None
993 except AstakosGroup.DoesNotExist:
994 raise Http404("No %s found matching the query" % (
995 model._meta.verbose_name))
997 update_form = AstakosGroupUpdateForm(instance=obj)
998 addmembers_form = AddGroupMembersForm()
999 if request.method == 'POST':
1001 addmembers_data = {}
1002 for k, v in request.POST.iteritems():
1003 if k in update_form.fields:
1005 if k in addmembers_form.fields:
1006 addmembers_data[k] = v
1007 update_data = update_data or None
1008 addmembers_data = addmembers_data or None
1009 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
1010 addmembers_form = AddGroupMembersForm(addmembers_data)
1011 if update_form.is_valid():
1013 if addmembers_form.is_valid():
1015 map(obj.approve_member, addmembers_form.valid_users)
1016 except AssertionError:
1017 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1018 messages.error(request, msg)
1019 addmembers_form = AddGroupMembersForm()
1021 template_name = "%s/%s_detail.html" % (
1022 model._meta.app_label, model._meta.object_name.lower())
1023 t = template_loader.get_template(template_name)
1024 c = RequestContext(request, {
1026 }, context_processors)
1029 sorting = 'person__email'
1030 form = MembersSortForm(request.GET)
1032 sorting = form.cleaned_data.get('sorting')
1034 result = callpoint.list_resources()
1035 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1036 resource_catalog.update_from_result(result)
1039 if not result.is_success:
1042 'Unable to retrieve system resources: %s' % result.reason
1045 extra_context = {'update_form': update_form,
1046 'addmembers_form': addmembers_form,
1047 'page': request.GET.get('page', 1),
1049 'resource_catalog':resource_catalog,
1050 'quota':resource_catalog.get_quota(obj.quota)}
1051 for key, value in extra_context.items():
1056 response = HttpResponse(t.render(c), mimetype=mimetype)
1058 request, response, model, getattr(obj, obj._meta.pk.name))
1062 @require_http_methods(["GET", "POST"])
1063 @signed_terms_required
1065 def group_search(request, extra_context=None, **kwargs):
1066 q = request.GET.get('q')
1067 if request.method == 'GET':
1068 form = AstakosGroupSearchForm({'q': q} if q else None)
1070 form = AstakosGroupSearchForm(get_query(request))
1072 q = form.cleaned_data['q'].strip()
1074 sorting = 'groupname'
1076 queryset = AstakosGroup.objects.select_related()
1077 queryset = queryset.filter(~Q(kind__name='default'))
1078 queryset = queryset.filter(name__contains=q)
1079 queryset = queryset.filter(approval_date__isnull=False)
1080 queryset = queryset.extra(select={
1081 'groupname': "auth_group.name",
1082 'kindname': "im_groupkind.name",
1083 'approved_members_num': """
1084 SELECT COUNT(*) FROM im_membership
1085 WHERE group_id = im_astakosgroup.group_ptr_id
1086 AND date_joined IS NOT NULL""",
1087 'membership_approval_date': """
1088 SELECT date_joined FROM im_membership
1089 WHERE group_id = im_astakosgroup.group_ptr_id
1090 AND person_id = %s""" % request.user.id,
1092 SELECT CASE WHEN EXISTS(
1093 SELECT date_joined FROM im_membership
1094 WHERE group_id = im_astakosgroup.group_ptr_id
1096 THEN 1 ELSE 0 END""" % request.user.id,
1098 SELECT CASE WHEN EXISTS(
1099 SELECT id FROM im_astakosuser_owner
1100 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1101 AND astakosuser_id = %s)
1102 THEN 1 ELSE 0 END""" % request.user.id,
1103 'is_owner': """SELECT CASE WHEN EXISTS(
1104 SELECT id FROM im_astakosuser_owner
1105 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1106 AND astakosuser_id = %s)
1107 THEN 1 ELSE 0 END""" % request.user.id,
1111 sort_form = AstakosGroupSortForm(request.GET)
1112 if sort_form.is_valid():
1113 sorting = sort_form.cleaned_data.get('sorting')
1114 queryset = queryset.order_by(sorting)
1117 queryset = AstakosGroup.objects.none()
1121 paginate_by=PAGINATE_BY_ALL,
1122 page=request.GET.get('page') or 1,
1123 template_name='im/astakosgroup_list.html',
1124 extra_context=dict(form=form,
1130 @require_http_methods(["GET", "POST"])
1131 @signed_terms_required
1133 def group_all(request, extra_context=None, **kwargs):
1134 q = AstakosGroup.objects.select_related()
1135 q = q.filter(~Q(kind__name='default'))
1136 q = q.filter(approval_date__isnull=False)
1137 q = q.extra(select={
1138 'groupname': "auth_group.name",
1139 'kindname': "im_groupkind.name",
1140 'approved_members_num': """
1141 SELECT COUNT(*) FROM im_membership
1142 WHERE group_id = im_astakosgroup.group_ptr_id
1143 AND date_joined IS NOT NULL""",
1144 'membership_approval_date': """
1145 SELECT date_joined FROM im_membership
1146 WHERE group_id = im_astakosgroup.group_ptr_id
1147 AND person_id = %s""" % request.user.id,
1149 SELECT CASE WHEN EXISTS(
1150 SELECT date_joined FROM im_membership
1151 WHERE group_id = im_astakosgroup.group_ptr_id
1153 THEN 1 ELSE 0 END""" % request.user.id,
1154 'is_owner': """SELECT CASE WHEN EXISTS(
1155 SELECT id FROM im_astakosuser_owner
1156 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1157 AND astakosuser_id = %s)
1158 THEN 1 ELSE 0 END""" % request.user.id, })
1161 sorting = 'groupname'
1162 print '>>>', sorting, request.GET
1163 sort_form = AstakosGroupSortForm(request.GET)
1164 if sort_form.is_valid():
1165 sorting = sort_form.cleaned_data.get('sorting')
1166 print '<<<', sorting
1167 q = q.order_by(sorting)
1172 paginate_by=PAGINATE_BY_ALL,
1173 page=request.GET.get('page') or 1,
1174 template_name='im/astakosgroup_list.html',
1175 extra_context=dict(form=AstakosGroupSearchForm(),
1180 #@require_http_methods(["POST"])
1181 @require_http_methods(["POST", "GET"])
1182 @signed_terms_required
1184 def group_join(request, group_id):
1185 m = Membership(group_id=group_id,
1186 person=request.user,
1187 date_requested=datetime.now())
1190 post_save_redirect = reverse(
1192 kwargs=dict(group_id=group_id))
1193 return HttpResponseRedirect(post_save_redirect)
1194 except IntegrityError, e:
1196 msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1197 messages.error(request, msg)
1198 return group_search(request)
1201 @require_http_methods(["POST"])
1202 @signed_terms_required
1204 def group_leave(request, group_id):
1206 m = Membership.objects.select_related().get(
1208 person=request.user)
1209 except Membership.DoesNotExist:
1210 return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1211 if request.user in m.group.owner.all():
1212 return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1213 return delete_object(
1217 template_name='im/astakosgroup_list.html',
1218 post_delete_redirect=reverse(
1220 kwargs=dict(group_id=group_id)))
1223 def handle_membership(func):
1225 def wrapper(request, group_id, user_id):
1227 m = Membership.objects.select_related().get(
1230 except Membership.DoesNotExist:
1231 return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1233 if request.user not in m.group.owner.all():
1234 return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1236 return group_detail(request, group_id)
1240 #@require_http_methods(["POST"])
1241 @require_http_methods(["POST", "GET"])
1242 @signed_terms_required
1245 def approve_member(request, membership):
1247 membership.approve()
1248 realname = membership.person.realname
1249 msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1250 messages.success(request, msg)
1251 except AssertionError:
1252 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1253 messages.error(request, msg)
1254 except BaseException, e:
1256 realname = membership.person.realname
1257 msg = _(astakos_messages.GENERIC_ERROR)
1258 messages.error(request, msg)
1261 @signed_terms_required
1264 def disapprove_member(request, membership):
1266 membership.disapprove()
1267 realname = membership.person.realname
1268 msg = astakos_messages.MEMBER_REMOVED % locals()
1269 messages.success(request, msg)
1270 except BaseException, e:
1272 msg = _(astakos_messages.GENERIC_ERROR)
1273 messages.error(request, msg)
1276 #@require_http_methods(["GET"])
1277 @require_http_methods(["POST", "GET"])
1278 @signed_terms_required
1280 def resource_list(request):
1281 def with_class(entry):
1282 entry['load_class'] = 'red'
1283 max_value = float(entry['maxValue'])
1284 curr_value = float(entry['currValue'])
1286 entry['ratio'] = (curr_value / max_value) * 100
1289 if entry['ratio'] < 66:
1290 entry['load_class'] = 'yellow'
1291 if entry['ratio'] < 33:
1292 entry['load_class'] = 'green'
1295 def pluralize(entry):
1296 entry['plural'] = engine.plural(entry.get('name'))
1299 result = callpoint.get_user_status(request.user.id)
1300 if result.is_success:
1301 backenddata = map(with_class, result.data)
1302 data = map(pluralize, result.data)
1305 messages.error(request, result.reason)
1306 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1307 resource_catalog.update_from_result_report(result)
1311 return render_response('im/resource_list.html',
1313 context_instance=get_context(request),
1314 resource_catalog=resource_catalog,
1318 def group_create_list(request):
1319 form = PickResourceForm()
1320 return render_response(
1321 template='im/astakosgroup_create_list.html',
1322 context_instance=get_context(request),)
1325 ##@require_http_methods(["GET"])
1326 #@require_http_methods(["POST", "GET"])
1327 #@signed_terms_required
1329 #def billing(request):
1331 # today = datetime.today()
1332 # month_last_day = calendar.monthrange(today.year, today.month)[1]
1333 # start = request.POST.get('datefrom', None)
1335 # today = datetime.fromtimestamp(int(start))
1336 # month_last_day = calendar.monthrange(today.year, today.month)[1]
1338 # start = datetime(today.year, today.month, 1).strftime("%s")
1339 # end = datetime(today.year, today.month, month_last_day).strftime("%s")
1340 # r = request_billing.apply(args=('pgerakios@grnet.gr',
1341 # int(start) * 1000,
1346 # status, data = r.result
1347 # data = _clear_billing_data(data)
1349 # messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1351 # messages.error(request, r.result)
1353 # return render_response(
1354 # template='im/billing.html',
1355 # context_instance=get_context(request),
1357 # zerodate=datetime(month=1, year=1970, day=1),
1360 # month_last_day=month_last_day)
1363 #def _clear_billing_data(data):
1365 # # remove addcredits entries
1366 # def isnotcredit(e):
1367 # return e['serviceName'] != "addcredits"
1369 # # separate services
1370 # def servicefilter(service_name):
1371 # service = service_name
1374 # return e['serviceName'] == service
1377 # data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1378 # data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1379 # data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1380 # data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1385 #@require_http_methods(["GET"])
1386 @require_http_methods(["POST", "GET"])
1387 @signed_terms_required
1389 def timeline(request):
1390 # data = {'entity':request.user.email}
1392 timeline_header = ()
1393 # form = TimelineForm(data)
1394 form = TimelineForm()
1395 if request.method == 'POST':
1397 form = TimelineForm(data)
1399 data = form.cleaned_data
1400 timeline_header = ('entity', 'resource',
1401 'event name', 'event date',
1402 'incremental cost', 'total cost')
1403 timeline_body = timeline_charge(
1404 data['entity'], data['resource'],
1405 data['start_date'], data['end_date'],
1406 data['details'], data['operation'])
1408 return render_response(template='im/timeline.html',
1409 context_instance=get_context(request),
1411 timeline_header=timeline_header,
1412 timeline_body=timeline_body)
1415 # TODO: action only on POST and user should confirm the removal
1416 @require_http_methods(["GET", "POST"])
1418 @signed_terms_required
1419 def remove_auth_provider(request, pk):
1421 provider = request.user.auth_providers.get(pk=pk)
1422 except AstakosUserAuthProvider.DoesNotExist:
1425 if provider.can_remove():
1427 return HttpResponseRedirect(reverse('edit_profile'))
1429 raise PermissionDenied