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.select_related().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 AstakosGroup.DoesNotExist:
816 raise Http404("No %s found matching the query" % (
817 model._meta.verbose_name))
819 update_form = AstakosGroupUpdateForm(instance=obj)
820 addmembers_form = AddGroupMembersForm()
821 if request.method == 'POST':
824 for k,v in request.POST.iteritems():
825 if k in update_form.fields:
827 if k in addmembers_form.fields:
828 addmembers_data[k] = v
829 update_data = update_data or None
830 addmembers_data = addmembers_data or None
831 update_form = AstakosGroupUpdateForm(update_data, instance=obj)
832 addmembers_form = AddGroupMembersForm(addmembers_data)
833 if update_form.is_valid():
835 if addmembers_form.is_valid():
836 map(obj.approve_member, addmembers_form.valid_users)
837 addmembers_form = AddGroupMembersForm()
839 template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
840 t = template_loader.get_template(template_name)
841 c = RequestContext(request, {
843 }, context_processors)
844 extra_context = {'update_form': update_form,
845 'addmembers_form': addmembers_form,
846 'page': request.GET.get('page', 1),
847 'sorting': request.GET.get('sorting')}
848 for key, value in extra_context.items():
853 response = HttpResponse(t.render(c), mimetype=mimetype)
854 populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
858 @signed_terms_required
860 def group_search(request, extra_context=None, **kwargs):
861 q = request.GET.get('q')
862 sorting = request.GET.get('sorting')
863 if request.method == 'GET':
864 form = AstakosGroupSearchForm({'q': q} if q else None)
866 form = AstakosGroupSearchForm(get_query(request))
868 q = form.cleaned_data['q'].strip()
870 queryset = AstakosGroup.objects.select_related()
871 queryset = queryset.filter(name__contains=q)
872 queryset = queryset.filter(approval_date__isnull=False)
873 queryset = queryset.extra(select={
874 'groupname': DB_REPLACE_GROUP_SCHEME,
875 'kindname': "im_groupkind.name",
876 'approved_members_num': """
877 SELECT COUNT(*) FROM im_membership
878 WHERE group_id = im_astakosgroup.group_ptr_id
879 AND date_joined IS NOT NULL""",
880 'membership_approval_date': """
881 SELECT date_joined FROM im_membership
882 WHERE group_id = im_astakosgroup.group_ptr_id
883 AND person_id = %s""" % request.user.id,
885 SELECT CASE WHEN EXISTS(
886 SELECT date_joined FROM im_membership
887 WHERE group_id = im_astakosgroup.group_ptr_id
889 THEN 1 ELSE 0 END""" % request.user.id,
891 SELECT CASE WHEN EXISTS(
892 SELECT id FROM im_astakosuser_owner
893 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
894 AND astakosuser_id = %s)
895 THEN 1 ELSE 0 END""" % request.user.id})
897 # TODO check sorting value
898 queryset = queryset.order_by(sorting)
900 queryset = AstakosGroup.objects.none()
904 paginate_by=PAGINATE_BY,
905 page=request.GET.get('page') or 1,
906 template_name='im/astakosgroup_list.html',
907 extra_context=dict(form=form,
912 @signed_terms_required
914 def group_all(request, extra_context=None, **kwargs):
915 q = AstakosGroup.objects.select_related()
916 q = q.filter(approval_date__isnull=False)
918 'groupname': DB_REPLACE_GROUP_SCHEME,
919 'kindname': "im_groupkind.name",
920 'approved_members_num': """
921 SELECT COUNT(*) FROM im_membership
922 WHERE group_id = im_astakosgroup.group_ptr_id
923 AND date_joined IS NOT NULL""",
924 'membership_approval_date': """
925 SELECT date_joined FROM im_membership
926 WHERE group_id = im_astakosgroup.group_ptr_id
927 AND person_id = %s""" % request.user.id,
929 SELECT CASE WHEN EXISTS(
930 SELECT date_joined FROM im_membership
931 WHERE group_id = im_astakosgroup.group_ptr_id
933 THEN 1 ELSE 0 END""" % request.user.id})
934 sorting = request.GET.get('sorting')
936 # TODO check sorting value
937 q = q.order_by(sorting)
941 paginate_by=PAGINATE_BY,
942 page=request.GET.get('page') or 1,
943 template_name='im/astakosgroup_list.html',
944 extra_context=dict(form=AstakosGroupSearchForm(),
949 @signed_terms_required
951 def group_join(request, group_id):
952 m = Membership(group_id=group_id,
954 date_requested=datetime.now())
957 post_save_redirect = reverse(
959 kwargs=dict(group_id=group_id))
960 return HttpResponseRedirect(post_save_redirect)
961 except IntegrityError, e:
963 msg = _('Failed to join group.')
964 messages.error(request, msg)
965 return group_search(request)
968 @signed_terms_required
970 def group_leave(request, group_id):
972 m = Membership.objects.select_related().get(
975 except Membership.DoesNotExist:
976 return HttpResponseBadRequest(_('Invalid membership.'))
977 if request.user in m.group.owner.all():
978 return HttpResponseForbidden(_('Owner can not leave the group.'))
979 return delete_object(
983 template_name='im/astakosgroup_list.html',
984 post_delete_redirect=reverse(
986 kwargs=dict(group_id=group_id)))
989 def handle_membership(func):
991 def wrapper(request, group_id, user_id):
993 m = Membership.objects.select_related().get(
996 except Membership.DoesNotExist:
997 return HttpResponseBadRequest(_('Invalid membership.'))
999 if request.user not in m.group.owner.all():
1000 return HttpResponseForbidden(_('User is not a group owner.'))
1002 return render_response(
1003 template='im/astakosgroup_detail.html',
1004 context_instance=get_context(request),
1006 quota=m.group.quota)
1010 @signed_terms_required
1013 def approve_member(request, membership):
1015 membership.approve()
1016 realname = membership.person.realname
1017 msg = _('%s has been successfully joined the group.' % realname)
1018 messages.success(request, msg)
1019 except BaseException, e:
1021 msg = _('Something went wrong during %s\'s approval.' % realname)
1022 messages.error(request, msg)
1025 @signed_terms_required
1028 def disapprove_member(request, membership):
1030 membership.disapprove()
1031 realname = membership.person.realname
1032 msg = _('%s has been successfully removed from the group.' % realname)
1033 messages.success(request, msg)
1034 except BaseException, e:
1036 msg = _('Something went wrong during %s\'s disapproval.' % realname)
1037 messages.error(request, msg)
1040 @signed_terms_required
1042 def resource_list(request):
1043 return render_response(
1044 template='im/astakosuserquota_list.html',
1045 context_instance=get_context(request),
1046 quota=request.user.quota)
1049 def group_create_list(request):
1050 return render_response(
1051 template='im/astakosgroup_create_list.html',
1052 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):