Merge branch 'dev' of https://code.grnet.gr/git/astakos into dev
[astakos] / snf-astakos-app / astakos / im / views.py
index 7013906..c5cd73e 100644 (file)
 # or implied, of GRNET S.A.
 
 import logging
-import socket
+import calendar
 
-from smtplib import SMTPException
 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
 from django.contrib.auth.views import password_change
-from django.core.exceptions import ValidationError
-from django.core.mail import send_mail
 from django.core.urlresolvers import reverse
 from django.db import transaction
 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.loader import render_to_string
+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 *
-from django.views.generic.list_detail import *
-
-from astakos.im.models import AstakosUser, Invitation, ApprovalTerms, AstakosGroup
+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.xheaders import populate_xheaders
+
+from astakos.im.models import (
+    AstakosUser, ApprovalTerms, AstakosGroup, Resource,
+    EmailChange, GroupKind, Membership)
 from astakos.im.activation_backends import get_backend, SimpleBackend
 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
-from astakos.im.forms import *
-from astakos.im.functions import send_greeting, send_feedback, SendMailError, \
-    invite as invite_func, logout as auth_logout, activate as activate_func, switch_account_to_shibboleth
-from astakos.im.settings import DEFAULT_CONTACT_EMAIL, DEFAULT_FROM_EMAIL, COOKIE_NAME, COOKIE_DOMAIN, IM_MODULES, SITENAME, LOGOUT_NEXT, LOGGING_LEVEL
+from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
+                              FeedbackForm, SignApprovalTermsForm,
+                              ExtendedPasswordChangeForm, EmailChangeForm,
+                              AstakosGroupCreationForm, AstakosGroupSearchForm,
+                              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)
+from astakos.im.tasks import request_billing
 
 logger = logging.getLogger(__name__)
 
-def render_response(template, tab=None, status=200, reset_cookie=False, context_instance=None, **kwargs):
+
+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):
     """
     Calls ``django.template.loader.render_to_string`` with an additional ``tab``
     keyword argument and returns an ``django.http.HttpResponse`` with the
@@ -76,7 +99,8 @@ def render_response(template, tab=None, status=200, reset_cookie=False, context_
     if tab is None:
         tab = template.partition('_')[0].partition('.html')[0]
     kwargs.setdefault('tab', tab)
-    html = render_to_string(template, kwargs, context_instance=context_instance)
+    html = template_loader.render_to_string(
+        template, kwargs, context_instance=context_instance)
     response = HttpResponse(html, status=status)
     if reset_cookie:
         set_cookie(response, context_instance['request'].user)
@@ -97,6 +121,7 @@ def requires_anonymous(func):
         return func(request, *args)
     return wrapper
 
+
 def signed_terms_required(func):
     """
     Decorator checkes whether the request.user is Anonymous and in that case
@@ -104,16 +129,17 @@ def signed_terms_required(func):
     """
     @wraps(func)
     def wrapper(request, *args, **kwargs):
-        if request.user.is_authenticated() and not request.user.signed_terms():
+        if request.user.is_authenticated() and not request.user.signed_terms:
             params = urlencode({'next': request.build_absolute_uri(),
-                              'show_form':''})
+                                'show_form': ''})
             terms_uri = reverse('latest_terms') + '?' + params
             return HttpResponseRedirect(terms_uri)
         return func(request, *args, **kwargs)
     return wrapper
 
+
 @signed_terms_required
-def index(request, login_template_name='im/login.html', profile_template_name='im/profile.html', extra_context={}):
+def index(request, login_template_name='im/login.html', extra_context=None):
     """
     If there is logged on user renders the profile page otherwise renders login page.
 
@@ -139,13 +165,14 @@ def index(request, login_template_name='im/login.html', profile_template_name='i
     if request.user.is_authenticated():
         return HttpResponseRedirect(reverse('edit_profile'))
     return render_response(template_name,
-                           login_form = LoginForm(request=request),
-                           context_instance = get_context(request, extra_context))
+                           login_form=LoginForm(request=request),
+                           context_instance=get_context(request, extra_context))
+
 
 @login_required
 @signed_terms_required
 @transaction.commit_manually
-def invite(request, template_name='im/invitations.html', extra_context={}):
+def invite(request, template_name='im/invitations.html', extra_context=None):
     """
     Allows a user to invite somebody else.
 
@@ -177,12 +204,11 @@ def invite(request, template_name='im/invitations.html', extra_context={}):
 
     * LOGIN_URL: login uri
     * ASTAKOS_DEFAULT_CONTACT_EMAIL: service support email
-    * ASTAKOS_DEFAULT_FROM_EMAIL: from email
     """
     status = None
     message = None
     form = InvitationForm()
-    
+
     inviter = request.user
     if request.method == 'POST':
         form = InvitationForm(request.POST)
@@ -211,17 +237,18 @@ def invite(request, template_name='im/invitations.html', extra_context={}):
     sent = [{'email': inv.username,
              'realname': inv.realname,
              'is_consumed': inv.is_consumed}
-             for inv in request.user.invitations_sent.all()]
+            for inv in request.user.invitations_sent.all()]
     kwargs = {'inviter': inviter,
-              'sent':sent}
+              'sent': sent}
     context = get_context(request, extra_context, **kwargs)
     return render_response(template_name,
-                           invitation_form = form,
-                           context_instance = context)
+                           invitation_form=form,
+                           context_instance=context)
+
 
 @login_required
 @signed_terms_required
-def edit_profile(request, template_name='im/profile.html', extra_context={}):
+def edit_profile(request, template_name='im/profile.html', extra_context=None):
     """
     Allows a user to edit his/her profile.
 
@@ -250,6 +277,7 @@ def edit_profile(request, template_name='im/profile.html', extra_context={}):
 
     * LOGIN_URL: login uri
     """
+    extra_context = extra_context or {}
     form = ProfileForm(instance=request.user)
     extra_context['next'] = request.GET.get('next')
     reset_cookie = False
@@ -269,15 +297,18 @@ def edit_profile(request, template_name='im/profile.html', extra_context={}):
             except ValueError, ve:
                 messages.success(request, ve)
     elif request.method == "GET":
-        request.user.is_verified = True
-        request.user.save()
+        if not request.user.is_verified:
+            request.user.is_verified = True
+            request.user.save()
     return render_response(template_name,
-                           reset_cookie = reset_cookie,
-                           profile_form = form,
-                           context_instance = get_context(request,
-                                                          extra_context))
+                           reset_cookie=reset_cookie,
+                           profile_form=form,
+                           context_instance=get_context(request,
+                                                        extra_context))
 
-def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
+
+@transaction.commit_manually
+def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context=None, backend=None):
     """
     Allows a user to create a local account.
 
@@ -288,14 +319,14 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
     if present, otherwise to the ``astakos.im.activation_backends.InvitationBackend``
     if settings.ASTAKOS_INVITATIONS_ENABLED is True or ``astakos.im.activation_backends.SimpleBackend`` if not
     (see activation_backends);
-    
+
     Upon successful user creation, if ``next`` url parameter is present the user is redirected there
     otherwise renders the same page with a success message.
-    
+
     On unsuccessful creation, renders ``template_name`` with an error message.
-    
+
     **Arguments**
-    
+
     ``template_name``
         A custom template to render. This is optional;
         if not specified, this will default to ``im/signup.html``.
@@ -308,13 +339,13 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
         An dictionary of variables to add to the template context.
 
     **Template:**
-    
+
     im/signup.html or ``template_name`` keyword argument.
-    im/signup_complete.html or ``on_success`` keyword argument. 
+    im/signup_complete.html or ``on_success`` keyword argument.
     """
     if request.user.is_authenticated():
         return HttpResponseRedirect(reverse('edit_profile'))
-    
+
     provider = get_query(request).get('provider', 'local')
     try:
         if not backend:
@@ -335,29 +366,35 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
                     additional_email = form.cleaned_data['additional_email']
                     if additional_email != user.email:
                         user.additionalmail_set.create(email=additional_email)
-                        msg = 'Additional email: %s saved for user %s.' % (additional_email, user.email)
-                        logger._log(LOGGING_LEVEL, msg, [])
+                        msg = 'Additional email: %s saved for user %s.' % (
+                            additional_email, user.email)
+                        logger.log(LOGGING_LEVEL, msg)
                 if user and user.is_active:
                     next = request.POST.get('next', '')
+                    transaction.commit()
                     return prepare_response(request, user, next=next)
                 messages.add_message(request, status, message)
+                transaction.commit()
                 return render_response(on_success,
                                        context_instance=get_context(request, extra_context))
             except SendMailError, e:
                 message = e.message
                 messages.error(request, message)
+                transaction.rollback()
             except BaseException, e:
                 message = _('Something went wrong.')
                 messages.error(request, message)
                 logger.exception(e)
+                transaction.rollback()
     return render_response(template_name,
-                           signup_form = form,
-                           provider = provider,
+                           signup_form=form,
+                           provider=provider,
                            context_instance=get_context(request, extra_context))
 
+
 @login_required
 @signed_terms_required
-def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context={}):
+def feedback(request, template_name='im/feedback.html', email_template_name='im/feedback_mail.txt', extra_context=None):
     """
     Allows a user to send feedback.
 
@@ -397,17 +434,17 @@ def feedback(request, template_name='im/feedback.html', email_template_name='im/
             try:
                 send_feedback(msg, data, request.user, email_template_name)
             except SendMailError, e:
-                status = messages.ERROR
                 messages.error(request, message)
             else:
                 message = _('Feedback successfully sent')
-                messages.succeess(request, message)
+                messages.success(request, message)
     return render_response(template_name,
-                           feedback_form = form,
-                           context_instance = get_context(request, extra_context))
+                           feedback_form=form,
+                           context_instance=get_context(request, extra_context))
+
 
 @signed_terms_required
-def logout(request, template='registration/logged_out.html', extra_context={}):
+def logout(request, template='registration/logged_out.html', extra_context=None):
     """
     Wraps `django.contrib.auth.logout` and delete the cookie.
     """
@@ -417,7 +454,7 @@ def logout(request, template='registration/logged_out.html', extra_context={}):
         auth_logout(request)
         response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
         msg = 'Cookie deleted for %s' % email
-        logger._log(LOGGING_LEVEL, msg, [])
+        logger.log(LOGGING_LEVEL, msg)
     next = request.GET.get('next')
     if next:
         response['Location'] = next
@@ -429,11 +466,13 @@ def logout(request, template='registration/logged_out.html', extra_context={}):
         return response
     messages.success(request, _('You have successfully logged out.'))
     context = get_context(request, extra_context)
-    response.write(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.
@@ -447,17 +486,26 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt', helpd
         user = AstakosUser.objects.get(auth_token=token)
     except AstakosUser.DoesNotExist:
         return HttpResponseBadRequest(_('No such user'))
-    
+
     if user.is_active:
         message = _('Account already active.')
         messages.error(request, message)
         return index(request)
-        
+
     try:
-        local_user = AstakosUser.objects.get(~Q(id = user.id), email=user.email, is_active=True)
+        local_user = AstakosUser.objects.get(
+            ~Q(id=user.id),
+            email=user.email,
+            is_active=True
+        )
     except AstakosUser.DoesNotExist:
         try:
-            activate_func(user, greeting_email_template_name, helpdesk_email_template_name, verify_email=True)
+            activate_func(
+                user,
+                greeting_email_template_name,
+                helpdesk_email_template_name,
+                verify_email=True
+            )
             response = prepare_response(request, user, next, renew=True)
             transaction.commit()
             return response
@@ -474,7 +522,11 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt', helpd
             return index(request)
     else:
         try:
-            user = switch_account_to_shibboleth(user, local_user, greeting_email_template_name)
+            user = switch_account_to_shibboleth(
+                user,
+                local_user,
+                greeting_email_template_name
+            )
             response = prepare_response(request, user, next, renew=True)
             transaction.commit()
             return response
@@ -490,7 +542,8 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt', helpd
             transaction.rollback()
             return index(request)
 
-def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context={}):
+
+def approval_terms(request, term_id=None, template_name='im/approval_terms.html', extra_context=None):
     term = None
     terms = None
     if not term_id:
@@ -500,8 +553,8 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
             pass
     else:
         try:
-             term = ApprovalTerms.objects.get(id=term_id)
-        except ApprovalTermDoesNotExist, e:
+            term = ApprovalTerms.objects.get(id=term_id)
+        except ApprovalTerms.DoesNotExist, e:
             pass
 
     if not term:
@@ -516,25 +569,27 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
         form = SignApprovalTermsForm(request.POST, instance=request.user)
         if not form.is_valid():
             return render_response(template_name,
-                           terms = terms,
-                           approval_terms_form = form,
-                           context_instance = get_context(request, extra_context))
+                                   terms=terms,
+                                   approval_terms_form=form,
+                                   context_instance=get_context(request, extra_context))
         user = form.save()
         return HttpResponseRedirect(next)
     else:
         form = None
-        if request.user.is_authenticated() and not request.user.signed_terms():
+        if request.user.is_authenticated() and not request.user.signed_terms:
             form = SignApprovalTermsForm(instance=request.user)
         return render_response(template_name,
-                               terms = terms,
-                               approval_terms_form = form,
-                               context_instance = get_context(request, extra_context))
+                               terms=terms,
+                               approval_terms_form=form,
+                               context_instance=get_context(request, extra_context))
+
 
 @signed_terms_required
 def change_password(request):
     return password_change(request,
-                            post_change_redirect=reverse('edit_profile'),
-                            password_change_form=ExtendedPasswordChangeForm)
+                           post_change_redirect=reverse('edit_profile'),
+                           password_change_form=ExtendedPasswordChangeForm)
+
 
 @signed_terms_required
 @login_required
@@ -543,7 +598,7 @@ def change_email(request, activation_key=None,
                  email_template_name='registration/email_change_email.txt',
                  form_template_name='registration/email_change_form.html',
                  confirm_template_name='registration/email_change_done.html',
-                 extra_context={}):
+                 extra_context=None):
     if activation_key:
         try:
             user = EmailChange.objects.change_email(activation_key)
@@ -557,10 +612,11 @@ def change_email(request, activation_key=None,
         except ValueError, e:
             messages.error(request, e)
         return render_response(confirm_template_name,
-                               modified_user = user if 'user' in locals() else None,
-                               context_instance = get_context(request,
-                                                              extra_context))
-    
+                               modified_user=user if 'user' in locals(
+                               ) else None,
+                               context_instance=get_context(request,
+                                                            extra_context))
+
     if not request.user.is_authenticated():
         path = quote(request.get_full_path())
         url = request.build_absolute_uri(reverse('index'))
@@ -582,130 +638,368 @@ def change_email(request, activation_key=None,
             messages.success(request, msg)
             transaction.commit()
     return render_response(form_template_name,
-                           form = form,
-                           context_instance = get_context(request,
-                                                          extra_context))
+                           form=form,
+                           context_instance=get_context(request,
+                                                        extra_context))
+
 
 @signed_terms_required
 @login_required
-def group_add(request):
-    return create_object(request,
-                         form_class=get_astakos_group_creation_form(request),
-                         post_save_redirect = '/im/group/%(id)s/')
+def group_add(request, kind_name='default'):
+    try:
+        kind = GroupKind.objects.get(name=kind_name)
+    except:
+        return HttpResponseBadRequest(_('No such group kind'))
+
+    post_save_redirect = '/im/group/%(id)s/'
+    context_processors = None
+    model, form_class = get_model_and_form_class(
+        model=None,
+        form_class=AstakosGroupCreationForm
+    )
+    resources = dict(
+        (str(r.id), r) for r in Resource.objects.select_related().all())
+    policies = []
+    if request.method == 'POST':
+        form = form_class(request.POST, request.FILES, resources=resources)
+        if form.is_valid():
+            new_object = form.save()
+
+            # save owner
+            new_object.owners = [request.user]
+
+            # save quota policies
+            for (rid, uplimit) in form.resources():
+                try:
+                    r = resources[rid]
+                except KeyError, e:
+                    logger.exception(e)
+                    # TODO Should I stay or should I go???
+                    continue
+                else:
+                    new_object.astakosgroupquota_set.create(
+                        resource=r,
+                        uplimit=uplimit
+                    )
+                policies.append('%s %d' % (r, uplimit))
+            msg = _("The %(verbose_name)s was created successfully.") %\
+                {"verbose_name": model._meta.verbose_name}
+            messages.success(request, msg, fail_silently=True)
+
+            # send notification
+            try:
+                send_admin_notification(
+                    template_name='im/group_creation_notification.txt',
+                    dictionary={
+                        'group': new_object,
+                        'owner': request.user,
+                        'policies': policies,
+                    },
+                    subject='%s alpha2 testing group creation notification' % SITENAME
+                )
+            except SendNotificationError, e:
+                messages.error(request, e, fail_silently=True)
+            return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
+    else:
+        now = datetime.now()
+        data = {
+            'kind': kind
+        }
+        form = form_class(data, resources=resources)
+
+    # Create the template, context, response
+    template_name = "%s/%s_form.html" % (
+        model._meta.app_label,
+        model._meta.object_name.lower()
+    )
+    t = template_loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+        'kind': kind,
+    }, context_processors)
+    return HttpResponse(t.render(c))
+
 
 @signed_terms_required
 @login_required
 def group_list(request):
-    list = AstakosGroup.objects.filter(membership__person=request.user)
-    return object_list(request, queryset=list)
+    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': 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.'))
-    return object_detail(request,
-                         AstakosGroup.objects.all(),
-                         object_id=group_id,
-                         extra_context = {'form':get_astakos_group_policy_creation_form(group),
-                                          'quota':group.policies,
-                                          'more_policies':group.has_undefined_policies})
-
-@signed_terms_required
-@login_required
-def group_policies_list(request, group_id):
-    list = AstakosGroupQuota.objects.filter(group__id=group_id)
-    return object_list(request, queryset=list)
+        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_policies_add(request, group_id):
-    try:
-        group = AstakosGroup.objects.select_related().get(id=group_id)
-    except AstakosGroup.DoesNotExist:
-        return HttpResponseBadRequest(_('Invalid group.'))
-    return create_object(request,
-                         form_class=get_astakos_group_policy_creation_form(group),
-                         template_name = 'im/astakosgroup_detail.html',
-                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)),
-                         extra_context = {'group':group,
-                                          'quota':group.policies,
-                                          'more_policies':group.has_undefined_policies})
-@signed_terms_required
-@login_required
-def group_approval_request(request, group_id):
-    return HttpResponse()
 
 @signed_terms_required
 @login_required
-def group_search(request, queryset=EmptyQuerySet(), extra_context={}, **kwargs):
-    join_forms = {}
+def group_search(request, extra_context=None, **kwargs):
+    q = request.GET.get('q')
+    sorting = request.GET.get('sorting')
     if request.method == 'GET':
-        form = AstakosGroupSearchForm()
+        form = AstakosGroupSearchForm({'q': q} if q else None)
     else:
         form = AstakosGroupSearchForm(get_query(request))
         if form.is_valid():
             q = form.cleaned_data['q'].strip()
-            q = URLField().to_python(q)
-            queryset = AstakosGroup.objects.select_related().filter(name=q)
-            f = MembershipCreationForm
-            for g in queryset:
-                join_forms[g.name] = f(dict(group=g,
-                                            person=request.user,
-                                            date_requested=datetime.now().strftime("%d/%m/%Y")))
-    return object_list(request,
-                        queryset,
-                        template_name='im/astakosgroup_list.html',
-                        extra_context=dict(form=form, is_search=True, join_forms=join_forms))
+    if 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(
+        request,
+        queryset,
+        paginate_by=PAGINATE_BY,
+        page=request.GET.get('page') or 1,
+        template_name='im/astakosgroup_list.html',
+        extra_context=dict(form=form,
+                           is_search=True,
+                           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,
+                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,
+                                   sorting=sorting))
+
 
 @signed_terms_required
 @login_required
 def group_join(request, group_id):
-    return create_object(request,
-                         model=Membership,
-                         template_name='im/astakosgroup_list.html',
-                         post_save_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
+    m = Membership(group_id=group_id,
+                   person=request.user,
+                   date_requested=datetime.now())
+    try:
+        m.save()
+        post_save_redirect = reverse(
+            'group_detail',
+            kwargs=dict(group_id=group_id))
+        return HttpResponseRedirect(post_save_redirect)
+    except IntegrityError, e:
+        logger.exception(e)
+        msg = _('Failed to join group.')
+        messages.error(request, msg)
+        return group_search(request)
+
 
 @signed_terms_required
 @login_required
 def group_leave(request, group_id):
     try:
-        m = Membership.objects.select_related().get(group__id=group_id, person=request.user)
+        m = Membership.objects.select_related().get(
+            group__id=group_id,
+            person=request.user)
     except Membership.DoesNotExist:
         return HttpResponseBadRequest(_('Invalid membership.'))
     if request.user in m.group.owner.all():
         return HttpResponseForbidden(_('Owner can not leave the group.'))
-    return delete_object(request,
-                         model=Membership,
-                         object_id = m.id,
-                         template_name='im/astakosgroup_list.html',
-                         post_delete_redirect = reverse('group_detail', kwargs=dict(group_id=group_id)))
-
-def handle_membership():
-    def decorator(func):
-        @wraps(func)
-        def wrapper(request, membership_id):
-            try:
-                m = Membership.objects.select_related().get(id=membership_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.policies,
-                                       more_policies=m.group.has_undefined_policies)
-        return wrapper
-    return decorator
+    return delete_object(
+        request,
+        model=Membership,
+        object_id=m.id,
+        template_name='im/astakosgroup_list.html',
+        post_delete_redirect=reverse(
+            'group_detail',
+            kwargs=dict(group_id=group_id)))
+
+
+def handle_membership(func):
+    @wraps(func)
+    def wrapper(request, group_id, user_id):
+        try:
+            m = Membership.objects.select_related().get(
+                group__id=group_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 group_detail(request, group_id)
+    return wrapper
+
 
 @signed_terms_required
 @login_required
-@handle_membership()
+@handle_membership
 def approve_member(request, membership):
     try:
         membership.approve()
@@ -714,12 +1008,14 @@ 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)
-    
+
+
 @signed_terms_required
 @login_required
-@handle_membership()
+@handle_membership
 def disapprove_member(request, membership):
     try:
         membership.disapprove()
@@ -730,3 +1026,107 @@ def disapprove_member(request, membership):
         logger.exception(e)
         msg = _('Something went wrong during %s\'s disapproval.' % realname)
         messages.error(request, msg)
+
+
+@signed_terms_required
+@login_required
+def resource_list(request):
+    return render_response(
+        template='im/astakosuserquota_list.html',
+        context_instance=get_context(request))
+
+
+def group_create_list(request):
+    return render_response(
+        template='im/astakosgroup_create_list.html',
+        context_instance=get_context(request),)
+
+
+@signed_terms_required
+@login_required
+def billing(request):
+    
+    today = datetime.today()
+    month_last_day= calendar.monthrange(today.year, today.month)[1]
+    
+    start = request.POST.get('datefrom', None)
+    if start:
+        today = datetime.fromtimestamp(int(start))
+        month_last_day= calendar.monthrange(today.year, today.month)[1]
+    
+    start = datetime(today.year, today.month, 1).strftime("%s")
+    end = datetime(today.year, today.month, month_last_day).strftime("%s")
+    r = request_billing.apply(args=('pgerakios@grnet.gr',
+                                    int(start) * 1000,
+                                    int(end) * 1000))
+    data = {}
+    
+    try:
+        status, data = r.result
+        data=_clear_billing_data(data)
+        if status != 200:
+            messages.error(request, _('Service response status: %d' % status))
+    except:
+        messages.error(request, r.result)
+    
+    print type(start)
+    
+    return render_response(
+        template='im/billing.html',
+        context_instance=get_context(request),
+        data=data,
+        zerodate=datetime(month=1,year=1970, day=1),
+        today=today,
+        start=int(start),
+        month_last_day=month_last_day)  
+    
+def _clear_billing_data(data):
+    
+    # remove addcredits entries
+    def isnotcredit(e):
+        return e['serviceName'] != "addcredits"
+    
+    
+    
+    # separate services    
+    def servicefilter(service_name):
+        service = service_name
+        def fltr(e):
+            return e['serviceName'] == service
+        return fltr
+        
+    
+    data['bill_nocredits'] = filter(isnotcredit, data['bill'])
+    data['bill_vmtime'] = filter(servicefilter('vmtime'), data['bill'])
+    data['bill_diskspace'] = filter(servicefilter('diskspace'), data['bill'])
+    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