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
61 from django.template.loader import render_to_string
62 from django.views.decorators.http import require_http_methods
63 from astakos.im.activation_backends import get_backend, SimpleBackend
65 from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
66 EmailChange, GroupKind, Membership,
68 from astakos.im.util import get_context, prepare_response, get_query, restrict_next
69 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
70 FeedbackForm, SignApprovalTermsForm,
72 AstakosGroupCreationForm, AstakosGroupSearchForm,
73 AstakosGroupUpdateForm, AddGroupMembersForm,
75 TimelineForm, PickResourceForm,
76 AstakosGroupCreationSummaryForm)
77 from astakos.im.functions import (send_feedback, SendMailError,
78 logout as auth_logout,
79 activate as activate_func,
80 send_activation as send_activation_func,
81 send_group_creation_notification,
82 SendNotificationError)
83 from astakos.im.endpoints.qh import timeline_charge
84 from astakos.im.settings import (COOKIE_DOMAIN, LOGOUT_NEXT,
85 LOGGING_LEVEL, PAGINATE_BY, RESOURCES_PRESENTATION_DATA, PAGINATE_BY_ALL)
86 from astakos.im.tasks import request_billing
87 from astakos.im.api.callpoint import AstakosCallpoint
89 import astakos.im.messages as astakos_messages
91 logger = logging.getLogger(__name__)
93 DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
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)
113 def requires_anonymous(func):
115 Decorator checkes whether the request.user is not Anonymous and in that case
116 redirects to `logout`.
119 def wrapper(request, *args):
120 if not request.user.is_anonymous():
121 next = urlencode({'next': request.build_absolute_uri()})
122 logout_uri = reverse(logout) + '?' + next
123 return HttpResponseRedirect(logout_uri)
124 return func(request, *args)
128 def signed_terms_required(func):
130 Decorator checkes whether the request.user is Anonymous and in that case
131 redirects to `logout`.
134 def wrapper(request, *args, **kwargs):
135 if request.user.is_authenticated() and not request.user.signed_terms:
136 params = urlencode({'next': request.build_absolute_uri(),
138 terms_uri = reverse('latest_terms') + '?' + params
139 return HttpResponseRedirect(terms_uri)
140 return func(request, *args, **kwargs)
144 @require_http_methods(["GET", "POST"])
145 @signed_terms_required
146 def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context=None):
148 If there is logged on user renders the profile page otherwise renders login page.
152 ``login_template_name``
153 A custom login template to use. This is optional; if not specified,
154 this will default to ``im/login.html``.
156 ``profile_template_name``
157 A custom profile template to use. This is optional; if not specified,
158 this will default to ``im/profile.html``.
161 An dictionary of variables to add to the template context.
165 im/profile.html or im/login.html or ``template_name`` keyword argument.
168 extra_context = extra_context or {}
169 template_name = login_template_name
170 if request.user.is_authenticated():
171 return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
173 return render_response(
175 login_form = LoginForm(request=request),
176 context_instance = get_context(request, extra_context)
180 @require_http_methods(["GET", "POST"])
182 @signed_terms_required
183 @transaction.commit_manually
184 def invite(request, template_name='im/invitations.html', extra_context=None):
186 Allows a user to invite somebody else.
188 In case of GET request renders a form for providing the invitee information.
189 In case of POST checks whether the user has not run out of invitations and then
190 sends an invitation email to singup to the service.
192 The view uses commit_manually decorator in order to ensure the number of the
193 user invitations is going to be updated only if the email has been successfully sent.
195 If the user isn't logged in, redirects to settings.LOGIN_URL.
200 A custom template to use. This is optional; if not specified,
201 this will default to ``im/invitations.html``.
204 An dictionary of variables to add to the template context.
208 im/invitations.html or ``template_name`` keyword argument.
212 The view expectes the following settings are defined:
214 * LOGIN_URL: login uri
216 extra_context = extra_context or {}
219 form = InvitationForm()
221 inviter = request.user
222 if request.method == 'POST':
223 form = InvitationForm(request.POST)
224 if inviter.invitations > 0:
227 email = form.cleaned_data.get('username')
228 realname = form.cleaned_data.get('realname')
229 inviter.invite(email, realname)
230 message = _(astakos_messages.INVITATION_SENT) % locals()
231 messages.success(request, message)
232 except SendMailError, e:
234 messages.error(request, message)
235 transaction.rollback()
236 except BaseException, e:
237 message = _(astakos_messages.GENERIC_ERROR)
238 messages.error(request, message)
240 transaction.rollback()
244 message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
245 messages.error(request, message)
247 sent = [{'email': inv.username,
248 'realname': inv.realname,
249 'is_consumed': inv.is_consumed}
250 for inv in request.user.invitations_sent.all()]
251 kwargs = {'inviter': inviter,
253 context = get_context(request, extra_context, **kwargs)
254 return render_response(template_name,
255 invitation_form=form,
256 context_instance=context)
259 @require_http_methods(["GET", "POST"])
261 @signed_terms_required
262 def edit_profile(request, template_name='im/profile.html', extra_context=None):
264 Allows a user to edit his/her profile.
266 In case of GET request renders a form for displaying the user information.
267 In case of POST updates the user informantion and redirects to ``next``
268 url parameter if exists.
270 If the user isn't logged in, redirects to settings.LOGIN_URL.
275 A custom template to use. This is optional; if not specified,
276 this will default to ``im/profile.html``.
279 An dictionary of variables to add to the template context.
283 im/profile.html or ``template_name`` keyword argument.
287 The view expectes the following settings are defined:
289 * LOGIN_URL: login uri
291 extra_context = extra_context or {}
293 instance=request.user,
294 session_key=request.session.session_key
296 extra_context['next'] = request.GET.get('next')
297 if request.method == 'POST':
300 instance=request.user,
301 session_key=request.session.session_key
305 prev_token = request.user.auth_token
309 session_key=request.session.session_key
311 next = restrict_next(
312 request.POST.get('next'),
316 return redirect(next)
317 msg = _(astakos_messages.PROFILE_UPDATED)
318 messages.success(request, msg)
319 except ValueError, ve:
320 messages.success(request, ve)
321 elif request.method == "GET":
322 if not request.user.is_verified:
323 request.user.is_verified = True
325 return render_response(template_name,
327 context_instance = get_context(request,
331 @transaction.commit_manually
332 @require_http_methods(["GET", "POST"])
333 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
335 Allows a user to create a local account.
337 In case of GET request renders a form for entering the user information.
338 In case of POST handles the signup.
340 The user activation will be delegated to the backend specified by the ``backend`` keyword argument
341 if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
342 if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
343 (see activation_backends);
345 Upon successful user creation, if ``next`` url parameter is present the user is redirected there
346 otherwise renders the same page with a success message.
348 On unsuccessful creation, renders ``template_name`` with an error message.
353 A custom template to render. This is optional;
354 if not specified, this will default to ``im/signup.html``.
357 A custom template to render in case of success. This is optional;
358 if not specified, this will default to ``im/signup_complete.html``.
361 An dictionary of variables to add to the template context.
365 im/signup.html or ``template_name`` keyword argument.
366 im/signup_complete.html or ``on_success`` keyword argument.
368 extra_context = extra_context or {}
369 if request.user.is_authenticated():
370 return HttpResponseRedirect(reverse('edit_profile'))
372 provider = get_query(request).get('provider', 'local')
373 id = get_query(request).get('id')
375 instance = AstakosUser.objects.get(id=id) if id else None
376 except AstakosUser.DoesNotExist:
381 backend = get_backend(request)
382 form = backend.get_signup_form(provider, instance)
384 form = SimpleBackend(request).get_signup_form(provider)
385 messages.error(request, e)
386 if request.method == 'POST':
388 user = form.save(commit=False)
390 result = backend.handle_activation(user)
391 status = messages.SUCCESS
392 message = result.message
394 if 'additional_email' in form.cleaned_data:
395 additional_email = form.cleaned_data['additional_email']
396 if additional_email != user.email:
397 user.additionalmail_set.create(email=additional_email)
398 msg = 'Additional email: %s saved for user %s.' % (
402 logger._log(LOGGING_LEVEL, msg, [])
403 if user and user.is_active:
404 next = request.POST.get('next', '')
405 response = prepare_response(request, user, next=next)
408 messages.add_message(request, status, message)
410 return render_response(
412 context_instance=get_context(
417 except SendMailError, e:
419 status = messages.ERROR
421 messages.error(request, message)
422 transaction.rollback()
423 except BaseException, e:
425 message = _(astakos_messages.GENERIC_ERROR)
426 messages.error(request, message)
428 transaction.rollback()
429 return render_response(template_name,
432 context_instance=get_context(request, extra_context))
435 @require_http_methods(["GET", "POST"])
437 @signed_terms_required
438 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
440 Allows a user to send feedback.
442 In case of GET request renders a form for providing the feedback information.
443 In case of POST sends an email to support team.
445 If the user isn't logged in, redirects to settings.LOGIN_URL.
450 A custom template to use. This is optional; if not specified,
451 this will default to ``im/feedback.html``.
454 An dictionary of variables to add to the template context.
458 im/signup.html or ``template_name`` keyword argument.
462 * LOGIN_URL: login uri
464 extra_context = extra_context or {}
465 if request.method == 'GET':
466 form = FeedbackForm()
467 if request.method == 'POST':
469 return HttpResponse('Unauthorized', status=401)
471 form = FeedbackForm(request.POST)
473 msg = form.cleaned_data['feedback_msg']
474 data = form.cleaned_data['feedback_data']
476 send_feedback(msg, data, request.user, email_template_name)
477 except SendMailError, e:
478 messages.error(request, message)
480 message = _(astakos_messages.FEEDBACK_SENT)
481 messages.success(request, message)
482 return render_response(template_name,
484 context_instance=get_context(request, extra_context))
487 @require_http_methods(["GET"])
488 @signed_terms_required
489 def logout(request, template='registration/logged_out.html', extra_context=None):
491 Wraps `django.contrib.auth.logout`.
493 extra_context = extra_context or {}
494 response = HttpResponse()
495 if request.user.is_authenticated():
496 email = request.user.email
498 next = restrict_next(
499 request.GET.get('next'),
503 response['Location'] = next
504 response.status_code = 302
506 response['Location'] = LOGOUT_NEXT
507 response.status_code = 301
509 messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
510 context = get_context(request, extra_context)
511 response.write(render_to_string(template, context_instance=context))
515 @require_http_methods(["GET", "POST"])
516 @transaction.commit_manually
517 def activate(request, greeting_email_template_name='im/welcome_email.txt',
518 helpdesk_email_template_name='im/helpdesk_notification.txt'):
520 Activates the user identified by the ``auth`` request parameter, sends a welcome email
521 and renews the user token.
523 The view uses commit_manually decorator in order to ensure the user state will be updated
524 only if the email will be send successfully.
526 token = request.GET.get('auth')
527 next = request.GET.get('next')
529 user = AstakosUser.objects.get(auth_token=token)
530 except AstakosUser.DoesNotExist:
531 return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
534 message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
535 messages.error(request, message)
536 return index(request)
539 activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
540 response = prepare_response(request, user, next, renew=True)
543 except SendMailError, e:
545 messages.add_message(request, messages.ERROR, message)
546 transaction.rollback()
547 return index(request)
548 except BaseException, e:
549 status = messages.ERROR
550 message = _(astakos_messages.GENERIC_ERROR)
551 messages.add_message(request, messages.ERROR, message)
553 transaction.rollback()
554 return index(request)
557 @require_http_methods(["GET", "POST"])
558 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
559 extra_context = extra_context or {}
564 term = ApprovalTerms.objects.order_by('-id')[0]
569 term = ApprovalTerms.objects.get(id=term_id)
570 except ApprovalTerms.DoesNotExist, e:
574 messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
575 return HttpResponseRedirect(reverse('index'))
576 f = open(term.location, 'r')
579 if request.method == 'POST':
580 next = restrict_next(
581 request.POST.get('next'),
585 next = reverse('index')
586 form = SignApprovalTermsForm(request.POST, instance=request.user)
587 if not form.is_valid():
588 return render_response(template_name,
590 approval_terms_form=form,
591 context_instance=get_context(request, extra_context))
593 return HttpResponseRedirect(next)
596 if request.user.is_authenticated() and not request.user.signed_terms:
597 form = SignApprovalTermsForm(instance=request.user)
598 return render_response(template_name,
600 approval_terms_form=form,
601 context_instance=get_context(request, extra_context))
604 @require_http_methods(["GET", "POST"])
606 @signed_terms_required
607 @transaction.commit_manually
608 def change_email(request, activation_key=None,
609 email_template_name='registration/email_change_email.txt',
610 form_template_name='registration/email_change_form.html',
611 confirm_template_name='registration/email_change_done.html',
613 extra_context = extra_context or {}
616 user = EmailChange.objects.change_email(activation_key)
617 if request.user.is_authenticated() and request.user == user:
618 msg = _(astakos_messages.EMAIL_CHANGED)
619 messages.success(request, msg)
621 response = prepare_response(request, user)
624 except ValueError, e:
625 messages.error(request, e)
626 return render_response(confirm_template_name,
627 modified_user=user if 'user' in locals(
629 context_instance=get_context(request,
632 if not request.user.is_authenticated():
633 path = quote(request.get_full_path())
634 url = request.build_absolute_uri(reverse('index'))
635 return HttpResponseRedirect(url + '?next=' + path)
636 form = EmailChangeForm(request.POST or None)
637 if request.method == 'POST' and form.is_valid():
639 ec = form.save(email_template_name, request)
640 except SendMailError, e:
642 messages.error(request, msg)
643 transaction.rollback()
644 except IntegrityError, e:
645 msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
646 messages.error(request, msg)
648 msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
649 messages.success(request, msg)
651 return render_response(
654 context_instance=get_context(request, extra_context)
658 def send_activation(request, user_id, template_name='im/login.html', extra_context=None):
659 extra_context = extra_context or {}
661 u = AstakosUser.objects.get(id=user_id)
662 except AstakosUser.DoesNotExist:
663 messages.error(request, _(astakos_messages.ACCOUNT_UNKNOWN))
666 send_activation_func(u)
667 msg = _(astakos_messages.ACTIVATION_SENT)
668 messages.success(request, msg)
669 except SendMailError, e:
670 messages.error(request, e)
671 return render_response(
673 login_form = LoginForm(request=request),
674 context_instance = get_context(
680 class ResourcePresentation():
682 def __init__(self, data):
685 def update_from_result(self, result):
686 if result.is_success:
687 for r in result.data:
688 rname = '%s%s%s' % (r.get('service'), RESOURCE_SEPARATOR, r.get('name'))
689 if not rname in self.data['resources']:
690 self.data['resources'][rname] = {}
692 self.data['resources'][rname].update(r)
693 self.data['resources'][rname]['id'] = rname
694 group = r.get('group')
695 if not group in self.data['groups']:
696 self.data['groups'][group] = {}
698 self.data['groups'][r.get('group')].update({'name': r.get('group')})
700 def test(self, quota_dict):
701 for k, v in quota_dict.iteritems():
704 if not rname in self.data['resources']:
705 self.data['resources'][rname] = {}
708 self.data['resources'][rname]['value'] = value
711 def update_from_result_report(self, result):
712 if result.is_success:
713 for r in result.data:
714 rname = r.get('name')
715 if not rname in self.data['resources']:
716 self.data['resources'][rname] = {}
718 self.data['resources'][rname].update(r)
719 self.data['resources'][rname]['id'] = rname
720 group = r.get('group')
721 if not group in self.data['groups']:
722 self.data['groups'][group] = {}
724 self.data['groups'][r.get('group')].update({'name': r.get('group')})
726 def get_group_resources(self, group):
727 return dict(filter(lambda t: t[1].get('group') == group, self.data['resources'].iteritems()))
729 def get_groups_resources(self):
730 for g in self.data['groups']:
731 yield g, self.get_group_resources(g)
733 def get_quota(self, group_quotas):
734 for r, v in group_quotas.iteritems():
736 quota = self.data['resources'].get(rname)
741 def get_policies(self, policies_data):
742 for policy in policies_data:
743 rname = '%s%s%s' % (policy.get('service'), RESOURCE_SEPARATOR, policy.get('resource'))
744 policy.update(self.data['resources'].get(rname))
748 return self.data.__repr__()
750 def __iter__(self, *args, **kwargs):
751 return self.data.__iter__(*args, **kwargs)
753 def __getitem__(self, *args, **kwargs):
754 return self.data.__getitem__(*args, **kwargs)
756 def get(self, *args, **kwargs):
757 return self.data.get(*args, **kwargs)
761 @require_http_methods(["GET", "POST"])
762 @signed_terms_required
764 def group_add(request, kind_name='default'):
766 result = callpoint.list_resources()
767 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
768 resource_catalog.update_from_result(result)
770 if not result.is_success:
773 'Unable to retrieve system resources: %s' % result.reason
777 kind = GroupKind.objects.get(name=kind_name)
779 return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
783 post_save_redirect = '/im/group/%(id)s/'
784 context_processors = None
785 model, form_class = get_model_and_form_class(
787 form_class=AstakosGroupCreationForm
790 if request.method == 'POST':
791 form = form_class(request.POST, request.FILES)
793 return render_response(
794 template='im/astakosgroup_form_summary.html',
795 context_instance=get_context(request),
796 form = AstakosGroupCreationSummaryForm(form.cleaned_data),
797 policies = resource_catalog.get_policies(form.policies()),
798 resource_catalog= resource_catalog,
806 for group, resources in resource_catalog.get_groups_resources():
807 data['is_selected_%s' % group] = False
808 for resource in resources:
809 data['%s_uplimit' % resource] = ''
811 form = form_class(data)
813 # Create the template, context, response
814 template_name = "%s/%s_form.html" % (
815 model._meta.app_label,
816 model._meta.object_name.lower()
818 t = template_loader.get_template(template_name)
819 c = RequestContext(request, {
822 'resource_catalog':resource_catalog,
823 }, context_processors)
824 return HttpResponse(t.render(c))
827 #@require_http_methods(["POST"])
828 @require_http_methods(["GET", "POST"])
829 @signed_terms_required
831 def group_add_complete(request):
833 form = AstakosGroupCreationSummaryForm(request.POST)
835 d = form.cleaned_data
836 d['owners'] = [request.user]
837 result = callpoint.create_groups((d,)).next()
838 if result.is_success:
839 new_object = result.data[0]
840 msg = _(astakos_messages.OBJECT_CREATED) %\
841 {"verbose_name": model._meta.verbose_name}
842 messages.success(request, msg, fail_silently=True)
846 send_group_creation_notification(
847 template_name='im/group_creation_notification.txt',
850 'owner': request.user,
851 'policies': d.get('policies', [])
854 except SendNotificationError, e:
855 messages.error(request, e, fail_silently=True)
856 post_save_redirect = '/im/group/%(id)s/'
857 return HttpResponseRedirect(post_save_redirect % new_object)
859 d = {"verbose_name": model._meta.verbose_name,
860 "reason":result.reason}
861 msg = _(astakos_messages.OBJECT_CREATED_FAILED) % d
862 messages.error(request, msg, fail_silently=True)
863 return render_response(
864 template='im/astakosgroup_form_summary.html',
865 context_instance=get_context(request),
869 #@require_http_methods(["GET"])
870 @require_http_methods(["GET", "POST"])
871 @signed_terms_required
873 def group_list(request):
874 none = request.user.astakos_groups.none()
875 sorting = request.GET.get('sorting')
877 SELECT auth_group.id,
879 im_groupkind.name AS kindname,
881 owner.email AS groupowner,
882 (SELECT COUNT(*) FROM im_membership
883 WHERE group_id = im_astakosgroup.group_ptr_id
884 AND date_joined IS NOT NULL) AS approved_members_num,
886 SELECT date_joined FROM im_membership
887 WHERE group_id = im_astakosgroup.group_ptr_id
888 AND person_id = %s) IS NULL
889 THEN 0 ELSE 1 END) AS membership_status
891 INNER JOIN im_membership ON (
892 im_astakosgroup.group_ptr_id = im_membership.group_id)
893 INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
894 INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
895 LEFT JOIN im_astakosuser_owner ON (
896 im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
897 LEFT JOIN auth_user as owner ON (
898 im_astakosuser_owner.astakosuser_id = owner.id)
899 WHERE im_membership.person_id = %s
900 """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id)
903 query = query+" ORDER BY %s ASC" %sorting
905 query = query+" ORDER BY groupname ASC"
906 q = AstakosGroup.objects.raw(query)
910 # Create the template, context, response
911 template_name = "%s/%s_list.html" % (
912 q.model._meta.app_label,
913 q.model._meta.object_name.lower()
915 extra_context = dict(
918 sorting=request.GET.get('sorting'),
919 page=request.GET.get('page', 1)
921 return render_response(template_name,
922 context_instance=get_context(request, extra_context)
926 @require_http_methods(["GET", "POST"])
927 @signed_terms_required
929 def group_detail(request, group_id):
930 q = AstakosGroup.objects.select_related().filter(pk=group_id)
932 'is_member': """SELECT CASE WHEN EXISTS(
933 SELECT id FROM im_membership
934 WHERE group_id = im_astakosgroup.group_ptr_id
936 THEN 1 ELSE 0 END""" % request.user.id,
937 'is_owner': """SELECT CASE WHEN EXISTS(
938 SELECT id FROM im_astakosuser_owner
939 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
940 AND astakosuser_id = %s)
941 THEN 1 ELSE 0 END""" % request.user.id,
942 'kindname': """SELECT name FROM im_groupkind
943 WHERE id = im_astakosgroup.kind_id"""})
946 context_processors = None
950 except AstakosGroup.DoesNotExist:
951 raise Http404("No %s found matching the query" % (
952 model._meta.verbose_name))
954 update_form = AstakosGroupUpdateForm(instance=obj)
955 addmembers_form = AddGroupMembersForm()
956 if request.method == 'POST':
959 for k, v in request.POST.iteritems():
960 if k in update_form.fields:
962 if k in addmembers_form.fields:
963 addmembers_data[k] = v
964 update_data = update_data or None
965 addmembers_data = addmembers_data or None
966 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
967 addmembers_form = AddGroupMembersForm(addmembers_data)
968 if update_form.is_valid():
970 if addmembers_form.is_valid():
972 map(obj.approve_member, addmembers_form.valid_users)
973 except AssertionError:
974 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
975 messages.error(request, msg)
976 addmembers_form = AddGroupMembersForm()
978 template_name = "%s/%s_detail.html" % (
979 model._meta.app_label, model._meta.object_name.lower())
980 t = template_loader.get_template(template_name)
981 c = RequestContext(request, {
983 }, context_processors)
986 sorting = request.GET.get('sorting')
988 form = MembersSortForm({'sort_by': sorting})
990 sorting = form.cleaned_data.get('sort_by')
993 form = MembersSortForm({'sort_by': 'person_first_name'})
995 result = callpoint.list_resources()
996 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
997 resource_catalog.update_from_result(result)
1000 if not result.is_success:
1003 'Unable to retrieve system resources: %s' % result.reason
1006 extra_context = {'update_form': update_form,
1007 'addmembers_form': addmembers_form,
1008 'page': request.GET.get('page', 1),
1010 'resource_catalog':resource_catalog,
1011 'quota':resource_catalog.get_quota(obj.quota)}
1012 for key, value in extra_context.items():
1017 response = HttpResponse(t.render(c), mimetype=mimetype)
1019 request, response, model, getattr(obj, obj._meta.pk.name))
1023 @require_http_methods(["GET", "POST"])
1024 @signed_terms_required
1026 def group_search(request, extra_context=None, **kwargs):
1027 q = request.GET.get('q')
1028 sorting = request.GET.get('sorting')
1029 if request.method == 'GET':
1030 form = AstakosGroupSearchForm({'q': q} if q else None)
1032 form = AstakosGroupSearchForm(get_query(request))
1034 q = form.cleaned_data['q'].strip()
1036 queryset = AstakosGroup.objects.select_related()
1037 queryset = queryset.filter(name__contains=q)
1038 queryset = queryset.filter(approval_date__isnull=False)
1039 queryset = queryset.extra(select={
1040 'groupname': DB_REPLACE_GROUP_SCHEME,
1041 'kindname': "im_groupkind.name",
1042 'approved_members_num': """
1043 SELECT COUNT(*) FROM im_membership
1044 WHERE group_id = im_astakosgroup.group_ptr_id
1045 AND date_joined IS NOT NULL""",
1046 'membership_approval_date': """
1047 SELECT date_joined FROM im_membership
1048 WHERE group_id = im_astakosgroup.group_ptr_id
1049 AND person_id = %s""" % request.user.id,
1051 SELECT CASE WHEN EXISTS(
1052 SELECT date_joined FROM im_membership
1053 WHERE group_id = im_astakosgroup.group_ptr_id
1055 THEN 1 ELSE 0 END""" % request.user.id,
1057 SELECT CASE WHEN EXISTS(
1058 SELECT id FROM im_astakosuser_owner
1059 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1060 AND astakosuser_id = %s)
1061 THEN 1 ELSE 0 END""" % request.user.id,
1062 'is_owner': """SELECT CASE WHEN EXISTS(
1063 SELECT id FROM im_astakosuser_owner
1064 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1065 AND astakosuser_id = %s)
1066 THEN 1 ELSE 0 END""" % request.user.id,
1069 # TODO check sorting value
1070 queryset = queryset.order_by(sorting)
1072 queryset = queryset.order_by("groupname")
1075 queryset = AstakosGroup.objects.none()
1079 paginate_by=PAGINATE_BY_ALL,
1080 page=request.GET.get('page') or 1,
1081 template_name='im/astakosgroup_list.html',
1082 extra_context=dict(form=form,
1088 @require_http_methods(["GET", "POST"])
1089 @signed_terms_required
1091 def group_all(request, extra_context=None, **kwargs):
1092 q = AstakosGroup.objects.select_related()
1093 q = q.filter(approval_date__isnull=False)
1094 q = q.extra(select={
1095 'groupname': DB_REPLACE_GROUP_SCHEME,
1096 'kindname': "im_groupkind.name",
1097 'approved_members_num': """
1098 SELECT COUNT(*) FROM im_membership
1099 WHERE group_id = im_astakosgroup.group_ptr_id
1100 AND date_joined IS NOT NULL""",
1101 'membership_approval_date': """
1102 SELECT date_joined FROM im_membership
1103 WHERE group_id = im_astakosgroup.group_ptr_id
1104 AND person_id = %s""" % request.user.id,
1106 SELECT CASE WHEN EXISTS(
1107 SELECT date_joined FROM im_membership
1108 WHERE group_id = im_astakosgroup.group_ptr_id
1110 THEN 1 ELSE 0 END""" % request.user.id,
1111 'is_owner': """SELECT CASE WHEN EXISTS(
1112 SELECT id FROM im_astakosuser_owner
1113 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
1114 AND astakosuser_id = %s)
1115 THEN 1 ELSE 0 END""" % request.user.id, })
1116 sorting = request.GET.get('sorting')
1118 # TODO check sorting value
1119 q = q.order_by(sorting)
1121 q = q.order_by("groupname")
1126 paginate_by=PAGINATE_BY_ALL,
1127 page=request.GET.get('page') or 1,
1128 template_name='im/astakosgroup_list.html',
1129 extra_context=dict(form=AstakosGroupSearchForm(),
1134 #@require_http_methods(["POST"])
1135 @require_http_methods(["POST", "GET"])
1136 @signed_terms_required
1138 def group_join(request, group_id):
1139 m = Membership(group_id=group_id,
1140 person=request.user,
1141 date_requested=datetime.now())
1144 post_save_redirect = reverse(
1146 kwargs=dict(group_id=group_id))
1147 return HttpResponseRedirect(post_save_redirect)
1148 except IntegrityError, e:
1150 msg = _(astakos_messages.GROUP_JOIN_FAILURE)
1151 messages.error(request, msg)
1152 return group_search(request)
1155 @require_http_methods(["POST"])
1156 @signed_terms_required
1158 def group_leave(request, group_id):
1160 m = Membership.objects.select_related().get(
1162 person=request.user)
1163 except Membership.DoesNotExist:
1164 return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1165 if request.user in m.group.owner.all():
1166 return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
1167 return delete_object(
1171 template_name='im/astakosgroup_list.html',
1172 post_delete_redirect=reverse(
1174 kwargs=dict(group_id=group_id)))
1177 def handle_membership(func):
1179 def wrapper(request, group_id, user_id):
1181 m = Membership.objects.select_related().get(
1184 except Membership.DoesNotExist:
1185 return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
1187 if request.user not in m.group.owner.all():
1188 return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
1190 return group_detail(request, group_id)
1194 #@require_http_methods(["POST"])
1195 @require_http_methods(["POST", "GET"])
1196 @signed_terms_required
1199 def approve_member(request, membership):
1201 membership.approve()
1202 realname = membership.person.realname
1203 msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
1204 messages.success(request, msg)
1205 except AssertionError:
1206 msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
1207 messages.error(request, msg)
1208 except BaseException, e:
1210 realname = membership.person.realname
1211 msg = _(astakos_messages.GENERIC_ERROR)
1212 messages.error(request, msg)
1215 @signed_terms_required
1218 def disapprove_member(request, membership):
1220 membership.disapprove()
1221 realname = membership.person.realname
1222 msg = astakos_messages.MEMBER_REMOVED % realname
1223 messages.success(request, msg)
1224 except BaseException, e:
1226 msg = _(astakos_messages.GENERIC_ERROR)
1227 messages.error(request, msg)
1230 #@require_http_methods(["GET"])
1231 @require_http_methods(["POST", "GET"])
1232 @signed_terms_required
1234 def resource_list(request):
1235 def with_class(entry):
1236 entry['load_class'] = 'red'
1237 max_value = float(entry['maxValue'])
1238 curr_value = float(entry['currValue'])
1240 entry['ratio'] = (curr_value / max_value) * 100
1243 if entry['ratio'] < 66:
1244 entry['load_class'] = 'yellow'
1245 if entry['ratio'] < 33:
1246 entry['load_class'] = 'green'
1249 def pluralize(entry):
1250 entry['plural'] = engine.plural(entry.get('name'))
1253 result = callpoint.get_user_status(request.user.id)
1254 if result.is_success:
1255 backenddata = map(with_class, result.data)
1256 data = map(pluralize, result.data)
1259 messages.error(request, result.reason)
1260 resource_catalog = ResourcePresentation(RESOURCES_PRESENTATION_DATA)
1261 resource_catalog.update_from_result_report(result)
1265 return render_response('im/resource_list.html',
1267 context_instance=get_context(request),
1268 resource_catalog=resource_catalog,
1272 def group_create_list(request):
1273 form = PickResourceForm()
1274 return render_response(
1275 template='im/astakosgroup_create_list.html',
1276 context_instance=get_context(request),)
1279 #@require_http_methods(["GET"])
1280 @require_http_methods(["POST", "GET"])
1281 @signed_terms_required
1283 def billing(request):
1285 today = datetime.today()
1286 month_last_day = calendar.monthrange(today.year, today.month)[1]
1287 start = request.POST.get('datefrom', None)
1289 today = datetime.fromtimestamp(int(start))
1290 month_last_day = calendar.monthrange(today.year, today.month)[1]
1292 start = datetime(today.year, today.month, 1).strftime("%s")
1293 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1294 r = request_billing.apply(args=('pgerakios@grnet.gr',
1300 status, data = r.result
1301 data = _clear_billing_data(data)
1303 messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
1305 messages.error(request, r.result)
1307 return render_response(
1308 template='im/billing.html',
1309 context_instance=get_context(request),
1311 zerodate=datetime(month=1, year=1970, day=1),
1314 month_last_day=month_last_day)
1317 def _clear_billing_data(data):
1319 # remove addcredits entries
1321 return e['serviceName'] != "addcredits"
1324 def servicefilter(service_name):
1325 service = service_name
1328 return e['serviceName'] == service
1331 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1332 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1333 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1334 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1339 #@require_http_methods(["GET"])
1340 @require_http_methods(["POST", "GET"])
1341 @signed_terms_required
1343 def timeline(request):
1344 # data = {'entity':request.user.email}
1346 timeline_header = ()
1347 # form = TimelineForm(data)
1348 form = TimelineForm()
1349 if request.method == 'POST':
1351 form = TimelineForm(data)
1353 data = form.cleaned_data
1354 timeline_header = ('entity', 'resource',
1355 'event name', 'event date',
1356 'incremental cost', 'total cost')
1357 timeline_body = timeline_charge(
1358 data['entity'], data['resource'],
1359 data['start_date'], data['end_date'],
1360 data['details'], data['operation'])
1362 return render_response(template='im/timeline.html',
1363 context_instance=get_context(request),
1365 timeline_header=timeline_header,
1366 timeline_body=timeline_body)