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.db.models.fields import DateTimeField
51 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
52 HttpResponseRedirect, HttpResponseBadRequest, Http404
53 from django.shortcuts import redirect
54 from django.template import RequestContext, loader as template_loader
55 from django.utils.http import urlencode
56 from django.utils.translation import ugettext as _
57 from django.views.generic.create_update import (create_object, delete_object,
58 get_model_and_form_class)
59 from django.views.generic.list_detail import object_list, object_detail
60 from django.http import HttpResponseBadRequest
61 from django.core.paginator import Paginator, InvalidPage
62 from django.core.xheaders import populate_xheaders
64 from astakos.im.models import (
65 AstakosUser, ApprovalTerms, AstakosGroup, Resource,
66 EmailChange, GroupKind, Membership)
67 from astakos.im.activation_backends import get_backend, SimpleBackend
68 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
69 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
70 FeedbackForm, SignApprovalTermsForm,
71 ExtendedPasswordChangeForm, EmailChangeForm,
72 AstakosGroupCreationForm, AstakosGroupSearchForm,
73 AstakosGroupUpdateForm, AddGroupMembersForm,
75 from astakos.im.functions import (send_feedback, SendMailError,
76 invite as invite_func, logout as auth_logout,
77 activate as activate_func,
78 switch_account_to_shibboleth,
79 send_admin_notification,
80 SendNotificationError)
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(loader.render_to_string(template, context_instance=context))
473 @transaction.commit_manually
474 def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
476 Activates the user identified by the ``auth`` request parameter, sends a welcome email
477 and renews the user token.
479 The view uses commit_manually decorator in order to ensure the user state will be updated
480 only if the email will be send successfully.
482 token = request.GET.get('auth')
483 next = request.GET.get('next')
485 user = AstakosUser.objects.get(auth_token=token)
486 except AstakosUser.DoesNotExist:
487 return HttpResponseBadRequest(_('No such user'))
490 message = _('Account already active.')
491 messages.error(request, message)
492 return index(request)
495 local_user = AstakosUser.objects.get(
500 except AstakosUser.DoesNotExist:
504 greeting_email_template_name,
505 helpdesk_email_template_name,
508 response = prepare_response(request, user, next, renew=True)
511 except SendMailError, e:
513 messages.error(request, message)
514 transaction.rollback()
515 return index(request)
516 except BaseException, e:
517 message = _('Something went wrong.')
518 messages.error(request, message)
520 transaction.rollback()
521 return index(request)
524 user = switch_account_to_shibboleth(
527 greeting_email_template_name
529 response = prepare_response(request, user, next, renew=True)
532 except SendMailError, e:
534 messages.error(request, message)
535 transaction.rollback()
536 return index(request)
537 except BaseException, e:
538 message = _('Something went wrong.')
539 messages.error(request, message)
541 transaction.rollback()
542 return index(request)
545 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
550 term = ApprovalTerms.objects.order_by('-id')[0]
555 term = ApprovalTerms.objects.get(id=term_id)
556 except ApprovalTerms.DoesNotExist, e:
560 return HttpResponseRedirect(reverse('index'))
561 f = open(term.location, 'r')
564 if request.method == 'POST':
565 next = request.POST.get('next')
567 next = reverse('index')
568 form = SignApprovalTermsForm(request.POST, instance=request.user)
569 if not form.is_valid():
570 return render_response(template_name,
572 approval_terms_form=form,
573 context_instance=get_context(request, extra_context))
575 return HttpResponseRedirect(next)
578 if request.user.is_authenticated() and not request.user.signed_terms:
579 form = SignApprovalTermsForm(instance=request.user)
580 return render_response(template_name,
582 approval_terms_form=form,
583 context_instance=get_context(request, extra_context))
586 @signed_terms_required
587 def change_password(request):
588 return password_change(request,
589 post_change_redirect=reverse('edit_profile'),
590 password_change_form=ExtendedPasswordChangeForm)
593 @signed_terms_required
595 @transaction.commit_manually
596 def change_email(request, activation_key=None,
597 email_template_name='registration/email_change_email.txt',
598 form_template_name='registration/email_change_form.html',
599 confirm_template_name='registration/email_change_done.html',
603 user = EmailChange.objects.change_email(activation_key)
604 if request.user.is_authenticated() and request.user == user:
605 msg = _('Email changed successfully.')
606 messages.success(request, msg)
608 response = prepare_response(request, user)
611 except ValueError, e:
612 messages.error(request, e)
613 return render_response(confirm_template_name,
614 modified_user=user if 'user' in locals(
616 context_instance=get_context(request,
619 if not request.user.is_authenticated():
620 path = quote(request.get_full_path())
621 url = request.build_absolute_uri(reverse('index'))
622 return HttpResponseRedirect(url + '?next=' + path)
623 form = EmailChangeForm(request.POST or None)
624 if request.method == 'POST' and form.is_valid():
626 ec = form.save(email_template_name, request)
627 except SendMailError, e:
629 messages.error(request, msg)
630 transaction.rollback()
631 except IntegrityError, e:
632 msg = _('There is already a pending change email request.')
633 messages.error(request, msg)
635 msg = _('Change email request has been registered succefully.\
636 You are going to receive a verification email in the new address.')
637 messages.success(request, msg)
639 return render_response(form_template_name,
641 context_instance=get_context(request,
645 @signed_terms_required
647 def group_add(request, kind_name='default'):
649 kind = GroupKind.objects.get(name=kind_name)
651 return HttpResponseBadRequest(_('No such group kind'))
653 template_loader = loader
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:
759 for k, l in d.iteritems():
760 page = request.GET.get('%s_page' % k, 1)
761 sorting = globals()['%s_sorting' % k] = request.GET.get('%s_sorting' % k)
763 sort_form = AstakosGroupSortForm({'sort_by': sorting})
764 if sort_form.is_valid():
765 sort_field = q._model_fields.get(sorting)
766 default = _get_default(sort_field)
767 l.sort(key=lambda i: getattr(i, sorting) if getattr(i, sorting) else default)
768 globals()['%s_sorting' % k] = sorting
769 paginator = Paginator(l, PAGINATE_BY)
772 page_number = int(page)
775 page_number = paginator.num_pages
777 # Page is not 'last', nor can it be converted to an int.
780 page_obj = globals()['%s_page_obj' % k] = paginator.page(page_number)
783 return object_list(request, queryset=none,
784 extra_context={'is_search':False,
785 'mine': own_page_obj,
786 'other': other_page_obj,
787 'own_sorting': own_sorting,
788 'other_sorting': other_sorting
792 @signed_terms_required
794 def group_detail(request, group_id):
795 q = AstakosGroup.objects.filter(pk=group_id)
797 'is_member': """SELECT CASE WHEN EXISTS(
798 SELECT id FROM im_membership
799 WHERE group_id = im_astakosgroup.group_ptr_id
801 THEN 1 ELSE 0 END""" % request.user.id,
802 'is_owner': """SELECT CASE WHEN EXISTS(
803 SELECT id FROM im_astakosuser_owner
804 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
805 AND astakosuser_id = %s)
806 THEN 1 ELSE 0 END""" % request.user.id,
807 'kindname': """SELECT name FROM im_groupkind
808 WHERE id = im_astakosgroup.kind_id"""})
811 context_processors = None
815 except ObjectDoesNotExist:
816 raise Http404("No %s found matching the query" % (model._meta.verbose_name))
818 update_form = AstakosGroupUpdateForm(instance=obj)
819 addmembers_form = AddGroupMembersForm()
820 if request.method == 'POST':
823 for k,v in request.POST.iteritems():
824 if k in update_form.fields:
826 if k in addmembers_form.fields:
827 addmembers_data[k] = v
828 update_data = update_data or None
829 addmembers_data = addmembers_data or None
830 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
831 addmembers_form = AddGroupMembersForm(addmembers_data)
832 if update_form.is_valid():
834 if addmembers_form.is_valid():
835 map(obj.approve_member, addmembers_form.valid_users)
836 addmembers_form = AddGroupMembersForm()
838 template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
839 t = template_loader.get_template(template_name)
840 c = RequestContext(request, {
842 }, context_processors)
843 extra_context = {'update_form': update_form,
844 'addmembers_form': addmembers_form,
845 'page': request.GET.get('page', 1),
846 'sorting': request.GET.get('sorting')}
847 for key, value in extra_context.items():
852 response = HttpResponse(t.render(c), mimetype=mimetype)
853 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
857 @signed_terms_required
859 def group_search(request, extra_context=None, **kwargs):
860 q = request.GET.get('q')
861 sorting = request.GET.get('sorting')
862 if request.method == 'GET':
863 form = AstakosGroupSearchForm({'q': q} if q else None)
865 form = AstakosGroupSearchForm(get_query(request))
867 q = form.cleaned_data['q'].strip()
869 queryset = AstakosGroup.objects.select_related()
870 queryset = queryset.filter(name__contains=q)
871 queryset = queryset.filter(approval_date__isnull=False)
872 queryset = queryset.extra(select={
873 'groupname': DB_REPLACE_GROUP_SCHEME,
874 'kindname': "im_groupkind.name",
875 'approved_members_num': """
876 SELECT COUNT(*) FROM im_membership
877 WHERE group_id = im_astakosgroup.group_ptr_id
878 AND date_joined IS NOT NULL""",
879 'membership_approval_date': """
880 SELECT date_joined FROM im_membership
881 WHERE group_id = im_astakosgroup.group_ptr_id
882 AND person_id = %s""" % request.user.id,
884 SELECT CASE WHEN EXISTS(
885 SELECT date_joined FROM im_membership
886 WHERE group_id = im_astakosgroup.group_ptr_id
888 THEN 1 ELSE 0 END""" % request.user.id})
890 # TODO check sorting value
891 queryset = queryset.order_by(sorting)
893 queryset = AstakosGroup.objects.none()
897 paginate_by=PAGINATE_BY,
898 page=request.GET.get('page') or 1,
899 template_name='im/astakosgroup_list.html',
900 extra_context=dict(form=form,
905 @signed_terms_required
907 def group_all(request, extra_context=None, **kwargs):
908 q = AstakosGroup.objects.select_related()
909 q = q.filter(approval_date__isnull=False)
911 'groupname': DB_REPLACE_GROUP_SCHEME,
912 'kindname': "im_groupkind.name",
913 'approved_members_num': """
914 SELECT COUNT(*) FROM im_membership
915 WHERE group_id = im_astakosgroup.group_ptr_id
916 AND date_joined IS NOT NULL""",
917 'membership_approval_date': """
918 SELECT date_joined FROM im_membership
919 WHERE group_id = im_astakosgroup.group_ptr_id
920 AND person_id = %s""" % request.user.id,
922 SELECT CASE WHEN EXISTS(
923 SELECT date_joined FROM im_membership
924 WHERE group_id = im_astakosgroup.group_ptr_id
926 THEN 1 ELSE 0 END""" % request.user.id})
927 sorting = request.GET.get('sorting')
929 # TODO check sorting value
930 q = q.order_by(sorting)
934 paginate_by=PAGINATE_BY,
935 page=request.GET.get('page') or 1,
936 template_name='im/astakosgroup_list.html',
937 extra_context=dict(form=AstakosGroupSearchForm(),
942 @signed_terms_required
944 def group_join(request, group_id):
945 m = Membership(group_id=group_id,
947 date_requested=datetime.now())
950 post_save_redirect = reverse(
952 kwargs=dict(group_id=group_id))
953 return HttpResponseRedirect(post_save_redirect)
954 except IntegrityError, e:
956 msg = _('Failed to join group.')
957 messages.error(request, msg)
958 return group_search(request)
961 @signed_terms_required
963 def group_leave(request, group_id):
965 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)
985 def handle_membership(func):
987 def wrapper(request, group_id, user_id):
989 m = Membership.objects.select_related().get(
993 except Membership.DoesNotExist:
994 return HttpResponseBadRequest(_('Invalid membership.'))
996 if request.user not in m.group.owner.all():
997 return HttpResponseForbidden(_('User is not a group owner.'))
999 return render_response(
1000 template='im/astakosgroup_detail.html',
1001 context_instance=get_context(request),
1008 @signed_terms_required
1011 def approve_member(request, membership):
1013 membership.approve()
1014 realname = membership.person.realname
1015 msg = _('%s has been successfully joined the group.' % realname)
1016 messages.success(request, msg)
1017 except BaseException, e:
1019 msg = _('Something went wrong during %s\'s approval.' % realname)
1020 messages.error(request, msg)
1023 @signed_terms_required
1026 def disapprove_member(request, membership):
1028 membership.disapprove()
1029 realname = membership.person.realname
1030 msg = _('%s has been successfully removed from the group.' % realname)
1031 messages.success(request, msg)
1032 except BaseException, e:
1034 msg = _('Something went wrong during %s\'s disapproval.' % realname)
1035 messages.error(request, msg)
1038 @signed_terms_required
1040 def resource_list(request):
1041 return render_response(
1042 template='im/astakosuserquota_list.html',
1043 context_instance=get_context(request),
1044 quota=request.user.quota
1048 def group_create_list(request):
1049 return render_response(
1050 template='im/astakosgroup_create_list.html',
1051 context_instance=get_context(request),
1055 @signed_terms_required
1057 def billing(request):
1059 today = datetime.today()
1060 month_last_day= calendar.monthrange(today.year, today.month)[1]
1062 start = request.POST.get('datefrom', None)
1064 today = datetime.fromtimestamp(int(start))
1065 month_last_day= calendar.monthrange(today.year, today.month)[1]
1067 start = datetime(today.year, today.month, 1).strftime("%s")
1068 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1069 r = request_billing.apply(args=('pgerakios@grnet.gr',
1075 status, data = r.result
1076 data=_clear_billing_data(data)
1078 messages.error(request, _('Service response status: %d' % status))
1080 messages.error(request, r.result)
1084 return render_response(
1085 template='im/billing.html',
1086 context_instance=get_context(request),
1088 zerodate=datetime(month=1,year=1970, day=1),
1091 month_last_day=month_last_day)
1093 def _clear_billing_data(data):
1095 # remove addcredits entries
1097 return e['serviceName'] != "addcredits"
1102 def servicefilter(service_name):
1103 service = service_name
1105 return e['serviceName'] == service
1109 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1110 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1111 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1112 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1117 def _get_default(field):
1118 if isinstance(field, DateTimeField):
1119 return datetime.utcfromtimestamp(0)
1120 elif isinstance(field, int):