commit a75dbd7b3a4e81955a410ea6e9f7b17f8f217fe6
Author: Giorgos Korfiatis <gkorf@grnet.gr>
Date:   Fri Jan 25 12:26:07 2013 +0200

    View for cancelling a pending application

diff --git a/snf-astakos-app/astakos/im/functions.py b/snf-astakos-app/astakos/im/functions.py
index e34942b..f817e49 100644
--- a/snf-astakos-app/astakos/im/functions.py
+++ b/snf-astakos-app/astakos/im/functions.py
@@ -411,6 +411,15 @@ def get_project_id_of_application_id(project_application_id):
         raise IOError(
             _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % project_application_id)
 
+def get_related_project_id(application_id):
+    try:
+        app = ProjectApplication.objects.get(id=application_id)
+        chain = app.chain
+        project = Project.objects.get(id=chain)
+        return chain
+    except:
+        return None
+
 def get_project_by_id(project_id):
     try:
         return Project.objects.get(id=project_id)
diff --git a/snf-astakos-app/astakos/im/messages.py b/snf-astakos-app/astakos/im/messages.py
index 69aefc3..43e7120 100644
--- a/snf-astakos-app/astakos/im/messages.py
+++ b/snf-astakos-app/astakos/im/messages.py
@@ -193,6 +193,8 @@ APPLICATION_CANNOT_APPROVE              =   "Cannot approve application %s in st
 APPLICATION_CANNOT_DENY                 =   "Cannot deny application %s in state '%s'"
 APPLICATION_CANNOT_DISMISS              =   "Cannot dismiss application %s in state '%s'"
 APPLICATION_CANNOT_CANCEL               =   "Cannot cancel application %s in state '%s'"
+APPLICATION_CANCELLED                   =   "Your project request has been cancelled."
+
 
 # Auth providers messages
 AUTH_PROVIDER_NOT_ACTIVE                     =   "'%(provider)s' is disabled."
diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py
index 41bdd21..964aaac 100644
--- a/snf-astakos-app/astakos/im/models.py
+++ b/snf-astakos-app/astakos/im/models.py
@@ -1456,12 +1456,20 @@ class ProjectApplication(models.Model):
             uplimit = p.get('uplimit', 0)
             self.add_resource_policy(service, resource, uplimit)
 
-    def pending_modifications(self):
+    def pending_modifications_incl_me(self):
         q = self.chained_applications()
-        q = q.filter(~Q(id=self.id) & Q(state=self.PENDING))
-        q = q.order_by('id')
+        q = q.filter(Q(state=self.PENDING))
         return q
 
+    def last_pending_incl_me(self):
+        try:
+            return self.pending_modifications_incl_me().order_by('-id')[0]
+        except IndexError:
+            return None
+
+    def pending_modifications(self):
+        return self.pending_modifications_incl_me().filter(~Q(id=self.id))
+
     def last_pending(self):
         try:
             return self.pending_modifications().order_by('-id')[0]
diff --git a/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html b/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html
index dd773a3..eab36df 100644
--- a/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html
+++ b/snf-astakos-app/astakos/im/templates/im/projects/project_detail.html
@@ -51,9 +51,20 @@
       <a style="font-size:0.7em"
          href="{% url astakos.im.views.project_modify object.pk %}">MODIFY</a>
 
+      {% with object.last_pending_incl_me as last_pending %}
+      {% if last_pending %}
+        -
+        <a style="font-size:0.7em"
+           href="{% url astakos.im.views.project_app_cancel last_pending.pk %}">
+          CANCEL {% if object.is_modification %} MODIFICATION {% else %}
+          PROJECT {% endif %} REQUEST
+        </a>
+      {% endif %}
+      {% endwith %}
+
       <!-- only one is possible, perhaps add cancel button too -->
       {% if can_join_request or can_leav_request %}
-      -
+        <br />
       {% endif %}
     {% endif %}
 
diff --git a/snf-astakos-app/astakos/im/urls.py b/snf-astakos-app/astakos/im/urls.py
index 3224974..0caf69b 100644
--- a/snf-astakos-app/astakos/im/urls.py
+++ b/snf-astakos-app/astakos/im/urls.py
@@ -71,6 +71,7 @@ urlpatterns = patterns(
     url(r'^projects/(?P<chain_id>\d+)/(?P<user_id>\d+)/remove/?$', 'project_remove_member', {}, name='project_remove_member'),
     url(r'^projects/app/(?P<application_id>\d+)/?$', 'project_app', {}, name='project_app'),
     url(r'^projects/app/(?P<application_id>\d+)/modify$', 'project_modify', {}, name='project_modify'),
+    url(r'^projects/app/(?P<application_id>\d+)/cancel$', 'project_app_cancel', {}, name='project_app_cancel'),
 
     url(r'^projects/how_it_works/?$', 'how_it_works', {}, name='how_it_works'),
     url(r'^remove_auth_provider/(?P<pk>\d+)?$', 'remove_auth_provider', {}, name='remove_auth_provider'),
diff --git a/snf-astakos-app/astakos/im/views.py b/snf-astakos-app/astakos/im/views.py
index 2a88718..24e98dc 100644
--- a/snf-astakos-app/astakos/im/views.py
+++ b/snf-astakos-app/astakos/im/views.py
@@ -98,6 +98,7 @@ from astakos.im.functions import (
     SendNotificationError,
     accept_membership, reject_membership, remove_membership, cancel_membership,
     leave_project, join_project, enroll_member, can_join_request, can_leave_request,
+    cancel_application, get_related_project_id,
     get_by_chain_or_404)
 from astakos.im.settings import (
     COOKIE_DOMAIN, LOGOUT_NEXT,
@@ -1100,6 +1101,38 @@ def project_list(request):
 @require_http_methods(["GET", "POST"])
 @signed_terms_required
 @login_required
+@project_transaction_context()
+def project_app_cancel(request, application_id, ctx=None):
+    chain_id = None
+    try:
+        application_id = int(application_id)
+        chain_id = get_related_project_id(application_id)
+        cancel_application(application_id, request.user)
+    except (IOError, PermissionDenied), e:
+        messages.error(request, e)
+    except BaseException, e:
+        logger.exception(e)
+        messages.error(request, _(astakos_messages.GENERIC_ERROR))
+        if ctx:
+            ctx.mark_rollback()
+    else:
+        msg = _(astakos_messages.APPLICATION_CANCELLED)
+        messages.success(request, msg)
+
+    next = request.GET.get('next')
+    if not next:
+        if chain_id:
+            next = reverse('astakos.im.views.project_detail', args=(chain_id,))
+        else:
+            next = reverse('astakos.im.views.project_list')
+
+    next = restrict_next(next, domain=COOKIE_DOMAIN)
+    return redirect(next)
+
+
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
 def project_modify(request, application_id):
 
     try:
