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 SELECT CASE WHEN EXISTS(
891 SELECT id FROM im_astakosuser_owner
892 WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
893 AND astakosuser_id = %s)
894 THEN 1 ELSE 0 END""" % request.user.id})
896 # TODO check sorting value
897 queryset = queryset.order_by(sorting)
899 queryset = AstakosGroup.objects.none()
903 paginate_by=PAGINATE_BY,
904 page=request.GET.get('page') or 1,
905 template_name='im/astakosgroup_list.html',
906 extra_context=dict(form=form,
911 @signed_terms_required
913 def group_all(request, extra_context=None, **kwargs):
914 q = AstakosGroup.objects.select_related()
915 q = q.filter(approval_date__isnull=False)
917 'groupname': DB_REPLACE_GROUP_SCHEME,
918 'kindname': "im_groupkind.name",
919 'approved_members_num': """
920 SELECT COUNT(*) FROM im_membership
921 WHERE group_id = im_astakosgroup.group_ptr_id
922 AND date_joined IS NOT NULL""",
923 'membership_approval_date': """
924 SELECT date_joined FROM im_membership
925 WHERE group_id = im_astakosgroup.group_ptr_id
926 AND person_id = %s""" % request.user.id,
928 SELECT CASE WHEN EXISTS(
929 SELECT date_joined FROM im_membership
930 WHERE group_id = im_astakosgroup.group_ptr_id
932 THEN 1 ELSE 0 END""" % request.user.id})
933 sorting = request.GET.get('sorting')
935 # TODO check sorting value
936 q = q.order_by(sorting)
940 paginate_by=PAGINATE_BY,
941 page=request.GET.get('page') or 1,
942 template_name='im/astakosgroup_list.html',
943 extra_context=dict(form=AstakosGroupSearchForm(),
948 @signed_terms_required
950 def group_join(request, group_id):
951 m = Membership(group_id=group_id,
953 date_requested=datetime.now())
956 post_save_redirect = reverse(
958 kwargs=dict(group_id=group_id))
959 return HttpResponseRedirect(post_save_redirect)
960 except IntegrityError, e:
962 msg = _('Failed to join group.')
963 messages.error(request, msg)
964 return group_search(request)
967 @signed_terms_required
969 def group_leave(request, group_id):
971 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)
991 def handle_membership(func):
993 def wrapper(request, group_id, user_id):
995 m = Membership.objects.select_related().get(
999 except Membership.DoesNotExist:
1000 return HttpResponseBadRequest(_('Invalid membership.'))
1002 if request.user not in m.group.owner.all():
1003 return HttpResponseForbidden(_('User is not a group owner.'))
1005 return render_response(
1006 template='im/astakosgroup_detail.html',
1007 context_instance=get_context(request),
1014 @signed_terms_required
1017 def approve_member(request, membership):
1019 membership.approve()
1020 realname = membership.person.realname
1021 msg = _('%s has been successfully joined the group.' % realname)
1022 messages.success(request, msg)
1023 except BaseException, e:
1025 msg = _('Something went wrong during %s\'s approval.' % realname)
1026 messages.error(request, msg)
1029 @signed_terms_required
1032 def disapprove_member(request, membership):
1034 membership.disapprove()
1035 realname = membership.person.realname
1036 msg = _('%s has been successfully removed from the group.' % realname)
1037 messages.success(request, msg)
1038 except BaseException, e:
1040 msg = _('Something went wrong during %s\'s disapproval.' % realname)
1041 messages.error(request, msg)
1044 @signed_terms_required
1046 def resource_list(request):
1047 return render_response(
1048 template='im/astakosuserquota_list.html',
1049 context_instance=get_context(request),
1050 quota=request.user.quota
1054 def group_create_list(request):
1055 return render_response(
1056 template='im/astakosgroup_create_list.html',
1057 context_instance=get_context(request),
1061 @signed_terms_required
1063 def billing(request):
1065 today = datetime.today()
1066 month_last_day= calendar.monthrange(today.year, today.month)[1]
1068 start = request.POST.get('datefrom', None)
1070 today = datetime.fromtimestamp(int(start))
1071 month_last_day= calendar.monthrange(today.year, today.month)[1]
1073 start = datetime(today.year, today.month, 1).strftime("%s")
1074 end = datetime(today.year, today.month, month_last_day).strftime("%s")
1075 r = request_billing.apply(args=('pgerakios@grnet.gr',
1081 status, data = r.result
1082 data=_clear_billing_data(data)
1084 messages.error(request, _('Service response status: %d' % status))
1086 messages.error(request, r.result)
1090 return render_response(
1091 template='im/billing.html',
1092 context_instance=get_context(request),
1094 zerodate=datetime(month=1,year=1970, day=1),
1097 month_last_day=month_last_day)
1099 def _clear_billing_data(data):
1101 # remove addcredits entries
1103 return e['serviceName'] != "addcredits"
1108 def servicefilter(service_name):
1109 service = service_name
1111 return e['serviceName'] == service
1115 data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1116 data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1117 data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1118 data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1123 def _get_default(field):
1124 if isinstance(field, DateTimeField):
1125 return datetime.utcfromtimestamp(0)
1126 elif isinstance(field, int):