1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
37 from urllib import quote
38 from functools import wraps
39 from datetime import datetime, timedelta
40 from collections import defaultdict
42 from django.contrib import messages
43 from django.contrib.auth.decorators import login_required
44 from django.contrib.auth.views import password_change
45 from django.core.urlresolvers import reverse
46 from django.db import transaction
47 from django.db.models import Q
48 from django.db.utils import IntegrityError
49 from django.forms.fields import URLField
50 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
51 HttpResponseRedirect, 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 (create_object, delete_object,
57 get_model_and_form_class)
58 from django.views.generic.list_detail import object_list, object_detail
59 from django.http import HttpResponseBadRequest
60 from django.core.xheaders import populate_xheaders
62 from astakos.im.models import (
63 AstakosUser, ApprovalTerms, AstakosGroup, Resource,
64 EmailChange, GroupKind, Membership, AstakosGroupQuota)
65 from astakos.im.activation_backends import get_backend, SimpleBackend
66 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
67 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
68 FeedbackForm, SignApprovalTermsForm,
69 ExtendedPasswordChangeForm, EmailChangeForm,
70 AstakosGroupCreationForm, AstakosGroupSearchForm,
71 AstakosGroupUpdateForm, AddGroupMembersForm,
72 AstakosGroupSortForm, MembersSortForm,
73 TimelineForm, PickResourceForm)
74 from astakos.im.functions import (send_feedback, SendMailError,
75 invite as invite_func, logout as auth_logout,
76 activate as activate_func,
77 switch_account_to_shibboleth,
78 send_admin_notification,
79 SendNotificationError)
80 from astakos.im.endpoints.quotaholder import timeline_charge
81 from astakos.im.settings import (
82 COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
83 LOGGING_LEVEL, PAGINATE_BY)
84 from astakos.im.tasks import request_billing
86 logger = logging.getLogger(__name__)
89 DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
92 def render_response(template, tab=None, status=200, reset_cookie=False,
93 context_instance=None, **kwargs):
95 Calls ``django.template.loader.render_to_string`` with an additional ``tab``
96 keyword argument and returns an ``django.http.HttpResponse`` with the
100 tab = template.partition('_')[0].partition('.html')[0]
101 kwargs.setdefault('tab', tab)
102 html = template_loader.render_to_string(
103 template, kwargs, context_instance=context_instance)
104 response = HttpResponse(html, status=status)
106 set_cookie(response, context_instance['request'].user)
110 def requires_anonymous(func):
112 Decorator checkes whether the request.user is not Anonymous and in that case
113 redirects to `logout`.
116 def wrapper(request, *args):
117 if not request.user.is_anonymous():
118 next = urlencode({'next': request.build_absolute_uri()})
119 logout_uri = reverse(logout) + '?' + next
120 return HttpResponseRedirect(logout_uri)
121 return func(request, *args)
125 def signed_terms_required(func):
127 Decorator checkes whether the request.user is Anonymous and in that case
128 redirects to `logout`.
131 def wrapper(request, *args, **kwargs):
132 if request.user.is_authenticated() and not request.user.signed_terms:
133 params = urlencode({'next': request.build_absolute_uri(),
135 terms_uri = reverse('latest_terms') + '?' + params
136 return HttpResponseRedirect(terms_uri)
137 return func(request, *args, **kwargs)
141 @signed_terms_required
142 def index(request, login_template_name='im/login.html', extra_context=None):
144 If there is logged on user renders the profile page otherwise renders login page.
148 ``login_template_name``
149 A custom login template to use. This is optional; if not specified,
150 this will default to ``im/login.html``.
152 ``profile_template_name``
153 A custom profile template to use. This is optional; if not specified,
154 this will default to ``im/profile.html``.
157 An dictionary of variables to add to the template context.
161 im/profile.html or im/login.html or ``template_name`` keyword argument.
164 template_name = login_template_name
165 if request.user.is_authenticated():
166 return HttpResponseRedirect(reverse('edit_profile'))
167 return render_response(template_name,
168 login_form=LoginForm(request=request),
169 context_instance=get_context(request, extra_context))
173 @signed_terms_required
174 @transaction.commit_manually
175 def invite(request, template_name='im/invitations.html', extra_context=None):
177 Allows a user to invite somebody else.
179 In case of GET request renders a form for providing the invitee information.
180 In case of POST checks whether the user has not run out of invitations and then
181 sends an invitation email to singup to the service.
183 The view uses commit_manually decorator in order to ensure the number of the
184 user invitations is going to be updated only if the email has been successfully sent.
186 If the user isn't logged in, redirects to settings.LOGIN_URL.
191 A custom template to use. This is optional; if not specified,
192 this will default to ``im/invitations.html``.
195 An dictionary of variables to add to the template context.
199 im/invitations.html or ``template_name`` keyword argument.
203 The view expectes the following settings are defined:
205 * LOGIN_URL: login uri
206 * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
210 form = InvitationForm()
212 inviter = request.user
213 if request.method == 'POST':
214 form = InvitationForm(request.POST)
215 if inviter.invitations > 0:
218 invitation = form.save()
219 invite_func(invitation, inviter)
220 message = _('Invitation sent to %s' % invitation.username)
221 messages.success(request, message)
222 except SendMailError, e:
224 messages.error(request, message)
225 transaction.rollback()
226 except BaseException, e:
227 message = _('Something went wrong.')
228 messages.error(request, message)
230 transaction.rollback()
234 message = _('No invitations left')
235 messages.error(request, message)
237 sent = [{'email': inv.username,
238 'realname': inv.realname,
239 'is_consumed': inv.is_consumed}
240 for inv in request.user.invitations_sent.all()]
241 kwargs = {'inviter': inviter,
243 context = get_context(request, extra_context, **kwargs)
244 return render_response(template_name,
245 invitation_form=form,
246 context_instance=context)
250 @signed_terms_required
251 def edit_profile(request, template_name='im/profile.html', extra_context=None):
253 Allows a user to edit his/her profile.
255 In case of GET request renders a form for displaying the user information.
256 In case of POST updates the user informantion and redirects to ``next``
257 url parameter if exists.
259 If the user isn't logged in, redirects to settings.LOGIN_URL.
264 A custom template to use. This is optional; if not specified,
265 this will default to ``im/profile.html``.
268 An dictionary of variables to add to the template context.
272 im/profile.html or ``template_name`` keyword argument.
276 The view expectes the following settings are defined:
278 * LOGIN_URL: login uri
280 extra_context = extra_context or {}
281 form = ProfileForm(instance=request.user)
282 extra_context['next'] = request.GET.get('next')
284 if request.method == 'POST':
285 form = ProfileForm(request.POST, instance=request.user)
288 prev_token = request.user.auth_token
290 reset_cookie = user.auth_token != prev_token
291 form = ProfileForm(instance=user)
292 next = request.POST.get('next')
294 return redirect(next)
295 msg = _('Profile has been updated successfully')
296 messages.success(request, msg)
297 except ValueError, ve:
298 messages.success(request, ve)
299 elif request.method == "GET":
300 if not request.user.is_verified:
301 request.user.is_verified = True
303 return render_response(template_name,
304 reset_cookie=reset_cookie,
306 context_instance=get_context(request,
310 @transaction.commit_manually
311 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
313 Allows a user to create a local account.
315 In case of GET request renders a form for entering the user information.
316 In case of POST handles the signup.
318 The user activation will be delegated to the backend specified by the ``backend`` keyword argument
319 if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
320 if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
321 (see activation_backends);
323 Upon successful user creation, if ``next`` url parameter is present the user is redirected there
324 otherwise renders the same page with a success message.
326 On unsuccessful creation, renders ``template_name`` with an error message.
331 A custom template to render. This is optional;
332 if not specified, this will default to ``im/signup.html``.
335 A custom template to render in case of success. This is optional;
336 if not specified, this will default to ``im/signup_complete.html``.
339 An dictionary of variables to add to the template context.
343 im/signup.html or ``template_name`` keyword argument.
344 im/signup_complete.html or ``on_success`` keyword argument.
346 if request.user.is_authenticated():
347 return HttpResponseRedirect(reverse('edit_profile'))
349 provider = get_query(request).get('provider', 'local')
352 backend = get_backend(request)
353 form = backend.get_signup_form(provider)
355 form = SimpleBackend(request).get_signup_form(provider)
356 messages.error(request, e)
357 if request.method == 'POST':
359 user = form.save(commit=False)
361 result = backend.handle_activation(user)
362 status = messages.SUCCESS
363 message = result.message
365 if 'additional_email' in form.cleaned_data:
366 additional_email = form.cleaned_data['additional_email']
367 if additional_email != user.email:
368 user.additionalmail_set.create(email=additional_email)
369 msg = 'Additional email: %s saved for user %s.' % (
370 additional_email, user.email)
371 logger.log(LOGGING_LEVEL, msg)
372 if user and user.is_active:
373 next = request.POST.get('next', '')
375 return prepare_response(request, user, next=next)
376 messages.add_message(request, status, message)
378 return render_response(on_success,
379 context_instance=get_context(request, extra_context))
380 except SendMailError, e:
382 messages.error(request, message)
383 transaction.rollback()
384 except BaseException, e:
385 message = _('Something went wrong.')
386 messages.error(request, message)
388 transaction.rollback()
389 return render_response(template_name,
392 context_instance=get_context(request, extra_context))
396 @signed_terms_required
397 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
399 Allows a user to send feedback.
401 In case of GET request renders a form for providing the feedback information.
402 In case of POST sends an email to support team.
404 If the user isn't logged in, redirects to settings.LOGIN_URL.
409 A custom template to use. This is optional; if not specified,
410 this will default to ``im/feedback.html``.
413 An dictionary of variables to add to the template context.
417 im/signup.html or ``template_name`` keyword argument.
421 * LOGIN_URL: login uri
422 * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
424 if request.method == 'GET':
425 form = FeedbackForm()
426 if request.method == 'POST':
428 return HttpResponse('Unauthorized', status=401)
430 form = FeedbackForm(request.POST)
432 msg = form.cleaned_data['feedback_msg']
433 data = form.cleaned_data['feedback_data']
435 send_feedback(msg, data, request.user, email_template_name)
436 except SendMailError, e:
437 messages.error(request, message)
439 message = _('Feedback successfully sent')
440 messages.success(request, message)
441 return render_response(template_name,
443 context_instance=get_context(request, extra_context))
446 @signed_terms_required
447 def logout(request, template='registration/logged_out.html', extra_context=None):
449 Wraps `django.contrib.auth.logout` and delete the cookie.
451 response = HttpResponse()
452 if request.user.is_authenticated():
453 email = request.user.email
455 response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
456 msg = 'Cookie deleted for %s' % email
457 logger.log(LOGGING_LEVEL, msg)
458 next = request.GET.get('next')
460 response['Location'] = next
461 response.status_code = 302
464 response['Location'] = LOGOUT_NEXT
465 response.status_code = 301
467 messages.success(request, _('You have successfully logged out.'))
468 context = get_context(request, extra_context)
469 response.write(template_loader.render_to_string(template, context_instance=context))
473 @transaction.commit_manually
474 def activate(request, greeting_email_template_name='im/welcome_email.txt',
475 helpdesk_email_template_name='im/helpdesk_notification.txt'):
477 Activates the user identified by the ``auth`` request parameter, sends a welcome email
478 and renews the user token.
480 The view uses commit_manually decorator in order to ensure the user state will be updated
481 only if the email will be send successfully.
483 token = request.GET.get('auth')
484 next = request.GET.get('next')
486 user = AstakosUser.objects.get(auth_token=token)
487 except AstakosUser.DoesNotExist:
488 return HttpResponseBadRequest(_('No such user'))
491 message = _('Account already active.')
492 messages.error(request, message)
493 return index(request)
496 local_user = AstakosUser.objects.get(
501 except AstakosUser.DoesNotExist:
505 greeting_email_template_name,
506 helpdesk_email_template_name,
509 response = prepare_response(request, user, next, renew=True)
512 except SendMailError, e:
514 messages.error(request, message)
515 transaction.rollback()
516 return index(request)
517 except BaseException, e:
518 message = _('Something went wrong.')
519 messages.error(request, message)
521 transaction.rollback()
522 return index(request)
525 user = switch_account_to_shibboleth(
528 greeting_email_template_name
530 response = prepare_response(request, user, next, renew=True)
533 except SendMailError, e:
535 messages.error(request, message)
536 transaction.rollback()
537 return index(request)
538 except BaseException, e:
539 message = _('Something went wrong.')
540 messages.error(request, message)
542 transaction.rollback()
543 return index(request)
546 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
551 term = ApprovalTerms.objects.order_by('-id')[0]
556 term = ApprovalTerms.objects.get(id=term_id)
557 except ApprovalTerms.DoesNotExist, e:
561 return HttpResponseRedirect(reverse('index'))
562 f = open(term.location, 'r')
565 if request.method == 'POST':
566 next = request.POST.get('next')
568 next = reverse('index')
569 form = SignApprovalTermsForm(request.POST, instance=request.user)
570 if not form.is_valid():
571 return render_response(template_name,
573 approval_terms_form=form,
574 context_instance=get_context(request, extra_context))
576 return HttpResponseRedirect(next)
579 if request.user.is_authenticated() and not request.user.signed_terms:
580 form = SignApprovalTermsForm(instance=request.user)
581 return render_response(template_name,
583 approval_terms_form=form,
584 context_instance=get_context(request, extra_context))
587 @signed_terms_required
588 def change_password(request):
589 return password_change(request,
590 post_change_redirect=reverse('edit_profile'),
591 password_change_form=ExtendedPasswordChangeForm)
594 @signed_terms_required
596 @transaction.commit_manually
597 def change_email(request, activation_key=None,
598 email_template_name='registration/email_change_email.txt',
599 form_template_name='registration/email_change_form.html',
600 confirm_template_name='registration/email_change_done.html',
604 user = EmailChange.objects.change_email(activation_key)
605 if request.user.is_authenticated() and request.user == user:
606 msg = _('Email changed successfully.')
607 messages.success(request, msg)
609 response = prepare_response(request, user)
612 except ValueError, e:
613 messages.error(request, e)
614 return render_response(confirm_template_name,
615 modified_user=user if 'user' in locals(
617 context_instance=get_context(request,
620 if not request.user.is_authenticated():
621 path = quote(request.get_full_path())
622 url = request.build_absolute_uri(reverse('index'))
623 return HttpResponseRedirect(url + '?next=' + path)
624 form = EmailChangeForm(request.POST or None)
625 if request.method == 'POST' and form.is_valid():
627 ec = form.save(email_template_name, request)
628 except SendMailError, e:
630 messages.error(request, msg)
631 transaction.rollback()
632 except IntegrityError, e:
633 msg = _('There is already a pending change email request.')
634 messages.error(request, msg)
636 msg = _('Change email request has been registered succefully.\
637 You are going to receive a verification email in the new address.')
638 messages.success(request, msg)
640 return render_response(form_template_name,
642 context_instance=get_context(request,
646 @signed_terms_required
648 def group_add(request, kind_name='default'):
650 kind = GroupKind.objects.get(name=kind_name)
652 return HttpResponseBadRequest(_('No such group kind'))
654 post_save_redirect = '/im/group/%(id)s/'
655 context_processors = None
656 model, form_class = get_model_and_form_class(
658 form_class=AstakosGroupCreationForm
661 (str(r.id), r) for r in Resource.objects.select_related().all())
663 if request.method == 'POST':
664 form = form_class(request.POST, request.FILES, resources=resources)
666 new_object = form.save()
669 new_object.owners = [request.user]
671 # save quota policies
672 for (rid, uplimit) in form.resources():
677 # TODO Should I stay or should I go???
680 new_object.astakosgroupquota_set.create(
684 policies.append('%s %d' % (r, uplimit))
685 msg = _("The %(verbose_name)s was created successfully.") %\
686 {"verbose_name": model._meta.verbose_name}
687 messages.success(request, msg, fail_silently=True)
691 send_admin_notification(
692 template_name='im/group_creation_notification.txt',
695 'owner': request.user,
696 'policies': policies,
698 subject='%s alpha2 testing group creation notification' % SITENAME
700 except SendNotificationError, e:
701 messages.error(request, e, fail_silently=True)
702 return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
708 form = form_class(data, resources=resources)
710 # Create the template, context, response
711 template_name = "%s/%s_form.html" % (
712 model._meta.app_label,
713 model._meta.object_name.lower()
715 t = template_loader.get_template(template_name)
716 c = RequestContext(request, {
719 }, context_processors)
720 return HttpResponse(t.render(c))
723 @signed_terms_required
725 def group_list(request):
726 none = request.user.astakos_groups.none()
727 q = AstakosGroup.objects.raw("""
728 SELECT auth_group.id,
730 im_groupkind.name AS kindname,
732 owner.email AS groupowner,
733 (SELECT COUNT(*) FROM im_membership
734 WHERE group_id = im_astakosgroup.group_ptr_id
735 AND date_joined IS NOT NULL) AS approved_members_num,
737 SELECT date_joined FROM im_membership
738 WHERE group_id = im_astakosgroup.group_ptr_id
739 AND person_id = %s) IS NULL
740 THEN 0 ELSE 1 END) AS membership_status
742 INNER JOIN im_membership ON (
743 im_astakosgroup.group_ptr_id = im_membership.group_id)
744 INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
745 INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
746 LEFT JOIN im_astakosuser_owner ON (
747 im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
748 LEFT JOIN auth_user as owner ON (
749 im_astakosuser_owner.astakosuser_id = owner.id)
750 WHERE im_membership.person_id = %s
751 """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
752 d = defaultdict(list)
754 if request.user.email == g.groupowner:
760 fields = ('own', 'other')
762 v = globals()['%s_sorting' % f] = request.GET.get('%s_sorting' % f)
764 form = AstakosGroupSortForm({'sort_by': v})
765 if not form.is_valid():
766 globals()['%s_sorting' % f] = form.cleaned_data.get('sort_by')
767 return object_list(request, queryset=none,
768 extra_context={'is_search':False,
771 'own_sorting': own_sorting,
772 'other_sorting': other_sorting,
773 'own_page': request.GET.get('own_page', 1),
774 'other_page': request.GET.get('other_page', 1)
778 @signed_terms_required
780 def group_detail(request, group_id):
781 q = AstakosGroup.objects.select_related().filter(pk=group_id)
783 'is_member': """SELECT CASE WHEN EXISTS(
784 SELECT id FROM im_membership
785 WHERE group_id = im_astakosgroup.group_ptr_id
787 THEN 1 ELSE 0 END""" % request.user.id,
788 'is_owner': """SELECT CASE WHEN EXISTS(
789 SELECT id FROM im_astakosuser_owner
790 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
791 AND astakosuser_id = %s)
792 THEN 1 ELSE 0 END""" % request.user.id,
793 'kindname': """SELECT name FROM im_groupkind
794 WHERE id = im_astakosgroup.kind_id"""})
797 context_processors = None
801 except AstakosGroup.DoesNotExist:
802 raise Http404("No %s found matching the query" % (
803 model._meta.verbose_name))
805 update_form = AstakosGroupUpdateForm(instance=obj)
806 addmembers_form = AddGroupMembersForm()
807 if request.method == 'POST':
810 for k,v in request.POST.iteritems():
811 if k in update_form.fields:
813 if k in addmembers_form.fields:
814 addmembers_data[k] = v
815 update_data = update_data or None
816 addmembers_data = addmembers_data or None
817 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
818 addmembers_form = AddGroupMembersForm(addmembers_data)
819 if update_form.is_valid():
821 if addmembers_form.is_valid():
822 map(obj.approve_member, addmembers_form.valid_users)
823 addmembers_form = AddGroupMembersForm()
825 template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
826 t = template_loader.get_template(template_name)
827 c = RequestContext(request, {
829 }, context_processors)
832 sorting= request.GET.get('sorting')
834 form = MembersSortForm({'sort_by': sorting})
836 sorting = form.cleaned_data.get('sort_by')
838 extra_context = {'update_form': update_form,
839 'addmembers_form': addmembers_form,
840 'page': request.GET.get('page', 1),
842 for key, value in extra_context.items():
847 response = HttpResponse(t.render(c), mimetype=mimetype)
848 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
852 @signed_terms_required
854 def group_search(request, extra_context=None, **kwargs):
855 q = request.GET.get('q')
856 sorting = request.GET.get('sorting')
857 if request.method == 'GET':
858 form = AstakosGroupSearchForm({'q': q} if q else None)
860 form = AstakosGroupSearchForm(get_query(request))
862 q = form.cleaned_data['q'].strip()
864 queryset = AstakosGroup.objects.select_related()
865 queryset = queryset.filter(name__contains=q)
866 queryset = queryset.filter(approval_date__isnull=False)
867 queryset = queryset.extra(select={
868 'groupname': DB_REPLACE_GROUP_SCHEME,
869 'kindname': "im_groupkind.name",
870 'approved_members_num': """
871 SELECT COUNT(*) FROM im_membership
872 WHERE group_id = im_astakosgroup.group_ptr_id
873 AND date_joined IS NOT NULL""",
874 'membership_approval_date': """
875 SELECT date_joined FROM im_membership
876 WHERE group_id = im_astakosgroup.group_ptr_id
877 AND person_id = %s""" % request.user.id,
879 SELECT CASE WHEN EXISTS(
880 SELECT date_joined FROM im_membership
881 WHERE group_id = im_astakosgroup.group_ptr_id
883 THEN 1 ELSE 0 END""" % request.user.id,
885 SELECT CASE WHEN EXISTS(
886 SELECT id FROM im_astakosuser_owner
887 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
888 AND astakosuser_id = %s)
889 THEN 1 ELSE 0 END""" % request.user.id})
891 # TODO check sorting value
892 queryset = queryset.order_by(sorting)
894 queryset = AstakosGroup.objects.none()
898 paginate_by=PAGINATE_BY,
899 page=request.GET.get('page') or 1,
900 template_name='im/astakosgroup_list.html',
901 extra_context=dict(form=form,
906 @signed_terms_required
908 def group_all(request, extra_context=None, **kwargs):
909 q = AstakosGroup.objects.select_related()
910 q = q.filter(approval_date__isnull=False)
912 'groupname': DB_REPLACE_GROUP_SCHEME,
913 'kindname': "im_groupkind.name",
914 'approved_members_num': """
915 SELECT COUNT(*) FROM im_membership
916 WHERE group_id = im_astakosgroup.group_ptr_id
917 AND date_joined IS NOT NULL""",
918 'membership_approval_date': """
919 SELECT date_joined FROM im_membership
920 WHERE group_id = im_astakosgroup.group_ptr_id
921 AND person_id = %s""" % request.user.id,
923 SELECT CASE WHEN EXISTS(
924 SELECT date_joined FROM im_membership
925 WHERE group_id = im_astakosgroup.group_ptr_id
927 THEN 1 ELSE 0 END""" % request.user.id})
928 sorting = request.GET.get('sorting')
930 # TODO check sorting value
931 q = q.order_by(sorting)
935 paginate_by=PAGINATE_BY,
936 page=request.GET.get('page') or 1,
937 template_name='im/astakosgroup_list.html',
938 extra_context=dict(form=AstakosGroupSearchForm(),
943 @signed_terms_required
945 def group_join(request, group_id):
946 m = Membership(group_id=group_id,
948 date_requested=datetime.now())
951 post_save_redirect = reverse(
953 kwargs=dict(group_id=group_id))
954 return HttpResponseRedirect(post_save_redirect)
955 except IntegrityError, e:
957 msg = _('Failed to join group.')
958 messages.error(request, msg)
959 return group_search(request)
962 @signed_terms_required
964 def group_leave(request, group_id):
966 m = Membership.objects.select_related().get(
969 except Membership.DoesNotExist:
970 return HttpResponseBadRequest(_('Invalid membership.'))
971 if request.user in m.group.owner.all():
972 return HttpResponseForbidden(_('Owner can not leave the group.'))
973 return delete_object(
977 template_name='im/astakosgroup_list.html',
978 post_delete_redirect=reverse(
980 kwargs=dict(group_id=group_id)))
983 def handle_membership(func):
985 def wrapper(request, group_id, user_id):
987 m = Membership.objects.select_related().get(
990 except Membership.DoesNotExist:
991 return HttpResponseBadRequest(_('Invalid membership.'))
993 if request.user not in m.group.owner.all():
994 return HttpResponseForbidden(_('User is not a group owner.'))
996 return group_detail(request, group_id)
1000 @signed_terms_required
1003 def approve_member(request, membership):
1005 membership.approve()
1006 realname = membership.person.realname
1007 msg = _('%s has been successfully joined the group.' % realname)
1008 messages.success(request, msg)
1009 except BaseException, e:
1011 realname = membership.person.realname
1012 msg = _('Something went wrong during %s\'s approval.' % realname)
1013 messages.error(request, msg)
1016 @signed_terms_required
1019 def disapprove_member(request, membership):
1021 membership.disapprove()
1022 realname = membership.person.realname
1023 msg = _('%s has been successfully removed from the group.' % realname)
1024 messages.success(request, msg)
1025 except BaseException, e:
1027 msg = _('Something went wrong during %s\'s disapproval.' % realname)
1028 messages.error(request, msg)
1031 @signed_terms_required
1033 def resource_list(request):
1034 if request.method == 'POST':
1035 form = PickResourceForm(request.POST)
1037 r = form.cleaned_data.get('resource')
1039 groups = request.user.membership_set.only('group').filter(
1040 date_joined__isnull=False)
1041 groups = [g.group_id for g in groups]
1042 q = AstakosGroupQuota.objects.select_related().filter(
1043 resource=r, group__in=groups)
1045 form = PickResourceForm()
1046 q = AstakosGroupQuota.objects.none()
1047 return object_list(request, q,
1048 template_name='im/astakosuserquota_list.html',
1049 extra_context={'form': form})
1052 def group_create_list(request):
1053 form = PickResourceForm()
1054 return render_response(
1055 template='im/astakosgroup_create_list.html',
1056 context_instance=get_context(request),)
1059 @signed_terms_required
1061 def billing(request):
1063 today = datetime.today()
1064 month_last_day= calendar.monthrange(today.year, today.month)[1]
1066 start = request.POST.get('datefrom', None)
1068 today = datetime.fromtimestamp(int(start))
1069 month_last_day= calendar.monthrange(today.year, today.month)[1]
1071 start = datetime(today.year, today.month, 1).strftime("%s")
1072 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1073 r = request_billing.apply(args=('pgerakios@grnet.gr',
1079 status, data = r.result
1080 data=_clear_billing_data(data)
1082 messages.error(request, _('Service response status: %d' % status))
1084 messages.error(request, r.result)
1088 return render_response(
1089 template='im/billing.html',
1090 context_instance=get_context(request),
1092 zerodate=datetime(month=1,year=1970, day=1),
1095 month_last_day=month_last_day)
1097 def _clear_billing_data(data):
1099 # remove addcredits entries
1101 return e['serviceName'] != "addcredits"
1106 def servicefilter(service_name):
1107 service = service_name
1109 return e['serviceName'] == service
1113 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1114 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1115 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1116 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1120 @signed_terms_required
1122 def timeline(request):
1123 # data = {'entity':request.user.email}
1125 timeline_header = ()
1126 # form = TimelineForm(data)
1127 form = TimelineForm()
1128 if request.method == 'POST':
1130 form = TimelineForm(data)
1132 data = form.cleaned_data
1133 timeline_header = ('entity', 'resource',
1134 'event name', 'event date',
1135 'incremental cost', 'total cost')
1136 timeline_body = timeline_charge(
1137 data['entity'], data['resource'],
1138 data['start_date'], data['end_date'],
1139 data['details'], data['operation'])
1141 return render_response(template='im/timeline.html',
1142 context_instance=get_context(request),
1144 timeline_header=timeline_header,
1145 timeline_body=timeline_body)