Merge branch 'dev' of https://code.grnet.gr/git/astakos into dev
[astakos] / snf-astakos-app / astakos / im / views.py
index b0e4177..c5cd73e 100644 (file)
@@ -37,6 +37,7 @@ import calendar
 from urllib import quote
 from functools import wraps
 from datetime import datetime, timedelta
+from collections import defaultdict
 
 from django.contrib import messages
 from django.contrib.auth.decorators import login_required
@@ -47,16 +48,16 @@ from django.db.models import Q
 from django.db.utils import IntegrityError
 from django.forms.fields import URLField
 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
-    HttpResponseRedirect, HttpResponseBadRequest
+    HttpResponseRedirect, HttpResponseBadRequest, Http404
 from django.shortcuts import redirect
-from django.template import RequestContext, loader
+from django.template import RequestContext, loader as template_loader
 from django.utils.http import urlencode
 from django.utils.translation import ugettext as _
 from django.views.generic.create_update import (create_object, delete_object,
                                                 get_model_and_form_class)
 from django.views.generic.list_detail import object_list, object_detail
 from django.http import HttpResponseBadRequest
-from django.core.paginator import Paginator, InvalidPage
+from django.core.xheaders import populate_xheaders
 
 from astakos.im.models import (
     AstakosUser, ApprovalTerms, AstakosGroup, Resource,
@@ -67,13 +68,16 @@ from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
                               FeedbackForm, SignApprovalTermsForm,
                               ExtendedPasswordChangeForm, EmailChangeForm,
                               AstakosGroupCreationForm, AstakosGroupSearchForm,
-                              AstakosGroupUpdateForm, AddGroupMembersForm)
+                              AstakosGroupUpdateForm, AddGroupMembersForm,
+                              AstakosGroupSortForm, MembersSortForm,
+                              TimelineForm)
 from astakos.im.functions import (send_feedback, SendMailError,
                                   invite as invite_func, logout as auth_logout,
                                   activate as activate_func,
                                   switch_account_to_shibboleth,
                                   send_admin_notification,
                                   SendNotificationError)
+from astakos.im.endpoints.quotaholder import timeline_charge
 from astakos.im.settings import (
     COOKIE_NAME, COOKIE_DOMAIN, SITENAME, LOGOUT_NEXT,
     LOGGING_LEVEL, PAGINATE_BY)
@@ -82,6 +86,9 @@ from astakos.im.tasks import request_billing
 logger = logging.getLogger(__name__)
 
 
+DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
+                                     'https://', '')"""
+
 def render_response(template, tab=None, status=200, reset_cookie=False,
                     context_instance=None, **kwargs):
     """
@@ -92,7 +99,7 @@ def render_response(template, tab=None, status=200, reset_cookie=False,
     if tab is None:
         tab = template.partition('_')[0].partition('.html')[0]
     kwargs.setdefault('tab', tab)
-    html = loader.render_to_string(
+    html = template_loader.render_to_string(
         template, kwargs, context_instance=context_instance)
     response = HttpResponse(html, status=status)
     if reset_cookie:
@@ -364,10 +371,10 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
                         logger.log(LOGGING_LEVEL, msg)
                 if user and user.is_active:
                     next = request.POST.get('next', '')
-                   transaction.commit()
+                    transaction.commit()
                     return prepare_response(request, user, next=next)
                 messages.add_message(request, status, message)
-               transaction.commit()
+                transaction.commit()
                 return render_response(on_success,
                                        context_instance=get_context(request, extra_context))
             except SendMailError, e:
@@ -459,12 +466,13 @@ def logout(request, template='registration/logged_out.html', extra_context=None)
         return response
     messages.success(request, _('You have successfully logged out.'))
     context = get_context(request, extra_context)
-    response.write(loader.render_to_string(template, context_instance=context))
+    response.write(template_loader.render_to_string(template, context_instance=context))
     return response
 
 
 @transaction.commit_manually
-def activate(request, greeting_email_template_name='im/welcome_email.txt', helpdesk_email_template_name='im/helpdesk_notification.txt'):
+def activate(request, greeting_email_template_name='im/welcome_email.txt',
+             helpdesk_email_template_name='im/helpdesk_notification.txt'):
     """
     Activates the user identified by the ``auth`` request parameter, sends a welcome email
     and renews the user token.
@@ -643,7 +651,6 @@ def group_add(request, kind_name='default'):
     except:
         return HttpResponseBadRequest(_('No such group kind'))
 
-    template_loader = loader
     post_save_redirect = '/im/group/%(id)s/'
     context_processors = None
     model, form_class = get_model_and_form_class(
@@ -716,74 +723,137 @@ def group_add(request, kind_name='default'):
 @signed_terms_required
 @login_required
 def group_list(request):
-    q = request.user.astakos_groups.none()
-    list = request.user.astakos_groups.select_related().all()
-    d = {}
-    d['own'] = [g for g in list if request.user in g.owner.all()]
-    d['other'] = list.exclude(id__in=(g.id for g in d['own']))
-    for k, queryset in d.iteritems():
-        paginator = Paginator(queryset, PAGINATE_BY)
-        page = request.GET.get('%s_page' % k, 1)
-        try:
-            page_number = int(page)
-        except ValueError:
-            if page == 'last':
-                page_number = paginator.num_pages
-            else:
-                # Page is not 'last', nor can it be converted to an int.
-                raise Http404
-        try:
-            page_obj = locals()['%s_page_obj' % k] = paginator.page(page_number)
-        except InvalidPage:
-            raise Http404
-    return object_list(request, queryset=q,
+    none = request.user.astakos_groups.none()
+    q = AstakosGroup.objects.raw("""
+        SELECT auth_group.id,
+        %s AS groupname,
+        im_groupkind.name AS kindname,
+        im_astakosgroup.*,
+        owner.email AS groupowner,
+        (SELECT COUNT(*) FROM im_membership
+            WHERE group_id = im_astakosgroup.group_ptr_id
+            AND date_joined IS NOT NULL) AS approved_members_num,
+        (SELECT CASE WHEN(
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s) IS NULL
+                    THEN 0 ELSE 1 END) AS membership_status
+        FROM im_astakosgroup
+        INNER JOIN im_membership ON (
+            im_astakosgroup.group_ptr_id = im_membership.group_id)
+        INNER JOIN auth_group ON(im_astakosgroup.group_ptr_id = auth_group.id)
+        INNER JOIN im_groupkind ON (im_astakosgroup.kind_id = im_groupkind.id)
+        LEFT JOIN im_astakosuser_owner ON (
+            im_astakosuser_owner.astakosgroup_id = im_astakosgroup.group_ptr_id)
+        LEFT JOIN auth_user as owner ON (
+            im_astakosuser_owner.astakosuser_id = owner.id)
+        WHERE im_membership.person_id = %s
+        """ % (DB_REPLACE_GROUP_SCHEME, request.user.id, request.user.id))
+    d = defaultdict(list)
+    for g in q:
+        if request.user.email == g.groupowner:
+            d['own'].append(g)
+        else:
+            d['other'].append(g)
+    
+    # validate sorting
+    fields = ('own', 'other')
+    for f in fields:
+        v = globals()['%s_sorting' % f] = request.GET.get('%s_sorting' % f)
+        if v:
+            form = AstakosGroupSortForm({'sort_by': v})
+            if not form.is_valid():
+                globals()['%s_sorting' % f] = form.cleaned_data.get('sort_by')
+    return object_list(request, queryset=none,
                        extra_context={'is_search':False,
-                                      'mine': locals()['own_page_obj'],
-                                      'other': locals()['other_page_obj']})
+                                      'mine': d['own'],
+                                      'other': d['other'],
+                                      'own_sorting': own_sorting,
+                                      'other_sorting': other_sorting,
+                                      'own_page': request.GET.get('own_page', 1),
+                                      'other_page': request.GET.get('other_page', 1)
+                                      })
 
 
 @signed_terms_required
 @login_required
 def group_detail(request, group_id):
+    q = AstakosGroup.objects.select_related().filter(pk=group_id)
+    q = q.extra(select={
+        'is_member': """SELECT CASE WHEN EXISTS(
+                            SELECT id FROM im_membership
+                            WHERE group_id = im_astakosgroup.group_ptr_id
+                            AND person_id = %s)
+                        THEN 1 ELSE 0 END""" % request.user.id,
+        'is_owner': """SELECT CASE WHEN EXISTS(
+                        SELECT id FROM im_astakosuser_owner
+                        WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+                        AND astakosuser_id = %s)
+                        THEN 1 ELSE 0 END""" % request.user.id,
+        'kindname': """SELECT name FROM im_groupkind
+                       WHERE id = im_astakosgroup.kind_id"""})
+    
+    model = q.model
+    context_processors = None
+    mimetype = None
     try:
-        group = AstakosGroup.objects.select_related().get(id=group_id)
+        obj = q.get()
     except AstakosGroup.DoesNotExist:
-        return HttpResponseBadRequest(_('Invalid group.'))
-    form = AstakosGroupUpdateForm(instance=group)
-    search_form = AddGroupMembersForm()
-    return object_detail(request,
-                         AstakosGroup.objects.all(),
-                         object_id=group_id,
-                         extra_context={'quota': group.quota,
-                                        'form': form,
-                                        'search_form': search_form}
-                         )
-
+        raise Http404("No %s found matching the query" % (
+            model._meta.verbose_name))
+    
+    update_form = AstakosGroupUpdateForm(instance=obj)
+    addmembers_form = AddGroupMembersForm()
+    if request.method == 'POST':
+        update_data = {}
+        addmembers_data = {}
+        for k,v in request.POST.iteritems():
+            if k in update_form.fields:
+                update_data[k] = v
+            if k in addmembers_form.fields:
+                addmembers_data[k] = v
+        update_data = update_data or None
+        addmembers_data = addmembers_data or None
+        update_form = AstakosGroupUpdateForm(update_data, instance=obj)
+        addmembers_form = AddGroupMembersForm(addmembers_data)
+        if update_form.is_valid():
+            update_form.save()
+        if addmembers_form.is_valid():
+            map(obj.approve_member, addmembers_form.valid_users)
+            addmembers_form = AddGroupMembersForm()
+    
+    template_name = "%s/%s_detail.html" % (model._meta.app_label, model._meta.object_name.lower())
+    t = template_loader.get_template(template_name)
+    c = RequestContext(request, {
+        'object': obj,
+    }, context_processors)
+    
+    # validate sorting
+    sorting= request.GET.get('sorting')
+    if sorting:
+        form = MembersSortForm({'sort_by': sorting})
+        if form.is_valid():
+            sorting = form.cleaned_data.get('sort_by')
+         
+    extra_context = {'update_form': update_form,
+                     'addmembers_form': addmembers_form,
+                     'page': request.GET.get('page', 1),
+                     'sorting': sorting}
+    for key, value in extra_context.items():
+        if callable(value):
+            c[key] = value()
+        else:
+            c[key] = value
+    response = HttpResponse(t.render(c), mimetype=mimetype)
+    populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
+    return response
 
-@signed_terms_required
-@login_required
-def group_update(request, group_id):
-    if request.method != 'POST':
-        return HttpResponseBadRequest('Method not allowed.')
-    try:
-        group = AstakosGroup.objects.select_related().get(id=group_id)
-    except AstakosGroup.DoesNotExist:
-        return HttpResponseBadRequest(_('Invalid group.'))
-    form = AstakosGroupUpdateForm(request.POST, instance=group)
-    if form.is_valid():
-        form.save()
-    search_form = AddGroupMembersForm()
-    return object_detail(request,
-                         AstakosGroup.objects.all(),
-                         object_id=group_id,
-                         extra_context={'quota': group.quota,
-                                        'form': form,
-                                        'search_form': search_form})
 
 @signed_terms_required
 @login_required
 def group_search(request, extra_context=None, **kwargs):
     q = request.GET.get('q')
+    sorting = request.GET.get('sorting')
     if request.method == 'GET':
         form = AstakosGroupSearchForm({'q': q} if q else None)
     else:
@@ -791,8 +861,35 @@ def group_search(request, extra_context=None, **kwargs):
         if form.is_valid():
             q = form.cleaned_data['q'].strip()
     if q:
-        queryset = AstakosGroup.objects.select_related(
-        ).filter(name__contains=q)
+        queryset = AstakosGroup.objects.select_related()
+        queryset = queryset.filter(name__contains=q)
+        queryset = queryset.filter(approval_date__isnull=False)
+        queryset = queryset.extra(select={
+                'groupname': DB_REPLACE_GROUP_SCHEME,
+                'kindname': "im_groupkind.name",
+                'approved_members_num': """
+                    SELECT COUNT(*) FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND date_joined IS NOT NULL""",
+                'membership_approval_date': """
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s""" % request.user.id,
+                'is_member': """
+                    SELECT CASE WHEN EXISTS(
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s)
+                    THEN 1 ELSE 0 END""" % request.user.id,
+                'is_owner': """
+                    SELECT CASE WHEN EXISTS(
+                    SELECT id FROM im_astakosuser_owner
+                    WHERE astakosgroup_id = im_astakosgroup.group_ptr_id
+                    AND astakosuser_id = %s)
+                    THEN 1 ELSE 0 END""" % request.user.id})
+        if sorting:
+            # TODO check sorting value
+            queryset = queryset.order_by(sorting)
     else:
         queryset = AstakosGroup.objects.none()
     return object_list(
@@ -803,19 +900,44 @@ def group_search(request, extra_context=None, **kwargs):
         template_name='im/astakosgroup_list.html',
         extra_context=dict(form=form,
                            is_search=True,
-                           q=q))
+                           q=q,
+                           sorting=sorting))
 
 @signed_terms_required
 @login_required
 def group_all(request, extra_context=None, **kwargs):
+    q = AstakosGroup.objects.select_related()
+    q = q.filter(approval_date__isnull=False)
+    q = q.extra(select={
+                'groupname': DB_REPLACE_GROUP_SCHEME,
+                'kindname': "im_groupkind.name",
+                'approved_members_num': """
+                    SELECT COUNT(*) FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND date_joined IS NOT NULL""",
+                'membership_approval_date': """
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s""" % request.user.id,
+                'is_member': """
+                    SELECT CASE WHEN EXISTS(
+                    SELECT date_joined FROM im_membership
+                    WHERE group_id = im_astakosgroup.group_ptr_id
+                    AND person_id = %s)
+                    THEN 1 ELSE 0 END""" % request.user.id})
+    sorting = request.GET.get('sorting')
+    if sorting:
+        # TODO check sorting value
+        q = q.order_by(sorting)
     return object_list(
                 request,
-                AstakosGroup.objects.select_related().all(),
+                q,
                 paginate_by=PAGINATE_BY,
                 page=request.GET.get('page') or 1,
                 template_name='im/astakosgroup_list.html',
                 extra_context=dict(form=AstakosGroupSearchForm(),
-                                   is_search=True))
+                                   is_search=True,
+                                   sorting=sorting))
 
 
 @signed_terms_required
@@ -823,14 +945,12 @@ def group_all(request, extra_context=None, **kwargs):
 def group_join(request, group_id):
     m = Membership(group_id=group_id,
                    person=request.user,
-                   date_requested=datetime.now()
-                   )
+                   date_requested=datetime.now())
     try:
         m.save()
         post_save_redirect = reverse(
             'group_detail',
-            kwargs=dict(group_id=group_id)
-        )
+            kwargs=dict(group_id=group_id))
         return HttpResponseRedirect(post_save_redirect)
     except IntegrityError, e:
         logger.exception(e)
@@ -845,8 +965,7 @@ def group_leave(request, group_id):
     try:
         m = Membership.objects.select_related().get(
             group__id=group_id,
-            person=request.user
-        )
+            person=request.user)
     except Membership.DoesNotExist:
         return HttpResponseBadRequest(_('Invalid membership.'))
     if request.user in m.group.owner.all():
@@ -858,9 +977,7 @@ def group_leave(request, group_id):
         template_name='im/astakosgroup_list.html',
         post_delete_redirect=reverse(
             'group_detail',
-            kwargs=dict(group_id=group_id)
-        )
-    )
+            kwargs=dict(group_id=group_id)))
 
 
 def handle_membership(func):
@@ -869,20 +986,14 @@ def handle_membership(func):
         try:
             m = Membership.objects.select_related().get(
                 group__id=group_id,
-                person__id=user_id
-            )
+                person__id=user_id)
         except Membership.DoesNotExist:
             return HttpResponseBadRequest(_('Invalid membership.'))
         else:
             if request.user not in m.group.owner.all():
                 return HttpResponseForbidden(_('User is not a group owner.'))
             func(request, m)
-            return render_response(
-                template='im/astakosgroup_detail.html',
-                context_instance=get_context(request),
-                object=m.group,
-                quota=m.group.quota
-            )
+            return group_detail(request, group_id)
     return wrapper
 
 
@@ -897,6 +1008,7 @@ def approve_member(request, membership):
         messages.success(request, msg)
     except BaseException, e:
         logger.exception(e)
+        realname = membership.person.realname
         msg = _('Something went wrong during %s\'s approval.' % realname)
         messages.error(request, msg)
 
@@ -916,47 +1028,18 @@ def disapprove_member(request, membership):
         messages.error(request, msg)
 
 
-
-
-@signed_terms_required
-@login_required
-def add_members(request, group_id):
-    if request.method != 'POST':
-        return HttpResponseBadRequest(_('Bad method'))
-    try:
-        group = AstakosGroup.objects.select_related().get(id=group_id)
-    except AstakosGroup.DoesNotExist:
-        return HttpResponseBadRequest(_('Invalid group.'))
-    search_form = AddGroupMembersForm(request.POST)
-    if search_form.is_valid():
-        users = search_form.get_valid_users()
-        map(group.approve_member, users)
-        search_form = AddGroupMembersForm()
-    form = AstakosGroupUpdateForm(instance=group)
-    return object_detail(request,
-                         AstakosGroup.objects.all(),
-                         object_id=group_id,
-                         extra_context={'quota': group.quota,
-                                        'form': form,
-                                        'search_form' : search_form}
-                         )
-
-
 @signed_terms_required
 @login_required
 def resource_list(request):
     return render_response(
         template='im/astakosuserquota_list.html',
-        context_instance=get_context(request),
-        quota=request.user.quota
-    )
+        context_instance=get_context(request))
 
 
 def group_create_list(request):
     return render_response(
         template='im/astakosgroup_create_list.html',
-        context_instance=get_context(request),
-    )
+        context_instance=get_context(request),)
 
 
 @signed_terms_required
@@ -980,7 +1063,7 @@ def billing(request):
     
     try:
         status, data = r.result
-        data=clear_billing_data(data)
+        data=_clear_billing_data(data)
         if status != 200:
             messages.error(request, _('Service response status: %d' % status))
     except:
@@ -997,7 +1080,7 @@ def billing(request):
         start=int(start),
         month_last_day=month_last_day)  
     
-def clear_billing_data(data):
+def _clear_billing_data(data):
     
     # remove addcredits entries
     def isnotcredit(e):
@@ -1019,3 +1102,31 @@ def clear_billing_data(data):
     data['bill_addcredits'] = filter(servicefilter('addcredits'), data['bill'])
         
     return data    
+
+@signed_terms_required
+@login_required
+def timeline(request):
+#    data = {'entity':request.user.email}
+    timeline_body = ()
+    timeline_header = ()
+#    form = TimelineForm(data)
+    form = TimelineForm()
+    if request.method == 'POST':
+        data = request.POST
+        form = TimelineForm(data)
+        if form.is_valid():
+            data = form.cleaned_data
+            timeline_header = ('entity', 'resource',
+                               'event name', 'event date',
+                               'incremental cost', 'total cost')
+            timeline_body = timeline_charge(
+                                    data['entity'],     data['resource'],
+                                    data['start_date'], data['end_date'],
+                                    data['details'],    data['operation'])
+        
+    return render_response(template='im/timeline.html',
+                           context_instance=get_context(request),
+                           form=form,
+                           timeline_header=timeline_header,
+                           timeline_body=timeline_body)
+    return data