Fixes
[astakos] / snf-astakos-app / astakos / im / views.py
index 6f40b48..e877f16 100644 (file)
@@ -67,6 +67,7 @@ from astakos.im.models import (AstakosUser, ApprovalTerms, AstakosGroup,
                                Resource, EmailChange, GroupKind, Membership,
                                AstakosGroupQuota, RESOURCE_SEPARATOR)
 from django.views.decorators.http import require_http_methods
+from django.db.models.query import QuerySet
 
 from astakos.im.activation_backends import get_backend, SimpleBackend
 from astakos.im.util import get_context, prepare_response, set_cookie, get_query
@@ -76,19 +77,22 @@ from astakos.im.forms import (LoginForm, InvitationForm, ProfileForm,
                               AstakosGroupCreationForm, AstakosGroupSearchForm,
                               AstakosGroupUpdateForm, AddGroupMembersForm,
                               AstakosGroupSortForm, MembersSortForm,
-                              TimelineForm, PickResourceForm)
+                              TimelineForm, PickResourceForm,
+                              AstakosGroupCreationSummaryForm)
 from astakos.im.functions import (send_feedback, SendMailError,
                                   logout as auth_logout,
                                   activate as activate_func,
                                   switch_account_to_shibboleth,
                                   send_group_creation_notification,
                                   SendNotificationError)
-from astakos.im.endpoints.quotaholder import timeline_charge
+from astakos.im.endpoints.qh import timeline_charge
 from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, LOGOUT_NEXT,
                                  LOGGING_LEVEL, PAGINATE_BY)
 from astakos.im.tasks import request_billing
 from astakos.im.api.callpoint import AstakosCallpoint
 
+import astakos.im.messages as astakos_messages
+
 logger = logging.getLogger(__name__)
 
 
@@ -228,21 +232,21 @@ def invite(request, template_name='im/invitations.html', extra_context=None):
                     email = form.cleaned_data.get('username')
                     realname = form.cleaned_data.get('realname')
                     inviter.invite(email, realname)
-                    message = _('Invitation sent to %s' % email)
+                    message = _(astakos_messages.INVITATION_SENT) % locals()
                     messages.success(request, message)
                 except SendMailError, e:
                     message = e.message
                     messages.error(request, message)
                     transaction.rollback()
                 except BaseException, e:
-                    message = _('Something went wrong.')
+                    message = _(astakos_messages.GENERIC_ERROR)
                     messages.error(request, message)
                     logger.exception(e)
                     transaction.rollback()
                 else:
                     transaction.commit()
         else:
