Change pagination in group list
[astakos] / snf-astakos-app / astakos / im / views.py
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
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.
15 #
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.
28 #
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.
33
34 import logging
35 import calendar
36
37 from urllib import quote
38 from functools import wraps
39 from datetime import datetime, timedelta
40 from collections import defaultdict
41
42 from django.contrib import messages
43 from django.contrib.auth.decorators import login_required
44 from django.contrib.auth.views import password_change
45 from django.core.urlresolvers import reverse
46 from django.db import transaction
47 from django.db.models import Q
48 from django.db.utils import IntegrityError
49 from django.forms.fields import URLField
50 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
51     HttpResponseRedirect, HttpResponseBadRequest, Http404
52 from django.shortcuts import redirect
53 from django.template import RequestContext, loader as template_loader
54 from django.utils.http import urlencode
55 from django.utils.translation import ugettext as _
56 from django.views.generic.create_update import (create_object, delete_object,
57                                                 get_model_and_form_class)
58 from django.views.generic.list_detail import object_list, object_detail
59 from django.http import HttpResponseBadRequest
60 from django.core.xheaders import populate_xheaders
61
62 from astakos.im.models import (
63     AstakosUser, ApprovalTerms, AstakosGroup, Resource,
64     EmailChange, GroupKind, Membership)
65 from astakos.im.activation_backends import get_backend, SimpleBackend
66 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
67 from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
68                               FeedbackForm, SignApprovalTermsForm,
69                               ExtendedPasswordChangeForm, EmailChangeForm,
70                               AstakosGroupCreationForm, AstakosGroupSearchForm,
71                               AstakosGroupUpdateForm, AddGroupMembersForm,
72                               AstakosGroupSortForm)
73 from astakos.im.functions import (send_feedback, SendMailError,
74                                   invite as invite_func, logout as auth_logout,
75                                   activate as activate_func,
76                                   switch_account_to_shibboleth,
77                                   send_admin_notification,
78                                   SendNotificationError)
79 from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, SITENAME,
80                                  LOGOUT_NEXT, LOGGING_LEVEL, PAGINATE_BY)
81 from astakos.im.tasks import request_billing
82
83 logger = logging.getLogger(__name__)
84
85
86 DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
87                                      'https://', '')"""
88
89 def render_response(template, tab=None, status=200, reset_cookie=False,
90                     context_instance=None, **kwargs):
91     """
92     Calls ``django.template.loader.render_to_string`` with an additional ``tab``
93     keyword argument and returns an ``django.http.HttpResponse`` with the
94     specified ``status``.
95     """
96     if tab is None:
97         tab = template.partition('_')[0].partition('.html')[0]
98     kwargs.setdefault('tab', tab)
99     html = template_loader.render_to_string(
100         template, kwargs, context_instance=context_instance)
101     response = HttpResponse(html, status=status)
102     if reset_cookie:
103         set_cookie(response, context_instance['request'].user)
104     return response
105
106
107 def requires_anonymous(func):
108     """
109     Decorator checkes whether the request.user is not Anonymous and in that case
110     redirects to `logout`.
111     """
112     @wraps(func)
113     def wrapper(request, *args):
114         if not request.user.is_anonymous():
115             next = urlencode({'next': request.build_absolute_uri()})
116             logout_uri = reverse(logout) + '?' + next
117             return HttpResponseRedirect(logout_uri)
118         return func(request, *args)
119     return wrapper
120
121
122 def signed_terms_required(func):
123     """
124     Decorator checkes whether the request.user is Anonymous and in that case
125     redirects to `logout`.
126     """
127     @wraps(func)
128     def wrapper(request, *args, **kwargs):
129         if request.user.is_authenticated() and not request.user.signed_terms:
130             params = urlencode({'next': request.build_absolute_uri(),
131                                 'show_form': ''})
132             terms_uri = reverse('latest_terms') + '?' + params
133             return HttpResponseRedirect(terms_uri)
134         return func(request, *args, **kwargs)
135     return wrapper
136
137
138 @signed_terms_required
139 def index(request, login_template_name='im/login.html', extra_context=None):
140     """
141     If there is logged on user renders the profile page otherwise renders login page.
142
143     **Arguments**
144
145     ``login_template_name``
146         A custom login template to use. This is optional; if not specified,
147         this will default to ``im/login.html``.
148
149     ``profile_template_name``
150         A custom profile template to use. This is optional; if not specified,
151         this will default to ``im/profile.html``.
152
153     ``extra_context``
154         An dictionary of variables to add to the template context.
155
156     **Template:**
157
158     im/profile.html or im/login.html or ``template_name`` keyword argument.
159
160     """
161     template_name = login_template_name
162     if request.user.is_authenticated():
163         return HttpResponseRedirect(reverse('edit_profile'))
164     return render_response(template_name,
165                            login_form=LoginForm(request=request),
166                            context_instance=get_context(request, extra_context))
167
168
169 @login_required
170 @signed_terms_required
171 @transaction.commit_manually
172 def invite(request, template_name='im/invitations.html', extra_context=None):
173     """
174     Allows a user to invite somebody else.
175
176     In case of GET request renders a form for providing the invitee information.
177     In case of POST checks whether the user has not run out of invitations and then
178     sends an invitation email to singup to the service.
179
180     The view uses commit_manually decorator in order to ensure the number of the
181     user invitations is going to be updated only if the email has been successfully sent.
182
183     If the user isn't logged in, redirects to settings.LOGIN_URL.
184
185     **Arguments**
186
187     ``template_name``
188         A custom template to use. This is optional; if not specified,
189         this will default to ``im/invitations.html``.
190
191     ``extra_context``
192         An dictionary of variables to add to the template context.
193
194     **Template:**
195
196     im/invitations.html or ``template_name`` keyword argument.
197
198     **Settings:**
199
200     The view expectes the following settings are defined:
201
202     * LOGIN_URL: login uri
203     * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
204     """
205     status = None
206     message = None
207     form = InvitationForm()
208
209     inviter = request.user
210     if request.method == 'POST':
211         form = InvitationForm(request.POST)
212         if inviter.invitations > 0:
213             if form.is_valid():
214                 try:
215                     invitation = form.save()
216                     invite_func(invitation, inviter)
217                     message = _('Invitation sent to %s' % invitation.username)
218                     messages.success(request, message)
219                 except SendMailError, e:
220                     message = e.message
221                     messages.error(request, message)
222                     transaction.rollback()
223                 except BaseException, e:
224                     message = _('Something went wrong.')
225                     messages.error(request, message)
226                     logger.exception(e)
227                     transaction.rollback()
228                 else:
229                     transaction.commit()
230         else:
231             message = _('No invitations left')
232             messages.error(request, message)
233
234     sent = [{'email': inv.username,
235              'realname': inv.realname,
236              'is_consumed': inv.is_consumed}
237             for inv in request.user.invitations_sent.all()]
238     kwargs = {'inviter': inviter,
239               'sent': sent}
240     context = get_context(request, extra_context, **kwargs)
241     return render_response(template_name,
242                            invitation_form=form,
243                            context_instance=context)
244
245
246 @login_required
247 @signed_terms_required
248 def edit_profile(request, template_name='im/profile.html', extra_context=None):
249     """
250     Allows a user to edit his/her profile.
251
252     In case of GET request renders a form for displaying the user information.
253     In case of POST updates the user informantion and redirects to ``next``
254     url parameter if exists.
255
256     If the user isn't logged in, redirects to settings.LOGIN_URL.
257
258     **Arguments**
259
260     ``template_name``
261         A custom template to use. This is optional; if not specified,
262         this will default to ``im/profile.html``.
263
264     ``extra_context``
265         An dictionary of variables to add to the template context.
266
267     **Template:**
268
269     im/profile.html or ``template_name`` keyword argument.
270
271     **Settings:**
272
273     The view expectes the following settings are defined:
274
275     * LOGIN_URL: login uri
276     """
277     extra_context = extra_context or {}
278     form = ProfileForm(instance=request.user)
279     extra_context['next'] = request.GET.get('next')
280     reset_cookie = False
281     if request.method == 'POST':
282         form = ProfileForm(request.POST, instance=request.user)
283         if form.is_valid():
284             try:
285                 prev_token = request.user.auth_token
286                 user = form.save()
287                 reset_cookie = user.auth_token != prev_token
288                 form = ProfileForm(instance=user)
289                 next = request.POST.get('next')
290                 if next:
291                     return redirect(next)
292                 msg = _('Profile has been updated successfully')
293                 messages.success(request, msg)
294             except ValueError, ve:
295                 messages.success(request, ve)
296     elif request.method == "GET":
297         if not request.user.is_verified:
298             request.user.is_verified = True
299             request.user.save()
300     return render_response(template_name,
301                            reset_cookie=reset_cookie,
302                            profile_form=form,
303                            context_instance=get_context(request,
304                                                         extra_context))
305
306
307 @transaction.commit_manually
308 def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
309     """
310     Allows a user to create a local account.
311
312     In case of GET request renders a form for entering the user information.
313     In case of POST handles the signup.
314
315     The user activation will be delegated to the backend specified by the ``backend`` keyword argument
316     if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
317     if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
318     (see activation_backends);
319
320     Upon successful user creation, if ``next`` url parameter is present the user is redirected there
321     otherwise renders the same page with a success message.
322
323     On unsuccessful creation, renders ``template_name`` with an error message.
324
325     **Arguments**
326
327     ``template_name``
328         A custom template to render. This is optional;
329         if not specified, this will default to ``im/signup.html``.
330
331     ``on_success``
332         A custom template to render in case of success. This is optional;
333         if not specified, this will default to ``im/signup_complete.html``.
334
335     ``extra_context``
336         An dictionary of variables to add to the template context.
337
338     **Template:**
339
340     im/signup.html or ``template_name`` keyword argument.
341     im/signup_complete.html or ``on_success`` keyword argument.
342     """
343     if request.user.is_authenticated():
344         return HttpResponseRedirect(reverse('edit_profile'))
345
346     provider = get_query(request).get('provider', 'local')
347     try:
348         if not backend:
349             backend = get_backend(request)
350         form = backend.get_signup_form(provider)
351     except Exception, e:
352         form = SimpleBackend(request).get_signup_form(provider)
353         messages.error(request, e)
354     if request.method == 'POST':
355         if form.is_valid():
356             user = form.save(commit=False)
357             try:
358                 result = backend.handle_activation(user)
359                 status = messages.SUCCESS
360                 message = result.message
361                 user.save()
362                 if 'additional_email' in form.cleaned_data:
363                     additional_email = form.cleaned_data['additional_email']
364                     if additional_email != user.email:
365                         user.additionalmail_set.create(email=additional_email)
366                         msg = 'Additional email: %s saved for user %s.' % (
367                             additional_email, user.email)
368                         logger.log(LOGGING_LEVEL, msg)
369                 if user and user.is_active:
370                     next = request.POST.get('next', '')
371                     transaction.commit()
372                     return prepare_response(request, user, next=next)
373                 messages.add_message(request, status, message)
374                 transaction.commit()
375                 return render_response(on_success,
376                                        context_instance=get_context(request, extra_context))
377             except SendMailError, e:
378                 message = e.message
379                 messages.error(request, message)
380                 transaction.rollback()
381             except BaseException, e:
382                 message = _('Something went wrong.')
383                 messages.error(request, message)
384                 logger.exception(e)
385                 transaction.rollback()
386     return render_response(template_name,
387                            signup_form=form,
388                            provider=provider,
389                            context_instance=get_context(request, extra_context))
390
391
392 @login_required
393 @signed_terms_required
394 def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
395     """
396     Allows a user to send feedback.
397
398     In case of GET request renders a form for providing the feedback information.
399     In case of POST sends an email to support team.
400
401     If the user isn't logged in, redirects to settings.LOGIN_URL.
402
403     **Arguments**
404
405     ``template_name``
406         A custom template to use. This is optional; if not specified,
407         this will default to ``im/feedback.html``.
408
409     ``extra_context``
410         An dictionary of variables to add to the template context.
411
412     **Template:**
413
414     im/signup.html or ``template_name`` keyword argument.
415
416     **Settings:**
417
418     * LOGIN_URL: login uri
419     * ASTAKOS_DEFAULT_CONTACT_EMAIL: List of feedback recipients
420     """
421     if request.method == 'GET':
422         form = FeedbackForm()
423     if request.method == 'POST':
424         if not request.user:
425             return HttpResponse('Unauthorized', status=401)
426
427         form = FeedbackForm(request.POST)
428         if form.is_valid():
429             msg = form.cleaned_data['feedback_msg']
430             data = form.cleaned_data['feedback_data']
431             try:
432                 send_feedback(msg, data, request.user, email_template_name)
433             except SendMailError, e:
434                 messages.error(request, message)
435             else:
436                 message = _('Feedback successfully sent')
437                 messages.success(request, message)
438     return render_response(template_name,
439                            feedback_form=form,
440                            context_instance=get_context(request, extra_context))
441
442
443 @signed_terms_required
444 def logout(request, template='registration/logged_out.html', extra_context=None):
445     """
446     Wraps `django.contrib.auth.logout` and delete the cookie.
447     """
448     response = HttpResponse()
449     if request.user.is_authenticated():
450         email = request.user.email
451         auth_logout(request)
452         response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
453         msg = 'Cookie deleted for %s' % email
454         logger.log(LOGGING_LEVEL, msg)
455     next = request.GET.get('next')
456     if next:
457         response['Location'] = next
458         response.status_code = 302
459         return response
460     elif LOGOUT_NEXT:
461         response['Location'] = LOGOUT_NEXT
462         response.status_code = 301
463         return response
464     messages.success(request, _('You have successfully logged out.'))
465     context = get_context(request, extra_context)
466     response.write(template_loader.render_to_string(template, context_instance=context))
467     return response
468
469
470 @transaction.commit_manually
471 def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
472     """
473     Activates the user identified by the ``auth`` request parameter, sends a welcome email
474     and renews the user token.
475
476     The view uses commit_manually decorator in order to ensure the user state will be updated
477     only if the email will be send successfully.
478     """
479     token = request.GET.get('auth')
480     next = request.GET.get('next')
481     try:
482         user = AstakosUser.objects.get(auth_token=token)
483     except AstakosUser.DoesNotExist:
484         return HttpResponseBadRequest(_('No such user'))
485
486     if user.is_active:
487         message = _('Account already active.')
488         messages.error(request, message)
489         return index(request)
490
491     try:
492         local_user = AstakosUser.objects.get(
493             ~Q(id=user.id),
494             email=user.email,
495             is_active=True
496         )
497     except AstakosUser.DoesNotExist:
498         try:
499             activate_func(
500                 user,
501                 greeting_email_template_name,
502                 helpdesk_email_template_name,
503                 verify_email=True
504             )
505             response = prepare_response(request, user, next, renew=True)
506             transaction.commit()
507             return response
508         except SendMailError, e:
509             message = e.message
510             messages.error(request, message)
511             transaction.rollback()
512             return index(request)
513         except BaseException, e:
514             message = _('Something went wrong.')
515             messages.error(request, message)
516             logger.exception(e)
517             transaction.rollback()
518             return index(request)
519     else:
520         try:
521             user = switch_account_to_shibboleth(
522                 user,
523                 local_user,
524                 greeting_email_template_name
525             )
526             response = prepare_response(request, user, next, renew=True)
527             transaction.commit()
528             return response
529         except SendMailError, e:
530             message = e.message
531             messages.error(request, message)
532             transaction.rollback()
533             return index(request)
534         except BaseException, e:
535             message = _('Something went wrong.')
536             messages.error(request, message)
537             logger.exception(e)
538             transaction.rollback()
539             return index(request)
540
541
542 def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
543     term = None
544     terms = None
545     if not term_id:
546         try:
547             term = ApprovalTerms.objects.order_by('-id')[0]
548         except IndexError:
549             pass
550     else:
551         try:
552             term = ApprovalTerms.objects.get(id=term_id)
553         except ApprovalTerms.DoesNotExist, e:
554             pass
555
556     if not term:
557         return HttpResponseRedirect(reverse('index'))
558     f = open(term.location, 'r')
559     terms = f.read()
560
561     if request.method == 'POST':
562         next = request.POST.get('next')
563         if not next:
564             next = reverse('index')
565         form = SignApprovalTermsForm(request.POST, instance=request.user)
566         if not form.is_valid():
567             return render_response(template_name,
568                                    terms=terms,
569                                    approval_terms_form=form,
570                                    context_instance=get_context(request, extra_context))
571         user = form.save()
572         return HttpResponseRedirect(next)
573     else:
574         form = None
575         if request.user.is_authenticated() and not request.user.signed_terms:
576             form = SignApprovalTermsForm(instance=request.user)
577         return render_response(template_name,
578                                terms=terms,
579                                approval_terms_form=form,
580                                context_instance=get_context(request, extra_context))
581
582
583 @signed_terms_required
584 def change_password(request):
585     return password_change(request,
586                            post_change_redirect=reverse('edit_profile'),
587                            password_change_form=ExtendedPasswordChangeForm)
588
589
590 @signed_terms_required
591 @login_required
592 @transaction.commit_manually
593 def change_email(request, activation_key=None,
594                  email_template_name='registration/email_change_email.txt',
595                  form_template_name='registration/email_change_form.html',
596                  confirm_template_name='registration/email_change_done.html',
597                  extra_context=None):
598     if activation_key:
599         try:
600             user = EmailChange.objects.change_email(activation_key)
601             if request.user.is_authenticated() and request.user == user:
602                 msg = _('Email changed successfully.')
603                 messages.success(request, msg)
604                 auth_logout(request)
605                 response = prepare_response(request, user)
606                 transaction.commit()
607                 return response
608         except ValueError, e:
609             messages.error(request, e)
610         return render_response(confirm_template_name,
611                                modified_user=user if 'user' in locals(
612                                ) else None,
613                                context_instance=get_context(request,
614                                                             extra_context))
615
616     if not request.user.is_authenticated():
617         path = quote(request.get_full_path())
618         url = request.build_absolute_uri(reverse('index'))
619         return HttpResponseRedirect(url + '?next=' + path)
620     form = EmailChangeForm(request.POST or None)
621     if request.method == 'POST' and form.is_valid():
622         try:
623             ec = form.save(email_template_name, request)
624         except SendMailError, e:
625             msg = e
626             messages.error(request, msg)
627             transaction.rollback()
628         except IntegrityError, e:
629             msg = _('There is already a pending change email request.')
630             messages.error(request, msg)
631         else:
632             msg = _('Change email request has been registered succefully.\
633                     You are going to receive a verification email in the new address.')
634             messages.success(request, msg)
635             transaction.commit()
636     return render_response(form_template_name,
637                            form=form,
638                            context_instance=get_context(request,
639                                                         extra_context))
640
641
642 @signed_terms_required
643 @login_required
644 def group_add(request, kind_name='default'):
645     try:
646         kind = GroupKind.objects.get(name=kind_name)
647     except:
648         return HttpResponseBadRequest(_('No such group kind'))
649
650     post_save_redirect = '/im/group/%(id)s/'
651     context_processors = None
652     model, form_class = get_model_and_form_class(
653         model=None,
654         form_class=AstakosGroupCreationForm
655     )
656     resources = dict(
657         (str(r.id), r) for r in Resource.objects.select_related().all())
658     policies = []
659     if request.method == 'POST':
660         form = form_class(request.POST, request.FILES, resources=resources)
661         if form.is_valid():
662             new_object = form.save()
663
664             # save owner
665             new_object.owners = [request.user]
666
667             # save quota policies
668             for (rid, uplimit) in form.resources():
669                 try:
670                     r = resources[rid]
671                 except KeyError, e:
672                     logger.exception(e)
673                     # TODO Should I stay or should I go???
674                     continue
675                 else:
676                     new_object.astakosgroupquota_set.create(
677                         resource=r,
678                         uplimit=uplimit
679                     )
680                 policies.append('%s %d' % (r, uplimit))
681             msg = _("The %(verbose_name)s was created successfully.") %\
682                 {"verbose_name": model._meta.verbose_name}
683             messages.success(request, msg, fail_silently=True)
684
685             # send notification
686             try:
687                 send_admin_notification(
688                     template_name='im/group_creation_notification.txt',
689                     dictionary={
690                         'group': new_object,
691                         'owner': request.user,
692                         'policies': policies,
693                     },
694                     subject='%s alpha2 testing group creation notification' % SITENAME
695                 )
696             except SendNotificationError, e:
697                 messages.error(request, e, fail_silently=True)
698             return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
699     else:
700         now = datetime.now()
701         data = {
702             'kind': kind
703         }
704         form = form_class(data, resources=resources)
705
706     # Create the template, context, response
707     template_name = "%s/%s_form.html" % (
708         model._meta.app_label,
709         model._meta.object_name.lower()
710     )
711     t = template_loader.get_template(template_name)
712     c = RequestContext(request, {
713         'form': form,
714         'kind': kind,
715     }, context_processors)
716     return HttpResponse(t.render(c))
717
718
719 @signed_terms_required
720 @login_required
721 def group_list(request):
722     none = request.user.astakos_groups.none()
723     q = AstakosGroup.objects.raw("""
724         SELECT auth_group.id,
725         %s AS groupname,
726         im_groupkind.name AS kindname,
727         im_astakosgroup.*,
728         owner.email AS groupowner,
729         (SELECT COUNT(*) FROM im_membership
730             WHERE group_id = im_astakosgroup.group_ptr_id
731             AND date_joined IS NOT NULL) AS approved_members_num,
732         (SELECT CASE WHEN(
733                     SELECT date_joined FROM im_membership
734                     WHERE group_id = im_astakosgroup.group_ptr_id
735                     AND person_id = %s) IS NULL
736                     THEN 0 ELSE 1 END) AS membership_status
737         FROM im_astakosgroup
738         INNER JOIN im_membership ON (
739             im_astakosgroup.group_ptr_id = im_membership.group_id)
740         INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
741         INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
742         LEFT JOIN im_astakosuser_owner ON (
743             im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
744         LEFT JOIN auth_user as owner ON (
745             im_astakosuser_owner.astakosuser_id = owner.id)
746         WHERE im_membership.person_id = %s
747         """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
748     d = defaultdict(list)
749     for g in q:
750         if request.user.email == g.groupowner:
751             d['own'].append(g)
752         else:
753             d['other'].append(g)
754     return object_list(request, queryset=none,
755                        extra_context={'is_search':False,
756                                       'mine': d['own'],
757                                       'other': d['other'],
758                                       'own_sorting': request.GET.get('own_sorting'),
759                                       'own_page': request.GET.get('own_page', 1),
760                                       'other_sorting': request.GET.get('other_sorting'),
761                                       'other_page': request.GET.get('other_page', 1),
762                                       })
763
764
765 @signed_terms_required
766 @login_required
767 def group_detail(request, group_id):
768     q = AstakosGroup.objects.select_related().filter(pk=group_id)
769     q = q.extra(select={
770         'is_member': """SELECT CASE WHEN EXISTS(
771                             SELECT id FROM im_membership
772                             WHERE group_id = im_astakosgroup.group_ptr_id
773                             AND person_id = %s)
774                         THEN 1 ELSE 0 END""" % request.user.id,
775         'is_owner': """SELECT CASE WHEN EXISTS(
776                         SELECT id FROM im_astakosuser_owner
777                         WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
778                         AND astakosuser_id = %s)
779                         THEN 1 ELSE 0 END""" % request.user.id,
780         'kindname': """SELECT name FROM im_groupkind
781                        WHERE id = im_astakosgroup.kind_id"""})
782     
783     model = q.model
784     context_processors = None
785     mimetype = None
786     try:
787         obj = q.get()
788     except AstakosGroup.DoesNotExist:
789         raise Http404("No %s found matching the query" % (
790             model._meta.verbose_name))
791     
792     update_form = AstakosGroupUpdateForm(instance=obj)
793     addmembers_form = AddGroupMembersForm()
794     if request.method == 'POST':
795         update_data = {}
796         addmembers_data = {}
797         for k,v in request.POST.iteritems():
798             if k in update_form.fields:
799                 update_data[k] = v
800             if k in addmembers_form.fields:
801                 addmembers_data[k] = v
802         update_data = update_data or None
803         addmembers_data = addmembers_data or None
804         update_form = AstakosGroupUpdateForm(update_data, instance=obj)
805         addmembers_form = AddGroupMembersForm(addmembers_data)
806         if update_form.is_valid():
807             update_form.save()
808         if addmembers_form.is_valid():
809             map(obj.approve_member, addmembers_form.valid_users)
810             addmembers_form = AddGroupMembersForm()
811     
812     template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
813     t = template_loader.get_template(template_name)
814     c = RequestContext(request, {
815         'object': obj,
816     }, context_processors)
817     extra_context = {'update_form': update_form,
818                      'addmembers_form': addmembers_form,
819                      'page': request.GET.get('page', 1),
820                      'sorting': request.GET.get('sorting')}
821     for key, value in extra_context.items():
822         if callable(value):
823             c[key] = value()
824         else:
825             c[key] = value
826     response = HttpResponse(t.render(c), mimetype=mimetype)
827     populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
828     return response
829
830
831 @signed_terms_required
832 @login_required
833 def group_search(request, extra_context=None, **kwargs):
834     q = request.GET.get('q')
835     sorting = request.GET.get('sorting')
836     if request.method == 'GET':
837         form = AstakosGroupSearchForm({'q': q} if q else None)
838     else:
839         form = AstakosGroupSearchForm(get_query(request))
840         if form.is_valid():
841             q = form.cleaned_data['q'].strip()
842     if q:
843         queryset = AstakosGroup.objects.select_related()
844         queryset = queryset.filter(name__contains=q)
845         queryset = queryset.filter(approval_date__isnull=False)
846         queryset = queryset.extra(select={
847                 'groupname': DB_REPLACE_GROUP_SCHEME,
848                 'kindname': "im_groupkind.name",
849                 'approved_members_num': """
850                     SELECT COUNT(*) FROM im_membership
851                     WHERE group_id = im_astakosgroup.group_ptr_id
852                     AND date_joined IS NOT NULL""",
853                 'membership_approval_date': """
854                     SELECT date_joined FROM im_membership
855                     WHERE group_id = im_astakosgroup.group_ptr_id
856                     AND person_id = %s""" % request.user.id,
857                 'is_member': """
858                     SELECT CASE WHEN EXISTS(
859                     SELECT date_joined FROM im_membership
860                     WHERE group_id = im_astakosgroup.group_ptr_id
861                     AND person_id = %s)
862                     THEN 1 ELSE 0 END""" % request.user.id,
863                 'is_owner': """
864                     SELECT CASE WHEN EXISTS(
865                     SELECT id FROM im_astakosuser_owner
866                     WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
867                     AND astakosuser_id = %s)
868                     THEN 1 ELSE 0 END""" % request.user.id})
869         if sorting:
870             # TODO check sorting value
871             queryset = queryset.order_by(sorting)
872     else:
873         queryset = AstakosGroup.objects.none()
874     return object_list(
875         request,
876         queryset,
877         paginate_by=PAGINATE_BY,
878         page=request.GET.get('page') or 1,
879         template_name='im/astakosgroup_list.html',
880         extra_context=dict(form=form,
881                            is_search=True,
882                            q=q,
883                            sorting=sorting))
884
885 @signed_terms_required
886 @login_required
887 def group_all(request, extra_context=None, **kwargs):
888     q = AstakosGroup.objects.select_related()
889     q = q.filter(approval_date__isnull=False)
890     q = q.extra(select={
891                 'groupname': DB_REPLACE_GROUP_SCHEME,
892                 'kindname': "im_groupkind.name",
893                 'approved_members_num': """
894                     SELECT COUNT(*) FROM im_membership
895                     WHERE group_id = im_astakosgroup.group_ptr_id
896                     AND date_joined IS NOT NULL""",
897                 'membership_approval_date': """
898                     SELECT date_joined FROM im_membership
899                     WHERE group_id = im_astakosgroup.group_ptr_id
900                     AND person_id = %s""" % request.user.id,
901                 'is_member': """
902                     SELECT CASE WHEN EXISTS(
903                     SELECT date_joined FROM im_membership
904                     WHERE group_id = im_astakosgroup.group_ptr_id
905                     AND person_id = %s)
906                     THEN 1 ELSE 0 END""" % request.user.id})
907     sorting = request.GET.get('sorting')
908     if sorting:
909         # TODO check sorting value
910         q = q.order_by(sorting)
911     return object_list(
912                 request,
913                 q,
914                 paginate_by=PAGINATE_BY,
915                 page=request.GET.get('page') or 1,
916                 template_name='im/astakosgroup_list.html',
917                 extra_context=dict(form=AstakosGroupSearchForm(),
918                                    is_search=True,
919                                    sorting=sorting))
920
921
922 @signed_terms_required
923 @login_required
924 def group_join(request, group_id):
925     m = Membership(group_id=group_id,
926                    person=request.user,
927                    date_requested=datetime.now())
928     try:
929         m.save()
930         post_save_redirect = reverse(
931             'group_detail',
932             kwargs=dict(group_id=group_id))
933         return HttpResponseRedirect(post_save_redirect)
934     except IntegrityError, e:
935         logger.exception(e)
936         msg = _('Failed to join group.')
937         messages.error(request, msg)
938         return group_search(request)
939
940
941 @signed_terms_required
942 @login_required
943 def group_leave(request, group_id):
944     try:
945         m = Membership.objects.select_related().get(
946             group__id=group_id,
947             person=request.user)
948     except Membership.DoesNotExist:
949         return HttpResponseBadRequest(_('Invalid membership.'))
950     if request.user in m.group.owner.all():
951         return HttpResponseForbidden(_('Owner can not leave the group.'))
952     return delete_object(
953         request,
954         model=Membership,
955         object_id=m.id,
956         template_name='im/astakosgroup_list.html',
957         post_delete_redirect=reverse(
958             'group_detail',
959             kwargs=dict(group_id=group_id)))
960
961
962 def handle_membership(func):
963     @wraps(func)
964     def wrapper(request, group_id, user_id):
965         try:
966             m = Membership.objects.select_related().get(
967                 group__id=group_id,
968                 person__id=user_id)
969         except Membership.DoesNotExist:
970             return HttpResponseBadRequest(_('Invalid membership.'))
971         else:
972             if request.user not in m.group.owner.all():
973                 return HttpResponseForbidden(_('User is not a group owner.'))
974             func(request, m)
975             return group_detail(request, group_id)
976     return wrapper
977
978
979 @signed_terms_required
980 @login_required
981 @handle_membership
982 def approve_member(request, membership):
983     try:
984         membership.approve()
985         realname = membership.person.realname
986         msg = _('%s has been successfully joined the group.' % realname)
987         messages.success(request, msg)
988     except BaseException, e:
989         logger.exception(e)
990         realname = membership.person.realname
991         msg = _('Something went wrong during %s\'s approval.' % realname)
992         messages.error(request, msg)
993
994
995 @signed_terms_required
996 @login_required
997 @handle_membership
998 def disapprove_member(request, membership):
999     try:
1000         membership.disapprove()
1001         realname = membership.person.realname
1002         msg = _('%s has been successfully removed from the group.' % realname)
1003         messages.success(request, msg)
1004     except BaseException, e:
1005         logger.exception(e)
1006         msg = _('Something went wrong during %s\'s disapproval.' % realname)
1007         messages.error(request, msg)
1008
1009
1010 @signed_terms_required
1011 @login_required
1012 def resource_list(request):
1013     return render_response(
1014         template='im/astakosuserquota_list.html',
1015         context_instance=get_context(request),
1016         quota=request.user.quota)
1017
1018
1019 def group_create_list(request):
1020     return render_response(
1021         template='im/astakosgroup_create_list.html',
1022         context_instance=get_context(request),)
1023
1024
1025 @signed_terms_required
1026 @login_required
1027 def billing(request):
1028     
1029     today = datetime.today()
1030     month_last_day= calendar.monthrange(today.year, today.month)[1]
1031     
1032     start = request.POST.get('datefrom', None)
1033     if start:
1034         today = datetime.fromtimestamp(int(start))
1035         month_last_day= calendar.monthrange(today.year, today.month)[1]
1036     
1037     start = datetime(today.year, today.month, 1).strftime("%s")
1038     end = datetime(today.year, today.month, month_last_day).strftime("%s")
1039     r = request_billing.apply(args=('pgerakios@grnet.gr',
1040                                     int(start) * 1000,
1041                                     int(end) * 1000))
1042     data = {}
1043     
1044     try:
1045         status, data = r.result
1046         data=_clear_billing_data(data)
1047         if status != 200:
1048             messages.error(request, _('Service response status: %d' % status))
1049     except:
1050         messages.error(request, r.result)
1051     
1052     print type(start)
1053     
1054     return render_response(
1055         template='im/billing.html',
1056         context_instance=get_context(request),
1057         data=data,
1058         zerodate=datetime(month=1,year=1970, day=1),
1059         today=today,
1060         start=int(start),
1061         month_last_day=month_last_day)  
1062     
1063 def _clear_billing_data(data):
1064     
1065     # remove addcredits entries
1066     def isnotcredit(e):
1067         return e['serviceName'] != "addcredits"
1068     
1069     
1070     
1071     # separate services    
1072     def servicefilter(service_name):
1073         service = service_name
1074         def fltr(e):
1075             return e['serviceName'] == service
1076         return fltr
1077         
1078     
1079     data['bill_nocredits'] = filter(isnotcredit, data['bill'])
1080     data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
1081     data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
1082     data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
1083         
1084     return data