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,
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:
754 return object_list(request, queryset=none,
755 extra_context={'is_search':False,
758 'own_sorting': request.GET.get('own_sorting'),
759 'own_page': request.GET.get('own_page', 1),
760 'other_sorting': request.GET.get('other_sorting'),
761 'other_page': request.GET.get('other_page', 1),
765 @signed_terms_required
767 def group_detail(request, group_id):
768 q = AstakosGroup.objects.select_related().filter(pk=group_id)
770 'is_member': """SELECT CASE WHEN EXISTS(
771 SELECT id FROM im_membership
772 WHERE group_id = im_astakosgroup.group_ptr_id
774 THEN 1 ELSE 0 END""" % request.user.id,
775 'is_owner': """SELECT CASE WHEN EXISTS(
776 SELECT id FROM im_astakosuser_owner
777 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
778 AND astakosuser_id = %s)
779 THEN 1 ELSE 0 END""" % request.user.id,
780 'kindname': """SELECT name FROM im_groupkind
781 WHERE id = im_astakosgroup.kind_id"""})
784 context_processors = None
788 except AstakosGroup.DoesNotExist:
789 raise Http404("No %s found matching the query" % (
790 model._meta.verbose_name))
792 update_form = AstakosGroupUpdateForm(instance=obj)
793 addmembers_form = AddGroupMembersForm()
794 if request.method == 'POST':
797 for k,v in request.POST.iteritems():
798 if k in update_form.fields:
800 if k in addmembers_form.fields:
801 addmembers_data[k] = v
802 update_data = update_data or None
803 addmembers_data = addmembers_data or None
804 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
805 addmembers_form = AddGroupMembersForm(addmembers_data)
806 if update_form.is_valid():
808 if addmembers_form.is_valid():
809 map(obj.approve_member, addmembers_form.valid_users)
810 addmembers_form = AddGroupMembersForm()
812 template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
813 t = template_loader.get_template(template_name)
814 c = RequestContext(request, {
816 }, context_processors)
817 extra_context = {'update_form': update_form,
818 'addmembers_form': addmembers_form,
819 'page': request.GET.get('page', 1),
820 'sorting': request.GET.get('sorting')}
821 for key, value in extra_context.items():
826 response = HttpResponse(t.render(c), mimetype=mimetype)
827 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
831 @signed_terms_required
833 def group_search(request, extra_context=None, **kwargs):
834 q = request.GET.get('q')
835 sorting = request.GET.get('sorting')
836 if request.method == 'GET':
837 form = AstakosGroupSearchForm({'q': q} if q else None)
839 form = AstakosGroupSearchForm(get_query(request))
841 q = form.cleaned_data['q'].strip()
843 queryset = AstakosGroup.objects.select_related()
844 queryset = queryset.filter(name__contains=q)
845 queryset = queryset.filter(approval_date__isnull=False)
846 queryset = queryset.extra(select={
847 'groupname': DB_REPLACE_GROUP_SCHEME,
848 'kindname': "im_groupkind.name",
849 'approved_members_num': """
850 SELECT COUNT(*) FROM im_membership
851 WHERE group_id = im_astakosgroup.group_ptr_id
852 AND date_joined IS NOT NULL""",
853 'membership_approval_date': """
854 SELECT date_joined FROM im_membership
855 WHERE group_id = im_astakosgroup.group_ptr_id
856 AND person_id = %s""" % request.user.id,
858 SELECT CASE WHEN EXISTS(
859 SELECT date_joined FROM im_membership
860 WHERE group_id = im_astakosgroup.group_ptr_id
862 THEN 1 ELSE 0 END""" % request.user.id,
864 SELECT CASE WHEN EXISTS(
865 SELECT id FROM im_astakosuser_owner
866 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
867 AND astakosuser_id = %s)
868 THEN 1 ELSE 0 END""" % request.user.id})
870 # TODO check sorting value
871 queryset = queryset.order_by(sorting)
873 queryset = AstakosGroup.objects.none()
877 paginate_by=PAGINATE_BY,
878 page=request.GET.get('page') or 1,
879 template_name='im/astakosgroup_list.html',
880 extra_context=dict(form=form,
885 @signed_terms_required
887 def group_all(request, extra_context=None, **kwargs):
888 q = AstakosGroup.objects.select_related()
889 q = q.filter(approval_date__isnull=False)
891 'groupname': DB_REPLACE_GROUP_SCHEME,
892 'kindname': "im_groupkind.name",
893 'approved_members_num': """
894 SELECT COUNT(*) FROM im_membership
895 WHERE group_id = im_astakosgroup.group_ptr_id
896 AND date_joined IS NOT NULL""",
897 'membership_approval_date': """
898 SELECT date_joined FROM im_membership
899 WHERE group_id = im_astakosgroup.group_ptr_id
900 AND person_id = %s""" % request.user.id,
902 SELECT CASE WHEN EXISTS(
903 SELECT date_joined FROM im_membership
904 WHERE group_id = im_astakosgroup.group_ptr_id
906 THEN 1 ELSE 0 END""" % request.user.id})
907 sorting = request.GET.get('sorting')
909 # TODO check sorting value
910 q = q.order_by(sorting)
914 paginate_by=PAGINATE_BY,
915 page=request.GET.get('page') or 1,
916 template_name='im/astakosgroup_list.html',
917 extra_context=dict(form=AstakosGroupSearchForm(),
922 @signed_terms_required
924 def group_join(request, group_id):
925 m = Membership(group_id=group_id,
927 date_requested=datetime.now())
930 post_save_redirect = reverse(
932 kwargs=dict(group_id=group_id))
933 return HttpResponseRedirect(post_save_redirect)
934 except IntegrityError, e:
936 msg = _('Failed to join group.')
937 messages.error(request, msg)
938 return group_search(request)
941 @signed_terms_required
943 def group_leave(request, group_id):
945 m = Membership.objects.select_related().get(
948 except Membership.DoesNotExist:
949 return HttpResponseBadRequest(_('Invalid membership.'))
950 if request.user in m.group.owner.all():
951 return HttpResponseForbidden(_('Owner can not leave the group.'))
952 return delete_object(
956 template_name='im/astakosgroup_list.html',
957 post_delete_redirect=reverse(
959 kwargs=dict(group_id=group_id)))
962 def handle_membership(func):
964 def wrapper(request, group_id, user_id):
966 m = Membership.objects.select_related().get(
969 except Membership.DoesNotExist:
970 return HttpResponseBadRequest(_('Invalid membership.'))
972 if request.user not in m.group.owner.all():
973 return HttpResponseForbidden(_('User is not a group owner.'))
975 return group_detail(request, group_id)
979 @signed_terms_required
982 def approve_member(request, membership):
985 realname = membership.person.realname
986 msg = _('%s has been successfully joined the group.' % realname)
987 messages.success(request, msg)
988 except BaseException, e:
990 realname = membership.person.realname
991 msg = _('Something went wrong during %s\'s approval.' % realname)
992 messages.error(request, msg)
995 @signed_terms_required
998 def disapprove_member(request, membership):
1000 membership.disapprove()
1001 realname = membership.person.realname
1002 msg = _('%s has been successfully removed from the group.' % realname)
1003 messages.success(request, msg)
1004 except BaseException, e:
1006 msg = _('Something went wrong during %s\'s disapproval.' % realname)
1007 messages.error(request, msg)
1010 @signed_terms_required
1012 def resource_list(request):
1013 return render_response(
1014 template='im/astakosuserquota_list.html',
1015 context_instance=get_context(request),
1016 quota=request.user.quota)
1019 def group_create_list(request):
1020 return render_response(
1021 template='im/astakosgroup_create_list.html',
1022 context_instance=get_context(request),)
1025 @signed_terms_required
1027 def billing(request):
1029 today = datetime.today()
1030 month_last_day= calendar.monthrange(today.year, today.month)[1]
1032 start = request.POST.get('datefrom', None)
1034 today = datetime.fromtimestamp(int(start))
1035 month_last_day= calendar.monthrange(today.year, today.month)[1]
1037 start = datetime(today.year, today.month, 1).strftime("%s")
1038 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1039 r = request_billing.apply(args=('pgerakios@grnet.gr',
1045 status, data = r.result
1046 data=_clear_billing_data(data)
1048 messages.error(request, _('Service response status: %d' % status))
1050 messages.error(request, r.result)
1054 return render_response(
1055 template='im/billing.html',
1056 context_instance=get_context(request),
1058 zerodate=datetime(month=1,year=1970, day=1),
1061 month_last_day=month_last_day)
1063 def _clear_billing_data(data):
1065 # remove addcredits entries
1067 return e['serviceName'] != "addcredits"
1072 def servicefilter(service_name):
1073 service = service_name
1075 return e['serviceName'] == service
1079 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1080 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1081 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1082 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])