-            message = _('No invitations left')
+            message = _(astakos_messages.MAX_INVITATION_NUMBER_REACHED)
             messages.error(request, message)
 
     sent = [{'email': inv.username,
@@ -304,7 +308,7 @@ def edit_profile(request, template_name='im/profile.html', extra_context=None):
                 next = request.POST.get('next')
                 if next:
                     return redirect(next)
-                msg = _('Profile has been updated successfully')
+                msg = _(astakos_messages.PROFILE_UPDATED)
                 messages.success(request, msg)
             except ValueError, ve:
                 messages.success(request, ve)
@@ -384,8 +388,9 @@ 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', '')
+                    response = prepare_response(request, user, next=next)
                     transaction.commit()
-                    return prepare_response(request, user, next=next)
+                    return response
                 messages.add_message(request, status, message)
                 transaction.commit()
                 return render_response(on_success,
@@ -395,7 +400,7 @@ def signup(request, template_name='im/signup.html', on_success='im/signup_comple
                 messages.error(request, message)
                 transaction.rollback()
             except BaseException, e:
-                message = _('Something went wrong.')
+                message = _(astakos_messages.GENERIC_ERROR)
                 messages.error(request, message)
                 logger.exception(e)
                 transaction.rollback()
@@ -450,7 +455,7 @@ def feedback(request, template_name='im/feedback.html', email_template_name='im/
             except SendMailError, e:
                 messages.error(request, message)
             else:
-                message = _('Feedback successfully sent')
+                message = _(astakos_messages.FEEDBACK_SENT)
                 messages.success(request, message)
     return render_response(template_name,
                            feedback_form=form,
@@ -479,7 +484,7 @@ def logout(request, template='registration/logged_out.html', extra_context=None)
         response['Location'] = LOGOUT_NEXT
         response.status_code = 301
         return response
-    messages.success(request, _('You have successfully logged out.'))
+    messages.success(request, _(astakos_messages.LOGOUT_SUCCESS))
     context = get_context(request, extra_context)
     response.write(
         template_loader.render_to_string(template, context_instance=context))
@@ -502,10 +507,10 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt',
     try:
         user = AstakosUser.objects.get(auth_token=token)
     except AstakosUser.DoesNotExist:
-        return HttpResponseBadRequest(_('No such user'))
+        return HttpResponseBadRequest(_(astakos_messages.ACCOUNT_UNKNOWN))
 
     if user.is_active:
-        message = _('Account already active.')
+        message = _(astakos_messages.ACCOUNT_ALREADY_ACTIVE)
         messages.error(request, message)
         return index(request)
 
@@ -532,7 +537,7 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt',
             transaction.rollback()
             return index(request)
         except BaseException, e:
-            message = _('Something went wrong.')
+            message = _(astakos_messages.GENERIC_ERROR)
             messages.error(request, message)
             logger.exception(e)
             transaction.rollback()
@@ -553,7 +558,7 @@ def activate(request, greeting_email_template_name='im/welcome_email.txt',
             transaction.rollback()
             return index(request)
         except BaseException, e:
-            message = _('Something went wrong.')
+            message = _(astakos_messages.GENERIC_ERROR)
             messages.error(request, message)
             logger.exception(e)
             transaction.rollback()
@@ -576,7 +581,7 @@ def approval_terms(request, term_id=None, template_name='im/approval_terms.html'
             pass
 
     if not term:
-        messages.error(request, 'There are no approval terms.')
+        messages.error(request, _(astakos_messages.NO_APPROVAL_TERMS))
         return HttpResponseRedirect(reverse('index'))
     f = open(term.location, 'r')
     terms = f.read()
@@ -624,7 +629,7 @@ def change_email(request, activation_key=None,
         try:
             user = EmailChange.objects.change_email(activation_key)
             if request.user.is_authenticated() and request.user == user:
-                msg = _('Email changed successfully.')
+                msg = _(astakos_messages.EMAIL_CHANGED)
                 messages.success(request, msg)
                 auth_logout(request)
                 response = prepare_response(request, user)
@@ -651,11 +656,10 @@ def change_email(request, activation_key=None,
             messages.error(request, msg)
             transaction.rollback()
         except IntegrityError, e:
-            msg = _('There is already a pending change email request.')
+            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
             messages.error(request, msg)
         else:
-            msg = _('Change email request has been registered succefully.\
-                    You are going to receive a verification email in the new address.')
+            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
             messages.success(request, msg)
             transaction.commit()
     return render_response(form_template_name,
@@ -664,10 +668,68 @@ def change_email(request, activation_key=None,
                                                         extra_context))
 
 
+
+resource_presentation = {
+       'compute': {
+            'help_text':'group compute help text',
+            'is_abbreviation':False,
+            'report_desc':''
+        },
+        'storage': {
+            'help_text':'group storage help text',
+            'is_abbreviation':False,
+            'report_desc':''
+        },
+        'pithos+.diskspace': {
+            'help_text':'resource pithos+.diskspace help text',
+            'is_abbreviation':False,
+            'report_desc':'Pithos+ Diskspace',
+            'placeholder':'eg. 10GB'
+        },
+        'cyclades.vm': {
+            'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
+            'is_abbreviation':True,
+            'report_desc':'Virtual Machines',
+            'placeholder':'eg. 2'
+        },
+        'cyclades.disksize': {
+            'help_text':'resource cyclades.disksize help text',
+            'is_abbreviation':False,
+            'report_desc':'Disksize',
+            'placeholder':'eg. 5GB, 2GB etc'
+        },
+        'cyclades.disk': {
+            'help_text':'resource cyclades.disk help text',
+            'is_abbreviation':False,
+            'report_desc':'Disk',
+            'placeholder':'eg. 5GB, 2GB etc'
+        },
+        'cyclades.ram': {
+            'help_text':'resource cyclades.ram help text',
+            'is_abbreviation':True,
+            'report_desc':'RAM',
+            'placeholder':'eg. 4GB'
+        },
+        'cyclades.cpu': {
+            'help_text':'resource cyclades.cpu help text',
+            'is_abbreviation':True,
+            'report_desc':'CPUs',
+            'placeholder':'eg. 1'
+        },
+        'cyclades.network.private': {
+            'help_text':'resource cyclades.network.private help text',
+            'is_abbreviation':False,
+            'report_desc':'Network',
+            'placeholder':'eg. 1'
+        }
+    }
+
+@require_http_methods(["GET", "POST"])
 @signed_terms_required
 @login_required
 def group_add(request, kind_name='default'):
     result = callpoint.list_resources()
+    print '###', result
     resource_catalog = {'resources':defaultdict(defaultdict),
                         'groups':defaultdict(list)}
     if result.is_success:
@@ -692,28 +754,9 @@ def group_add(request, kind_name='default'):
     try:
         kind = GroupKind.objects.get(name=kind_name)
     except:
-        return HttpResponseBadRequest(_('No such group kind'))
+        return HttpResponseBadRequest(_(astakos_messages.GROUPKIND_UNKNOWN))
+    
     
-    resource_presentation = {
-       'compute': {
-            'help_text':'group compute help text',
-        },
-        'storage': {
-            'help_text':'group storage help text',
-        },
-        'pithos+.diskspace': {
-            'help_text':'resource pithos+.diskspace help text',
-        },
-        'cyclades.vm': {
-            'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
-        },
-        'cyclades.disksize': {
-            'help_text':'resource cyclades.disksize help text',
-        },
-        'cyclades.ram': {
-            'help_text':'resource cyclades.ram help text',
-        }
-    }
 
     post_save_redirect = '/im/group/%(id)s/'
     context_processors = None
@@ -722,45 +765,36 @@ def group_add(request, kind_name='default'):
         form_class=AstakosGroupCreationForm
     )
     
+    resources = resource_catalog['resources']
+    
     if request.method == 'POST':
         form = form_class(request.POST, request.FILES)
         if form.is_valid():
-            d = form.cleaned_data.copy()
-            d['owners'] = [request.user]
-            result = callpoint.create_groups((d,)).next()
-            if result.is_success:
-                new_object = result.data[0]
-                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_group_creation_notification(
-#                         template_name='im/group_creation_notification.txt',
-#                         dictionary={
-#                             'group': new_object,
-#                             'owner': request.user,
-#                             'policies': list(form.cleaned_data['policies']),
-#                         }
-#                     )
-#                 except SendNotificationError, e:
-#                     messages.error(request, e, fail_silently=True)
-                return HttpResponseRedirect(post_save_redirect % new_object)
-            else:
-                msg = _("The %(verbose_name)s creation failed: %(reason)s.") %\
-                    {"verbose_name": model._meta.verbose_name,
-                     "reason":result.reason}
-                messages.error(request, msg, fail_silently=True)
+            return render_response(
+                template='im/astakosgroup_form_summary.html',
+                context_instance=get_context(request),
+                form = AstakosGroupCreationSummaryForm(form.cleaned_data),
+                policies = form.policies(),
+                resource_presentation=resource_presentation,
+                resource_catalog= resource_catalog,
+                resources = resources,
+            
+            )
     else:
         now = datetime.now()
         data = {
-            'kind': kind
+            'kind': kind,
         }
+        for group, resources in resource_catalog['groups'].iteritems():
+            data['is_selected_%s' % group] = False
+            for resource in resources:
+                data['%s_uplimit' % resource] = ''
+        
         form = form_class(data)
 
     # Create the template, context, response
-    template_name = "%s/%s_form_demo.html" % (
+    template_name = "%s/%s_form.html" % (
         model._meta.app_label,
         model._meta.object_name.lower()
     )
@@ -773,10 +807,56 @@ def group_add(request, kind_name='default'):
     }, context_processors)
     return HttpResponse(t.render(c))
 
+
+#@require_http_methods(["POST"])
+@require_http_methods(["GET", "POST"])
+@signed_terms_required
+@login_required
+def group_add_complete(request):
+    model = AstakosGroup
+    form = AstakosGroupCreationSummaryForm(request.POST)
+    if form.is_valid():
+        d = form.cleaned_data
+        d['owners'] = [request.user]
+        result = callpoint.create_groups((d,)).next()
+        if result.is_success:
+            new_object = result.data[0]
+            msg = _(astakos_messages.OBJECT_CREATED) %\
+                {"verbose_name": model._meta.verbose_name}
+            messages.success(request, msg, fail_silently=True)
+            
+            # send notification
+            try:
+                send_group_creation_notification(
+                    template_name='im/group_creation_notification.txt',
+                    dictionary={
+                        'group': new_object,
+                        'owner': request.user,
+                        'policies': d.get('policies', [])
+                    }
+                )
+            except SendNotificationError, e:
+                messages.error(request, e, fail_silently=True)
+            post_save_redirect = '/im/group/%(id)s/'
+            return HttpResponseRedirect(post_save_redirect % new_object)
+        else:
+            msg = _(astakos_messages.OBJECT_CREATED_FAILED) %\
+                {"verbose_name": model._meta.verbose_name,
+                 "reason":result.reason}
+            messages.error(request, msg, fail_silently=True)
+    return render_response(
+        template='im/astakosgroup_form_summary.html',
+        context_instance=get_context(request),
+        form=form)
+
+
+#@require_http_methods(["GET"])
+@require_http_methods(["GET", "POST"])
 @signed_terms_required
 @login_required
 def group_list(request):
     none = request.user.astakos_groups.none()
+    sorting = request.GET.get('sorting')
     q = AstakosGroup.objects.raw("""
         SELECT auth_group.id,
         %s AS groupname,
@@ -802,32 +882,25 @@ def group_list(request):
             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)
-                                      })
+            
+    # Create the template, context, response
+    template_name = "%s/%s_list.html" % (
+        q.model._meta.app_label,
+        q.model._meta.object_name.lower()
+    )
+    extra_context = dict(
+        is_search=False,
+        q=q,
+        sorting=request.GET.get('sorting'),
+        page=request.GET.get('page', 1)
+    )
+    return render_response(template_name,
+                           context_instance=get_context(request, extra_context)
+    )
 
 
+@require_http_methods(["GET", "POST"])
 @signed_terms_required
 @login_required
 def group_detail(request, group_id):
@@ -872,7 +945,11 @@ def group_detail(request, group_id):
         if update_form.is_valid():
             update_form.save()
         if addmembers_form.is_valid():
-            map(obj.approve_member, addmembers_form.valid_users)
+            try:
+                map(obj.approve_member, addmembers_form.valid_users)
+            except AssertionError:
+                msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
+                messages.error(request, msg)
             addmembers_form = AddGroupMembersForm()
 
     template_name = "%s/%s_detail.html" % (
@@ -889,10 +966,30 @@ def group_detail(request, group_id):
         if form.is_valid():
             sorting = form.cleaned_data.get('sort_by')
 
+    result = callpoint.list_resources()
+    resource_catalog = {'resources':defaultdict(defaultdict),
+                        'groups':defaultdict(list)}
+    if result.is_success:
+        for r in result.data:
+            service = r.get('service', '')
+            name = r.get('name', '')
+            group = r.get('group', '')
+            unit = r.get('unit', '')
+            fullname = '%s%s%s' % (service, RESOURCE_SEPARATOR, name)
+            resource_catalog['resources'][fullname] = dict(unit=unit, name=name)
+            resource_catalog['groups'][group].append(fullname)
+        
+        resource_catalog = dict(resource_catalog)
+        for k, v in resource_catalog.iteritems():
+            resource_catalog[k] = dict(v)
+    
+    print '####', resource_catalog, obj.quota
     extra_context = {'update_form': update_form,
                      'addmembers_form': addmembers_form,
                      'page': request.GET.get('page', 1),
-                     'sorting': sorting}
+                     'sorting': sorting,
+                     'resource_catalog':resource_catalog,
+                     'resource_presentation':resource_presentation,}
     for key, value in extra_context.items():
         if callable(value):
             c[key] = value()
@@ -904,9 +1001,11 @@ def group_detail(request, group_id):
     return response
 
 
+@require_http_methods(["GET", "POST"])
 @signed_terms_required
 @login_required
 def group_search(request, extra_context=None, **kwargs):
+    print '###', request
     q = request.GET.get('q')
     sorting = request.GET.get('sorting')
     if request.method == 'GET':
@@ -941,7 +1040,13 @@ def group_search(request, extra_context=None, **kwargs):
                     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})
+                    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)
@@ -959,6 +1064,7 @@ def group_search(request, extra_context=None, **kwargs):
                            sorting=sorting))
 
 
+@require_http_methods(["GET", "POST"])
 @signed_terms_required
 @login_required
 def group_all(request, extra_context=None, **kwargs):
@@ -980,7 +1086,12 @@ def group_all(request, extra_context=None, **kwargs):
                     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})
+                    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,   })
     sorting = request.GET.get('sorting')
     if sorting:
         # TODO check sorting value
@@ -996,6 +1107,8 @@ def group_all(request, extra_context=None, **kwargs):
                            sorting=sorting))
 
 
+#@require_http_methods(["POST"])
+@require_http_methods(["POST", "GET"])
 @signed_terms_required
 @login_required
 def group_join(request, group_id):
@@ -1010,11 +1123,12 @@ def group_join(request, group_id):
         return HttpResponseRedirect(post_save_redirect)
     except IntegrityError, e:
         logger.exception(e)
-        msg = _('Failed to join group.')
+        msg = _(astakos_messages.GROUP_JOIN_FAILURE)
         messages.error(request, msg)
         return group_search(request)
 
 
+@require_http_methods(["POST"])
 @signed_terms_required
 @login_required
 def group_leave(request, group_id):
@@ -1023,9 +1137,9 @@ def group_leave(request, group_id):
             group__id=group_id,
             person=request.user)
     except Membership.DoesNotExist:
-        return HttpResponseBadRequest(_('Invalid membership.'))
+        return HttpResponseBadRequest(_(astakos_messages.NOT_A_MEMBER))
     if request.user in m.group.owner.all():
-        return HttpResponseForbidden(_('Owner can not leave the group.'))
+        return HttpResponseForbidden(_(astakos_messages.OWNER_CANNOT_LEAVE_GROUP))
     return delete_object(
         request,
         model=Membership,
@@ -1044,15 +1158,17 @@ def handle_membership(func):
                 group__id=group_id,
                 person__id=user_id)
         except Membership.DoesNotExist:
-            return HttpResponseBadRequest(_('Invalid membership.'))
+            return HttpResponseBadRequest(_(astakos_messages.NOT_MEMBER))
         else:
             if request.user not in m.group.owner.all():
-                return HttpResponseForbidden(_('User is not a group owner.'))
+                return HttpResponseForbidden(_(astakos_messages.NOT_OWNER))
             func(request, m)
             return group_detail(request, group_id)
     return wrapper
 
 
+#@require_http_methods(["POST"])
+@require_http_methods(["POST", "GET"])
 @signed_terms_required
 @login_required
 @handle_membership
@@ -1060,12 +1176,15 @@ def approve_member(request, membership):
     try:
         membership.approve()
         realname = membership.person.realname
-        msg = _('%s has been successfully joined the group.' % realname)
+        msg = _(astakos_messages.MEMBER_JOINED_GROUP) % locals()
         messages.success(request, msg)
+    except AssertionError:
+        msg = _(astakos_messages.GROUP_MAX_PARTICIPANT_NUMBER_REACHED)
+        messages.error(request, msg)
     except BaseException, e:
         logger.exception(e)
         realname = membership.person.realname
-        msg = _('Something went wrong during %s\'s approval.' % realname)
+        msg = _(astakos_messages.GENERIC_ERROR)
         messages.error(request, msg)
 
 
@@ -1076,14 +1195,16 @@ def disapprove_member(request, membership):
     try:
         membership.disapprove()
         realname = membership.person.realname
-        msg = _('%s has been successfully removed from the group.' % realname)
+        msg = MEMBER_REMOVED % realname
         messages.success(request, msg)
     except BaseException, e:
         logger.exception(e)
-        msg = _('Something went wrong during %s\'s disapproval.' % realname)
+        msg = _(astakos_messages.GENERIC_ERROR)
         messages.error(request, msg)
 
 
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
 @signed_terms_required
 @login_required
 def resource_list(request):
@@ -1109,7 +1230,10 @@ def resource_list(request):
         entry['load_class'] = 'red'
         max_value = float(entry['maxValue'])
         curr_value = float(entry['currValue'])
-        entry['ratio'] = (curr_value / max_value) * 100
+        if max_value > 0 :
+           entry['ratio'] = (curr_value / max_value) * 100
+        else: 
+           entry['ratio'] = 0 
         if entry['ratio'] < 66:
             entry['load_class'] = 'yellow'
         if entry['ratio'] < 33:
@@ -1129,6 +1253,7 @@ def resource_list(request):
         messages.error(request, result.reason)
     return render_response('im/resource_list.html',
                            data=data,
+                           resource_presentation=resource_presentation,
                            context_instance=get_context(request))
 
 
@@ -1139,6 +1264,8 @@ def group_create_list(request):
         context_instance=get_context(request),)
 
 
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
 @signed_terms_required
 @login_required
 def billing(request):
@@ -1162,7 +1289,7 @@ def billing(request):
         status, data = r.result
         data = _clear_billing_data(data)
         if status != 200:
-            messages.error(request, _('Service response status: %d' % status))
+            messages.error(request, _(astakos_messages.BILLING_ERROR) % status)
     except:
         messages.error(request, r.result)
 
@@ -1200,6 +1327,8 @@ def _clear_billing_data(data):
     return data
      
      
+#@require_http_methods(["GET"])
+@require_http_methods(["POST", "GET"])
 @signed_terms_required
 @login_required
 def timeline(request):
@@ -1226,4 +1355,4 @@ def timeline(request):
                            form=form,
                            timeline_header=timeline_header,
                            timeline_body=timeline_body)
-    return data 
+    return data