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)
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 from astakos.im.functions import (send_feedback, SendMailError,
74 invite as invite_func, logout as auth_logout,
75 activate as activate_func,
76 switch_account_to_shibboleth,
77 send_admin_notification,
78 SendNotificationError)
79 from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, SITENAME,
80 LOGOUT_NEXT, LOGGING_LEVEL, PAGINATE_BY)
81 from astakos.im.tasks import request_billing
83 logger = logging.getLogger(__name__)
86 DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
89 def render_response(template, tab=None, status=200, reset_cookie=False,
90 context_instance=None, **kwargs):
92 Calls ``django.template.loader.render_to_string`` with an additional ``tab``
93 keyword argument and returns an ``django.http.HttpResponse`` with the
97 tab = template.partition('_')[0].partition('.html')[0]
98 kwargs.setdefault('tab', tab)
99 html = template_loader.render_to_string(
100 template, kwargs, context_instance=context_instance)
101 response = HttpResponse(html, status=status)
103 set_cookie(response, context_instance['request'].user)
107 def requires_anonymous(func):
109 Decorator checkes whether the request.user is not Anonymous and in that case
110 redirects to `logout`.
113 def wrapper(request, *args):
114 if not request.user.is_anonymous():
115 next = urlencode({'next': request.build_absolute_uri()})
116 logout_uri = reverse(logout) + '?' + next
117 return HttpResponseRedirect(logout_uri)
118 return func(request, *args)
122 def signed_terms_required(func):
124 Decorator checkes whether the request.user is Anonymous and in that case
125 redirects to `logout`.
128 def wrapper(request, *args, **kwargs):
129 if request.user.is_authenticated() and not request.user.signed_terms:
130 params = urlencode({'next': request.build_absolute_uri(),
132 terms_uri = reverse('latest_terms') + '?' + params
133 return HttpResponseRedirect(terms_uri)
134 return func(request, *args, **kwargs)
138 @signed_terms_required
139 def index(request, login_template_name='im/login.html', extra_context=None):
141 If there is logged on user renders the profile page otherwise renders login page.
145 ``login_template_name``
146 A custom login template to use. This is optional; if not specified,
147 this will default to ``im/login.html``.
149 ``profile_template_name``
150 A custom profile template to use. This is optional; if not specified,
151 this will default to ``im/profile.html``.
154 An dictionary of variables to add to the template context.
158 im/profile.html or im/login.html or ``template_name`` keyword argument.
161 template_name = login_template_name
162 if request.user.is_authenticated():
163 return HttpResponseRedirect(reverse('edit_profile'))
164 return render_response(template_name,
165 login_form=LoginForm(request=request),
166 context_instance=get_context(request, extra_context))
170 @signed_terms_required
171 @transaction.commit_manually
172 def invite(request, template_name='im/invitations.html', extra_context=None):
174 Allows a user to invite somebody else.
176 In case of GET request renders a form for providing the invitee information.
177 In case of POST checks whether the user has not run out of invitations and then
178 sends an invitation email to singup to the service.
180 The view uses commit_manually decorator in order to ensure the number of the
181 user invitations is going to be updated only if the email has been successfully sent.
183 If the user isn't logged in, redirects to settings.LOGIN_URL.
188 A custom template to use. This is optional; if not specified,
189 this will default to ``im/invitations.html``.
192 An dictionary of variables to add to the template context.
196 im/invitations.html or ``template_name`` keyword argument.
200 The view expectes the following settings are defined:
202 * LOGIN_URL: login uri
203 * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
207 form = InvitationForm()
209 inviter = request.user
210 if request.method == 'POST':
211 form = InvitationForm(request.POST)
212 if inviter.invitations > 0:
215 invitation = form.save()
216 invite_func(invitation, inviter)
217 message = _('Invitation sent to %s' % invitation.username)
218 messages.success(request, message)
219 except SendMailError, e:
221 messages.error(request, message)
222 transaction.rollback()
223 except BaseException, e:
224 message = _('Something went wrong.')
225 messages.error(request, message)
227 transaction.rollback()
231 message = _('No invitations left')
232 messages.error(request, message)
234 sent = [{'email': inv.username,
235 'realname': inv.realname,
236 'is_consumed': inv.is_consumed}
237 for inv in request.user.invitations_sent.all()]
238 kwargs = {'inviter': inviter,
240 context = get_context(request, extra_context, **kwargs)
241 return render_response(template_name,
242 invitation_form=form,
243 context_instance=context)
247 @signed_terms_required
248 def edit_profile(request, template_name='im/profile.html', extra_context=None):
250 Allows a user to edit his/her profile.
252 In case of GET request renders a form for displaying the user information.
253 In case of POST updates the user informantion and redirects to ``next``
254 url parameter if exists.
256 If the user isn't logged in, redirects to settings.LOGIN_URL.
261 A custom template to use. This is optional; if not specified,
262 this will default to ``im/profile.html``.
265 An dictionary of variables to add to the template context.
269 im/profile.html or ``template_name`` keyword argument.
273 The view expectes the following settings are defined:
275 * LOGIN_URL: login uri
277 extra_context = extra_context or {}
278 form = ProfileForm(instance=request.user)
279 extra_context['next'] = request.GET.get('next')
281 if request.method == 'POST':
282 form = ProfileForm(request.POST, instance=request.user)
285 prev_token = request.user.auth_token
287 reset_cookie = user.auth_token != prev_token
288 form = ProfileForm(instance=user)
289 next = request.POST.get('next')
291 return redirect(next)
292 msg = _('Profile has been updated successfully')
293 messages.success(request, msg)
294 except ValueError, ve:
295 messages.success(request, ve)
296 elif request.method == "GET":
297 if not request.user.is_verified:
298 request.user.is_verified = True
300 return render_response(template_name,
301 reset_cookie=reset_cookie,
303 context_instance=get_context(request,
307 @transaction.commit_manually
308 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
310 Allows a user to create a local account.
312 In case of GET request renders a form for entering the user information.
313 In case of POST handles the signup.
315 The user activation will be delegated to the backend specified by the ``backend`` keyword argument
316 if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
317 if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
318 (see activation_backends);
320 Upon successful user creation, if ``next`` url parameter is present the user is redirected there
321 otherwise renders the same page with a success message.
323 On unsuccessful creation, renders ``template_name`` with an error message.
328 A custom template to render. This is optional;
329 if not specified, this will default to ``im/signup.html``.
332 A custom template to render in case of success. This is optional;
333 if not specified, this will default to ``im/signup_complete.html``.
336 An dictionary of variables to add to the template context.
340 im/signup.html or ``template_name`` keyword argument.
341 im/signup_complete.html or ``on_success`` keyword argument.
343 if request.user.is_authenticated():
344 return HttpResponseRedirect(reverse('edit_profile'))
346 provider = get_query(request).get('provider', 'local')
349 backend = get_backend(request)
350 form = backend.get_signup_form(provider)
352 form = SimpleBackend(request).get_signup_form(provider)
353 messages.error(request, e)
354 if request.method == 'POST':
356 user = form.save(commit=False)
358 result = backend.handle_activation(user)
359 status = messages.SUCCESS
360 message = result.message
362 if 'additional_email' in form.cleaned_data:
363 additional_email = form.cleaned_data['additional_email']
364 if additional_email != user.email:
365 user.additionalmail_set.create(email=additional_email)
366 msg = 'Additional email: %s saved for user %s.' % (
367 additional_email, user.email)
368 logger.log(LOGGING_LEVEL, msg)
369 if user and user.is_active:
370 next = request.POST.get('next', '')
372 return prepare_response(request, user, next=next)
373 messages.add_message(request, status, message)
375 return render_response(on_success,
376 context_instance=get_context(request, extra_context))
377 except SendMailError, e:
379 messages.error(request, message)
380 transaction.rollback()
381 except BaseException, e:
382 message = _('Something went wrong.')
383 messages.error(request, message)
385 transaction.rollback()
386 return render_response(template_name,
389 context_instance=get_context(request, extra_context))
393 @signed_terms_required
394 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
396 Allows a user to send feedback.
398 In case of GET request renders a form for providing the feedback information.
399 In case of POST sends an email to support team.
401 If the user isn't logged in, redirects to settings.LOGIN_URL.
406 A custom template to use. This is optional; if not specified,
407 this will default to ``im/feedback.html``.
410 An dictionary of variables to add to the template context.
414 im/signup.html or ``template_name`` keyword argument.
418 * LOGIN_URL: login uri
419 * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
421 if request.method == 'GET':
422 form = FeedbackForm()
423 if request.method == 'POST':
425 return HttpResponse('Unauthorized', status=401)
427 form = FeedbackForm(request.POST)
429 msg = form.cleaned_data['feedback_msg']
430 data = form.cleaned_data['feedback_data']
432 send_feedback(msg, data, request.user, email_template_name)
433 except SendMailError, e:
434 messages.error(request, message)
436 message = _('Feedback successfully sent')
437 messages.success(request, message)
438 return render_response(template_name,
440 context_instance=get_context(request, extra_context))
443 @signed_terms_required
444 def logout(request, template='registration/logged_out.html', extra_context=None):
446 Wraps `django.contrib.auth.logout` and delete the cookie.
448 response = HttpResponse()
449 if request.user.is_authenticated():
450 email = request.user.email
452 response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
453 msg = 'Cookie deleted for %s' % email
454 logger.log(LOGGING_LEVEL, msg)
455 next = request.GET.get('next')
457 response['Location'] = next
458 response.status_code = 302
461 response['Location'] = LOGOUT_NEXT
462 response.status_code = 301
464 messages.success(request, _('You have successfully logged out.'))
465 context = get_context(request, extra_context)
466 response.write(template_loader.render_to_string(template, context_instance=context))
470 @transaction.commit_manually
471 def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
473 Activates the user identified by the ``auth`` request parameter, sends a welcome email
474 and renews the user token.
476 The view uses commit_manually decorator in order to ensure the user state will be updated
477 only if the email will be send successfully.
479 token = request.GET.get('auth')
480 next = request.GET.get('next')
482 user = AstakosUser.objects.get(auth_token=token)
483 except AstakosUser.DoesNotExist:
484 return HttpResponseBadRequest(_('No such user'))
487 message = _('Account already active.')
488 messages.error(request, message)
489 return index(request)
492 local_user = AstakosUser.objects.get(
497 except AstakosUser.DoesNotExist:
501 greeting_email_template_name,
502 helpdesk_email_template_name,
505 response = prepare_response(request, user, next, renew=True)
508 except SendMailError, e:
510 messages.error(request, message)
511 transaction.rollback()
512 return index(request)
513 except BaseException, e:
514 message = _('Something went wrong.')
515 messages.error(request, message)
517 transaction.rollback()
518 return index(request)
521 user = switch_account_to_shibboleth(
524 greeting_email_template_name
526 response = prepare_response(request, user, next, renew=True)
529 except SendMailError, e:
531 messages.error(request, message)
532 transaction.rollback()
533 return index(request)
534 except BaseException, e:
535 message = _('Something went wrong.')
536 messages.error(request, message)
538 transaction.rollback()
539 return index(request)
542 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
547 term = ApprovalTerms.objects.order_by('-id')[0]
552 term = ApprovalTerms.objects.get(id=term_id)
553 except ApprovalTerms.DoesNotExist, e:
557 return HttpResponseRedirect(reverse('index'))
558 f = open(term.location, 'r')
561 if request.method == 'POST':
562 next = request.POST.get('next')
564 next = reverse('index')
565 form = SignApprovalTermsForm(request.POST, instance=request.user)
566 if not form.is_valid():
567 return render_response(template_name,
569 approval_terms_form=form,
570 context_instance=get_context(request, extra_context))
572 return HttpResponseRedirect(next)
575 if request.user.is_authenticated() and not request.user.signed_terms:
576 form = SignApprovalTermsForm(instance=request.user)
577 return render_response(template_name,
579 approval_terms_form=form,
580 context_instance=get_context(request, extra_context))
583 @signed_terms_required
584 def change_password(request):
585 return password_change(request,
586 post_change_redirect=reverse('edit_profile'),
587 password_change_form=ExtendedPasswordChangeForm)
590 @signed_terms_required
592 @transaction.commit_manually
593 def change_email(request, activation_key=None,
594 email_template_name='registration/email_change_email.txt',
595 form_template_name='registration/email_change_form.html',
596 confirm_template_name='registration/email_change_done.html',
600 user = EmailChange.objects.change_email(activation_key)
601 if request.user.is_authenticated() and request.user == user:
602 msg = _('Email changed successfully.')
603 messages.success(request, msg)
605 response = prepare_response(request, user)
608 except ValueError, e:
609 messages.error(request, e)
610 return render_response(confirm_template_name,
611 modified_user=user if 'user' in locals(
613 context_instance=get_context(request,
616 if not request.user.is_authenticated():
617 path = quote(request.get_full_path())
618 url = request.build_absolute_uri(reverse('index'))
619 return HttpResponseRedirect(url + '?next=' + path)
620 form = EmailChangeForm(request.POST or None)
621 if request.method == 'POST' and form.is_valid():
623 ec = form.save(email_template_name, request)
624 except SendMailError, e:
626 messages.error(request, msg)
627 transaction.rollback()
628 except IntegrityError, e:
629 msg = _('There is already a pending change email request.')
630 messages.error(request, msg)
632 msg = _('Change email request has been registered succefully.\
633 You are going to receive a verification email in the new address.')
634 messages.success(request, msg)
636 return render_response(form_template_name,
638 context_instance=get_context(request,
642 @signed_terms_required
644 def group_add(request, kind_name='default'):
646 kind = GroupKind.objects.get(name=kind_name)
648 return HttpResponseBadRequest(_('No such group kind'))
650 post_save_redirect = '/im/group/%(id)s/'
651 context_processors = None
652 model, form_class = get_model_and_form_class(
654 form_class=AstakosGroupCreationForm
657 (str(r.id), r) for r in Resource.objects.select_related().all())
659 if request.method == 'POST':
660 form = form_class(request.POST, request.FILES, resources=resources)
662 new_object = form.save()
665 new_object.owners = [request.user]
667 # save quota policies
668 for (rid, uplimit) in form.resources():
673 # TODO Should I stay or should I go???
676 new_object.astakosgroupquota_set.create(
680 policies.append('%s %d' % (r, uplimit))
681 msg = _("The %(verbose_name)s was created successfully.") %\
682 {"verbose_name": model._meta.verbose_name}
683 messages.success(request, msg, fail_silently=True)
687 send_admin_notification(
688 template_name='im/group_creation_notification.txt',
691 'owner': request.user,
692 'policies': policies,
694 subject='%s alpha2 testing group creation notification' % SITENAME
696 except SendNotificationError, e:
697 messages.error(request, e, fail_silently=True)
698 return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
704 form = form_class(data, resources=resources)
706 # Create the template, context, response
707 template_name = "%s/%s_form.html" % (
708 model._meta.app_label,
709 model._meta.object_name.lower()
711 t = template_loader.get_template(template_name)
712 c = RequestContext(request, {
715 }, context_processors)
716 return HttpResponse(t.render(c))
719 @signed_terms_required
721 def group_list(request):
722 none = request.user.astakos_groups.none()
723 q = AstakosGroup.objects.raw("""
724 SELECT auth_group.id,
726 im_groupkind.name AS kindname,
728 owner.email AS groupowner,
729 (SELECT COUNT(*) FROM im_membership
730 WHERE group_id = im_astakosgroup.group_ptr_id
731 AND date_joined IS NOT NULL) AS approved_members_num,
733 SELECT date_joined FROM im_membership
734 WHERE group_id = im_astakosgroup.group_ptr_id
735 AND person_id = %s) IS NULL
736 THEN 0 ELSE 1 END) AS membership_status
738 INNER JOIN im_membership ON (
739 im_astakosgroup.group_ptr_id = im_membership.group_id)
740 INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
741 INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
742 LEFT JOIN im_astakosuser_owner ON (
743 im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
744 LEFT JOIN auth_user as owner ON (
745 im_astakosuser_owner.astakosuser_id = owner.id)
746 WHERE im_membership.person_id = %s
747 """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
748 d = defaultdict(list)
750 if request.user.email == g.groupowner:
756 fields = ('own', 'other')
758 v = globals()['%s_sorting' % f] = request.GET.get('%s_sorting' % f)
760 form = AstakosGroupSortForm({'sort_by': v})
761 if not form.is_valid():
762 globals()['%s_sorting' % f] = form.cleaned_data.get('sort_by')
763 return object_list(request, queryset=none,
764 extra_context={'is_search':False,
767 'own_sorting': own_sorting,
768 'other_sorting': other_sorting,
769 'own_page': request.GET.get('own_page', 1),
770 'other_page': request.GET.get('other_page', 1)
774 @signed_terms_required
776 def group_detail(request, group_id):
777 q = AstakosGroup.objects.select_related().filter(pk=group_id)
779 'is_member': """SELECT CASE WHEN EXISTS(
780 SELECT id FROM im_membership
781 WHERE group_id = im_astakosgroup.group_ptr_id
783 THEN 1 ELSE 0 END""" % request.user.id,
784 'is_owner': """SELECT CASE WHEN EXISTS(
785 SELECT id FROM im_astakosuser_owner
786 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
787 AND astakosuser_id = %s)
788 THEN 1 ELSE 0 END""" % request.user.id,
789 'kindname': """SELECT name FROM im_groupkind
790 WHERE id = im_astakosgroup.kind_id"""})
793 context_processors = None
797 except AstakosGroup.DoesNotExist:
798 raise Http404("No %s found matching the query" % (
799 model._meta.verbose_name))
801 update_form = AstakosGroupUpdateForm(instance=obj)
802 addmembers_form = AddGroupMembersForm()
803 if request.method == 'POST':
806 for k,v in request.POST.iteritems():
807 if k in update_form.fields:
809 if k in addmembers_form.fields:
810 addmembers_data[k] = v
811 update_data = update_data or None
812 addmembers_data = addmembers_data or None
813 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
814 addmembers_form = AddGroupMembersForm(addmembers_data)
815 if update_form.is_valid():
817 if addmembers_form.is_valid():
818 map(obj.approve_member, addmembers_form.valid_users)
819 addmembers_form = AddGroupMembersForm()
821 template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
822 t = template_loader.get_template(template_name)
823 c = RequestContext(request, {
825 }, context_processors)
828 sorting= request.GET.get('sorting')
830 form = MembersSortForm({'sort_by': sorting})
832 sorting = form.cleaned_data.get('sort_by')
834 extra_context = {'update_form': update_form,
835 'addmembers_form': addmembers_form,
836 'page': request.GET.get('page', 1),
838 for key, value in extra_context.items():
843 response = HttpResponse(t.render(c), mimetype=mimetype)
844 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
848 @signed_terms_required
850 def group_search(request, extra_context=None, **kwargs):
851 q = request.GET.get('q')
852 sorting = request.GET.get('sorting')
853 if request.method == 'GET':
854 form = AstakosGroupSearchForm({'q': q} if q else None)
856 form = AstakosGroupSearchForm(get_query(request))
858 q = form.cleaned_data['q'].strip()
860 queryset = AstakosGroup.objects.select_related()
861 queryset = queryset.filter(name__contains=q)
862 queryset = queryset.filter(approval_date__isnull=False)
863 queryset = queryset.extra(select={
864 'groupname': DB_REPLACE_GROUP_SCHEME,
865 'kindname': "im_groupkind.name",
866 'approved_members_num': """
867 SELECT COUNT(*) FROM im_membership
868 WHERE group_id = im_astakosgroup.group_ptr_id
869 AND date_joined IS NOT NULL""",
870 'membership_approval_date': """
871 SELECT date_joined FROM im_membership
872 WHERE group_id = im_astakosgroup.group_ptr_id
873 AND person_id = %s""" % request.user.id,
875 SELECT CASE WHEN EXISTS(
876 SELECT date_joined FROM im_membership
877 WHERE group_id = im_astakosgroup.group_ptr_id
879 THEN 1 ELSE 0 END""" % request.user.id,
881 SELECT CASE WHEN EXISTS(
882 SELECT id FROM im_astakosuser_owner
883 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
884 AND astakosuser_id = %s)
885 THEN 1 ELSE 0 END""" % request.user.id})
887 # TODO check sorting value
888 queryset = queryset.order_by(sorting)
890 queryset = AstakosGroup.objects.none()
894 paginate_by=PAGINATE_BY,
895 page=request.GET.get('page') or 1,
896 template_name='im/astakosgroup_list.html',
897 extra_context=dict(form=form,
902 @signed_terms_required
904 def group_all(request, extra_context=None, **kwargs):
905 q = AstakosGroup.objects.select_related()
906 q = q.filter(approval_date__isnull=False)
908 'groupname': DB_REPLACE_GROUP_SCHEME,
909 'kindname': "im_groupkind.name",
910 'approved_members_num': """
911 SELECT COUNT(*) FROM im_membership
912 WHERE group_id = im_astakosgroup.group_ptr_id
913 AND date_joined IS NOT NULL""",
914 'membership_approval_date': """
915 SELECT date_joined FROM im_membership
916 WHERE group_id = im_astakosgroup.group_ptr_id
917 AND person_id = %s""" % request.user.id,
919 SELECT CASE WHEN EXISTS(
920 SELECT date_joined FROM im_membership
921 WHERE group_id = im_astakosgroup.group_ptr_id
923 THEN 1 ELSE 0 END""" % request.user.id})
924 sorting = request.GET.get('sorting')
926 # TODO check sorting value
927 q = q.order_by(sorting)
931 paginate_by=PAGINATE_BY,
932 page=request.GET.get('page') or 1,
933 template_name='im/astakosgroup_list.html',
934 extra_context=dict(form=AstakosGroupSearchForm(),
939 @signed_terms_required
941 def group_join(request, group_id):
942 m = Membership(group_id=group_id,
944 date_requested=datetime.now())
947 post_save_redirect = reverse(
949 kwargs=dict(group_id=group_id))
950 return HttpResponseRedirect(post_save_redirect)
951 except IntegrityError, e:
953 msg = _('Failed to join group.')
954 messages.error(request, msg)
955 return group_search(request)
958 @signed_terms_required
960 def group_leave(request, group_id):
962 m = Membership.objects.select_related().get(
965 except Membership.DoesNotExist:
966 return HttpResponseBadRequest(_('Invalid membership.'))
967 if request.user in m.group.owner.all():
968 return HttpResponseForbidden(_('Owner can not leave the group.'))
969 return delete_object(
973 template_name='im/astakosgroup_list.html',
974 post_delete_redirect=reverse(
976 kwargs=dict(group_id=group_id)))
979 def handle_membership(func):
981 def wrapper(request, group_id, user_id):
983 m = Membership.objects.select_related().get(
986 except Membership.DoesNotExist:
987 return HttpResponseBadRequest(_('Invalid membership.'))
989 if request.user not in m.group.owner.all():
990 return HttpResponseForbidden(_('User is not a group owner.'))
992 return group_detail(request, group_id)
996 @signed_terms_required
999 def approve_member(request, membership):
1001 membership.approve()
1002 realname = membership.person.realname
1003 msg = _('%s has been successfully joined the group.' % realname)
1004 messages.success(request, msg)
1005 except BaseException, e:
1007 realname = membership.person.realname
1008 msg = _('Something went wrong during %s\'s approval.' % realname)
1009 messages.error(request, msg)
1012 @signed_terms_required
1015 def disapprove_member(request, membership):
1017 membership.disapprove()
1018 realname = membership.person.realname
1019 msg = _('%s has been successfully removed from the group.' % realname)
1020 messages.success(request, msg)
1021 except BaseException, e:
1023 msg = _('Something went wrong during %s\'s disapproval.' % realname)
1024 messages.error(request, msg)
1027 @signed_terms_required
1029 def resource_list(request):
1030 return render_response(
1031 template='im/astakosuserquota_list.html',
1032 context_instance=get_context(request),
1033 quota=request.user.quota)
1036 def group_create_list(request):
1037 return render_response(
1038 template='im/astakosgroup_create_list.html',
1039 context_instance=get_context(request),)
1042 @signed_terms_required
1044 def billing(request):
1046 today = datetime.today()
1047 month_last_day= calendar.monthrange(today.year, today.month)[1]
1049 start = request.POST.get('datefrom', None)
1051 today = datetime.fromtimestamp(int(start))
1052 month_last_day= calendar.monthrange(today.year, today.month)[1]
1054 start = datetime(today.year, today.month, 1).strftime("%s")
1055 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1056 r = request_billing.apply(args=('pgerakios@grnet.gr',
1062 status, data = r.result
1063 data=_clear_billing_data(data)
1065 messages.error(request, _('Service response status: %d' % status))
1067 messages.error(request, r.result)
1071 return render_response(
1072 template='im/billing.html',
1073 context_instance=get_context(request),
1075 zerodate=datetime(month=1,year=1970, day=1),
1078 month_last_day=month_last_day)
1080 def _clear_billing_data(data):
1082 # remove addcredits entries
1084 return e['serviceName'] != "addcredits"
1089 def servicefilter(service_name):
1090 service = service_name
1092 return e['serviceName'] == service
1096 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1097 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1098 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1099 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])