Merge remote-tracking branch 'origin' into future
authorSofia Papagiannaki <papagian@gmail.com>
Mon, 22 Oct 2012 14:31:46 +0000 (17:31 +0300)
committerSofia Papagiannaki <papagian@gmail.com>
Mon, 22 Oct 2012 14:31:46 +0000 (17:31 +0300)
Conflicts:
snf-astakos-app/README
snf-astakos-app/astakos/im/forms.py
snf-astakos-app/astakos/im/settings.py
snf-astakos-app/astakos/im/static/im/js/forms.js
snf-astakos-app/astakos/im/target/redirect.py
snf-astakos-app/astakos/im/target/shibboleth.py
snf-astakos-app/astakos/im/urls.py
snf-astakos-app/astakos/im/util.py
snf-astakos-app/astakos/im/views.py
snf-astakos-app/conf/20-snf-astakos-app-settings.conf

1  2 
snf-astakos-app/README
snf-astakos-app/astakos/im/forms.py
snf-astakos-app/astakos/im/management/commands/user-modify.py
snf-astakos-app/astakos/im/settings.py
snf-astakos-app/astakos/im/target/local.py
snf-astakos-app/astakos/im/target/redirect.py
snf-astakos-app/astakos/im/target/shibboleth.py
snf-astakos-app/astakos/im/urls.py
snf-astakos-app/astakos/im/util.py
snf-astakos-app/astakos/im/views.py
snf-astakos-app/conf/20-snf-astakos-app-settings.conf

@@@ -89,14 -89,8 +89,18 @@@ ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJEC
  ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT '%s alpha2 testing account activated (%%(user)s)' % SITENAME                    Account activation helpdesk notification email subject
  ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT          'Email change on %s alpha2 testing' % SITENAME                                  Email change subject               
  ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT        'Password reset on %s alpha2 testing' % SITENAME                                Password change email subject
++
 +ASTAKOS_QUOTA_HOLDER_URL                    ''                                                                              The quota holder URI
 +                                                                                                                    e.g. ``http://localhost:8080/api/quotaholder/v``
 +ASTAKOS_SERVICES                            {'cyclades': {'url':'https://node1.example.com/ui/', 'quota': {'vm': 2}},       Cloud service default url and quota      
 +                                            'pithos+':  {'url':'https://node2.example.com/ui/', 'quota': {                  
 +                                            'diskspace': 50 * 1024 * 1024 * 1024}}})                                        
 +ASTAKOS_AQUARIUM_URL                        ''                                                                              The billing (aquarium) URI
 +                                                                                                                    e.g. ``http://localhost:8888/user``
 +ASTAKOS_PAGINATE_BY                         10                                                                              Number of object to be displayed per page
++
+ ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN          True                                                                            Enforce token renewal on password change/reset. If set to False, user can optionally decide
+                                                                                                                             whether to renew the token or not.
  =========================================== =============================================================================   ===========================================================================================
  
  Administrator functions
@@@ -34,27 -34,24 +34,27 @@@ from urlparse import urljoi
  
  from django import forms
  from django.utils.translation import ugettext as _
 -from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, \
 -    PasswordResetForm, PasswordChangeForm, SetPasswordForm
 +from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
-                                        PasswordResetForm, PasswordChangeForm
-                                        )
++                                       PasswordResetForm, PasswordChangeForm,
++                                       SetPasswordForm)
  from django.core.mail import send_mail
  from django.contrib.auth.tokens import default_token_generator
  from django.template import Context, loader
  from django.utils.http import int_to_base36
  from django.core.urlresolvers import reverse
 -from django.utils.functional import lazy
  from django.utils.safestring import mark_safe
 -from django.contrib import messages
  from django.utils.encoding import smart_str
 -
 -from astakos.im.models import AstakosUser, Invitation, get_latest_terms, EmailChange
 -from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, \
 -    BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, \
 -    RECAPTCHA_ENABLED, LOGGING_LEVEL, PASSWORD_RESET_EMAIL_SUBJECT, \
 -    NEWPASSWD_INVALIDATE_TOKEN
 +from django.forms.extras.widgets import SelectDateWidget
 +from django.conf import settings
 +
 +from astakos.im.models import (AstakosUser, EmailChange, AstakosGroup,
 +                               Invitation, Membership, GroupKind, Resource,
 +                               get_latest_terms)
 +from astakos.im.settings import (INVITATIONS_PER_LEVEL, BASEURL, SITENAME,
 +                                 RECAPTCHA_PRIVATE_KEY, RECAPTCHA_ENABLED,
 +                                 DEFAULT_CONTACT_EMAIL, LOGGING_LEVEL,
-                                  PASSWORD_RESET_EMAIL_SUBJECT)
++                                 PASSWORD_RESET_EMAIL_SUBJECT, 
++                                 NEWPASSWD_INVALIDATE_TOKEN)
  from astakos.im.widgets import DummyWidget, RecaptchaWidget
  from astakos.im.functions import send_change_email
  
@@@ -526,134 -491,28 +529,160 @@@ class ExtendedPasswordChangeForm(Passwo
              user.save()
          return user
  
 +
 +class AstakosGroupCreationForm(forms.ModelForm):
 +    kind = forms.ModelChoiceField(
 +        queryset=GroupKind.objects.all(),
 +        label="",
 +        widget=forms.HiddenInput()
 +    )
 +    name = forms.URLField()
 +    moderation_enabled = forms.BooleanField(
 +        help_text="Check if you want to approve members participation manually",
 +        required=False   
 +    )
 +    
 +    class Meta:
 +        model = AstakosGroup
 +
 +    def __init__(self, *args, **kwargs):
 +        try:
 +            resources = kwargs.pop('resources')
 +        except KeyError:
 +            resources = {}
 +        super(AstakosGroupCreationForm, self).__init__(*args, **kwargs)
 +        self.fields.keyOrder = ['kind', 'name', 'homepage', 'desc', 'issue_date',
 +                                'expiration_date', 'estimated_participants',
 +                                'moderation_enabled']
 +        for id, r in resources.iteritems():
 +            self.fields['resource_%s' % id] = forms.IntegerField(
 +                label=r,
 +                required=False,
 +                help_text=_('Leave it blank for no additional quota.')
 +            )
 +
 +    def resources(self):
 +        for name, value in self.cleaned_data.items():
 +            prefix, delimiter, suffix = name.partition('resource_')
 +            if suffix:
 +                # yield only those having a value
 +                if not value:
 +                    continue
 +                yield (suffix, value)
 +
 +class AstakosGroupUpdateForm(forms.ModelForm):
 +    class Meta:
 +        model = AstakosGroup
 +        fields = ('homepage', 'desc')
 +
 +class AddGroupMembersForm(forms.Form):
 +    q = forms.CharField(max_length=800, widget=forms.Textarea, label=_('Search users'),
 +                        help_text=_('Add comma separated user emails'),
 +                        required=True)
 +    
 +    def clean(self):
 +        q = self.cleaned_data.get('q') or ''
 +        users = q.split(',')
 +        users = list(u.strip() for u in users if u)
 +        db_entries = AstakosUser.objects.filter(email__in=users)
 +        unknown = list(set(users) - set(u.email for u in db_entries))
 +        if unknown:
 +            raise forms.ValidationError(
 +                _('Unknown users: %s' % ','.join(unknown)))
 +        self.valid_users = db_entries
 +        return self.cleaned_data
 +    
 +    def get_valid_users(self):
 +        """Should be called after form cleaning"""
 +        try:
 +            return self.valid_users
 +        except:
 +            return ()
 +
 +
 +class AstakosGroupSearchForm(forms.Form):
 +    q = forms.CharField(max_length=200, label='Search group')
 +
 +class TimelineForm(forms.Form):
 +#    entity = forms.CharField(
 +#        widget=forms.HiddenInput(), label='')
 +    entity = forms.ModelChoiceField(
 +        queryset=AstakosUser.objects.filter(is_active = True)
 +    )
 +    resource = forms.ModelChoiceField(
 +        queryset=Resource.objects.all()
 +    )
 +    start_date = forms.DateTimeField()
 +    end_date = forms.DateTimeField()
 +    details = forms.BooleanField(required=False, label="Detailed Listing")
 +    operation = forms.ChoiceField(
 +                        label   = 'Charge Method',
 +                        choices = ( ('',                '-------------'),
 +                                    ('charge_usage',    'Charge Usage'),
 +                                    ('charge_traffic',  'Charge Traffic'), )
 +                )
 +    def clean(self):
 +        super(TimelineForm, self).clean()
 +        d = self.cleaned_data
 +        if 'resource' in d:
 +            d['resource'] = str(d['resource'])
 +        if 'start_date' in d:
 +            d['start_date'] = d['start_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
 +        if 'end_date' in d:
 +            d['end_date'] = d['end_date'].strftime("%Y-%m-%dT%H:%M:%S.%f")[:24]
 +      if 'entity' in d:
 +            d['entity'] = d['entity'].email 
 +        return d
 +
 +class AstakosGroupSortForm(forms.Form):
 +    sort_by = forms.ChoiceField(label='Sort by',
 +                                choices=(('groupname', 'Name'),
 +                                         ('kindname', 'Type'),
 +                                         ('issue_date', 'Issue Date'),
 +                                         ('expiration_date', 'Expiration Date'),
 +                                         ('approved_members_num', 'Participants'),
 +                                         ('is_enabled', 'Status'),
 +                                         ('moderation_enabled', 'Moderation'),
 +                                         ('membership_status','Enrollment Status')
 +                                         ),
 +                                required=False)
 +
 +class MembersSortForm(forms.Form):
 +    sort_by = forms.ChoiceField(label='Sort by',
 +                                choices=(('person__email', 'User Id'),
 +                                         ('person__first_name', 'Name'),
 +                                         ('date_joined', 'Status')
 +                                         ),
 +                                required=False)
 +
 +class PickResourceForm(forms.Form):
 +    resource = forms.ModelChoiceField(
 +        queryset=Resource.objects.select_related().all()
 +    )
-     resource.widget.attrs["onchange"]="this.form.submit()"
++    resource.widget.attrs["onchange"]="this.form.submit()"
++
+ class ExtendedSetPasswordForm(SetPasswordForm):
+     """
+     Extends SetPasswordForm by enabling user
+     to optionally renew also the token.
+     """
+     if not NEWPASSWD_INVALIDATE_TOKEN:
+         renew = forms.BooleanField(label='Renew token', required=False,
+                                    initial=True,
+                                    help_text='Unsetting this may result in security risk.')
+     
+     def __init__(self, user, *args, **kwargs):
+         super(ExtendedSetPasswordForm, self).__init__(user, *args, **kwargs)
+     
+     def save(self, commit=True):
+         user = super(ExtendedSetPasswordForm, self).save(commit=False)
+         if NEWPASSWD_INVALIDATE_TOKEN or self.cleaned_data.get('renew'):
+             try:
+                 user = AstakosUser.objects.get(id=user.id)
+             except AstakosUser.DoesNotExist:
+                 pass
+             else:
+                 user.renew_token()
+         if commit:
+             user.save()
+         return user
@@@ -120,36 -118,5 +120,21 @@@ EMAIL_CHANGE_EMAIL_SUBJECT = getattr(se
  PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
          'Password reset on %s alpha2 testing' % SITENAME)
  
- # Configurable email subjects
- INVITATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_INVITATION_EMAIL_SUBJECT',
-         'Invitation to %s alpha2 testing' % SITENAME)
- GREETING_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_GREETING_EMAIL_SUBJECT',
-         'Welcome to %s alpha2 testing' % SITENAME)
- FEEDBACK_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_FEEDBACK_EMAIL_SUBJECT',
-         'Feedback from %s alpha2 testing' % SITENAME)
- VERIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_VERIFICATION_EMAIL_SUBJECT',
-         '%s alpha2 testing account activation is needed' % SITENAME)
- ADMIN_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_ADMIN_NOTIFICATION_EMAIL_SUBJECT',
-         '%s alpha2 testing account created (%%(user)s)' % SITENAME)
- HELPDESK_NOTIFICATION_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_HELPDESK_NOTIFICATION_EMAIL_SUBJECT',
-         '%s alpha2 testing account activated (%%(user)s)' % SITENAME)
- EMAIL_CHANGE_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT',
-         'Email change on %s alpha2 testing' % SITENAME)
- PASSWORD_RESET_EMAIL_SUBJECT = getattr(settings, 'ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT',
-         'Password reset on %s alpha2 testing' % SITENAME)
 +# Set the quota holder component URI
 +QUOTA_HOLDER_URL = getattr(settings, 'ASTAKOS_QUOTA_HOLDER_URL', '')
 +
 +# Set the cloud service properties
 +SERVICES = getattr(settings, 'ASTAKOS_SERVICES',
 +                   {'cyclades': {'url':'https://node1.example.com/ui/',
 +                                 'quota': {'vm': 2}},
 +                    'pithos+':  {'url':'https://node2.example.com/ui/',
 +                                 'quota': {'diskspace': 50 * 1024 * 1024 * 1024}}})
 +
 +# Set the billing URI
 +AQUARIUM_URL = getattr(settings, 'ASTAKOS_AQUARIUM_URL', '')
 +
 +# Set how many objects should be displayed per page
- PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 8)
++PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 8)
++
+ # Enforce token renewal on password change/reset
+ NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)
@@@ -44,10 -48,10 +45,11 @@@ from astakos.im.settings import RATELIM
  
  from ratelimit.decorators import ratelimit
  
 -retries = RATELIMIT_RETRIES_ALLOWED-1
 -rate = str(retries)+'/m'
 +retries = RATELIMIT_RETRIES_ALLOWED - 1
 +rate = str(retries) + '/m'
 +
  
+ @require_http_methods(["GET", "POST"])
  @csrf_exempt
  @requires_anonymous
  @ratelimit(field='username', method='POST', rate=rate)
@@@ -37,8 -39,10 +37,9 @@@ from django.utils.http import urlencod
  from django.contrib.auth import authenticate
  from django.http import HttpResponse, HttpResponseBadRequest
  from django.core.exceptions import ValidationError
+ from django.views.decorators.http import require_http_methods
  
 -from urllib import quote
 -from urlparse import urlunsplit, urlsplit, urlparse, parse_qsl
 +from urlparse import urlunsplit, urlsplit, parse_qsl
  
  from astakos.im.settings import COOKIE_NAME, COOKIE_DOMAIN
  from astakos.im.util import set_cookie
@@@ -48,7 -52,7 +49,8 @@@ import loggin
  
  logger = logging.getLogger(__name__)
  
 +
+ @require_http_methods(["GET", "POST"])
  def login(request):
      """
      If there is no ``next`` request parameter redirects to astakos index page
@@@ -35,10 -35,12 +35,11 @@@ from django.http import HttpResponseBad
  from django.utils.translation import ugettext as _
  from django.contrib import messages
  from django.template import RequestContext
+ from django.views.decorators.http import require_http_methods
  
 -from astakos.im.util import prepare_response, get_context, get_invitation
 +from astakos.im.util import prepare_response, get_context
  from astakos.im.views import requires_anonymous, render_response
 -from astakos.im.settings import DEFAULT_USER_LEVEL
 -from astakos.im.models import AstakosUser, Invitation, AdditionalMail
 +from astakos.im.models import AstakosUser
  from astakos.im.forms import LoginForm
  from astakos.im.activation_backends import get_backend, SimpleBackend
  
@@@ -54,14 -55,11 +55,15 @@@ class Tokens
      SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
      SHIB_MAIL = "HTTP_SHIB_MAIL"
  
 +
+ @require_http_methods(["GET", "POST"])
  @requires_anonymous
 -def login(request,  backend=None, on_login_template='im/login.html', on_creation_template='im/third_party_registration.html', extra_context={}):
 +def login(request, backend=None, on_login_template='im/login.html',
 +          on_creation_template='im/third_party_registration.html',
 +          extra_context=None
 +          ):
      tokens = request.META
 -    
 +
      try:
          eppn = tokens[Tokens.SHIB_EPPN]
      except KeyError:
          realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
      else:
          return HttpResponseBadRequest("Missing user name in request")
 -    
 +
      affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
      email = tokens.get(Tokens.SHIB_MAIL, None)
 -    
 +
      try:
 -        user = AstakosUser.objects.get(provider='shibboleth', third_party_identifier=eppn)
 +        user = AstakosUser.objects.get(provider='shibboleth',
-                                        third_party_identifier=eppn
-                                        )
++                                       third_party_identifier=eppn)
          if user.is_active:
              return prepare_response(request,
                                      user,
          try:
              if not backend:
                  backend = get_backend(request)
 -            form = backend.get_signup_form(provider='shibboleth', instance=user)
 +            form = backend.get_signup_form(
 +                provider='shibboleth', instance=user)
          except Exception, e:
 -            form = SimpleBackend(request).get_signup_form(provider='shibboleth', instance=user)
 -            messages.add_message(request, messages.ERROR, e)
 +            form = SimpleBackend(request).get_signup_form(
 +                provider='shibboleth',
-                 instance=user
-             )
++                instance=user)
 +            messages.error(request, e)
          return render_response(on_creation_template,
 -                               signup_form = form,
 -                               provider = 'shibboleth',
 -                               context_instance=get_context(request, extra_context))
 +                               signup_form=form,
 +                               provider='shibboleth',
-                                context_instance=get_context(
-                                request,
-                                extra_context
-                                )
-                                )
++                               context_instance=get_context(request,
++                                                            extra_context))
  # interpreted as representing official policies, either expressed
  # or implied, of GRNET S.A.
  
 -from django.conf.urls.defaults import patterns, include, url
 -from django.contrib.auth.views import password_change
 +from django.conf.urls.defaults import patterns, url
  
- from astakos.im.forms import ExtendedPasswordResetForm, ExtendedPasswordChangeForm, LoginForm
+ from astakos.im.forms import (ExtendedPasswordResetForm,
+                               ExtendedPasswordChangeForm,
+                               ExtendedSetPasswordForm, LoginForm)
  from astakos.im.settings import IM_MODULES, INVITATIONS_ENABLED, EMAILCHANGE_ENABLED
  
  urlpatterns = patterns('astakos.im.views',
@@@ -98,23 -65,19 +100,19 @@@ urlpatterns += patterns('astakos.im.tar
  
  if 'local' in IM_MODULES:
      urlpatterns += patterns('astakos.im.target',
 -        url(r'^local/?$', 'local.login')
 -    )
 +                            url(r'^local/?$', 'local.login')
 +                            )
      urlpatterns += patterns('django.contrib.auth.views',
-                             url(r'^local/password_reset/?$', 'password_reset',
-                                 {'email_template_name': 'registration/password_email.txt',
-                                  'password_reset_form': ExtendedPasswordResetForm}),
-                             url(r'^local/password_reset_done/?$',
-                                 'password_reset_done'),
-                             url(
-                                 r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/?$',
-                             'password_reset_confirm'),
-                             url(r'^local/password/reset/complete/?$',
-                                 'password_reset_complete'),
-                             url(
-                             r'^password_change/?$', 'password_change', {'post_change_redirect': 'profile',
-                                                                         'password_change_form': ExtendedPasswordChangeForm})
-                             )
+         url(r'^local/password_reset/?$', 'password_reset',
+          {'email_template_name':'registration/password_email.txt',
+           'password_reset_form':ExtendedPasswordResetForm}),
+         url(r'^local/password_reset_done/?$', 'password_reset_done'),
+         url(r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/?$',
+          'password_reset_confirm', {'set_password_form':ExtendedSetPasswordForm}),
+         url(r'^local/password/reset/complete/?$', 'password_reset_complete'),
+         url(r'^password_change/?$', 'password_change', {'post_change_redirect':'profile',
+             'password_change_form':ExtendedPasswordChangeForm})
+     )
  
  if INVITATIONS_ENABLED:
      urlpatterns += patterns('astakos.im.views',
Simple merge
  # 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.core.mail import send_mail
 -from django.http import HttpResponse, HttpResponseBadRequest
 -from django.shortcuts import redirect
 -from django.template.loader import render_to_string
 -from django.utils.translation import ugettext as _
 -from django.core.urlresolvers import reverse
 -from django.contrib.auth.decorators import login_required
  from django.contrib import messages
 -from django.db import transaction
 -from django.utils.http import urlencode
 -from django.http import HttpResponseRedirect, HttpResponseBadRequest
 -from django.db.utils import IntegrityError
 +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.urlresolvers import reverse
 +from django.db import transaction
  from django.db.models import Q
++<<<<<<< HEAD
 +from django.db.utils import IntegrityError
 +from django.forms.fields import URLField
 +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, \
 +    HttpResponseRedirect, HttpResponseBadRequest, Http404
 +from django.shortcuts import redirect
 +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.xheaders import populate_xheaders
 +
 +from astakos.im.models import (
 +    AstakosUser, ApprovalTerms, AstakosGroup, Resource,
 +    EmailChange, GroupKind, Membership, AstakosGroupQuota)
+ from django.views.decorators.http import require_http_methods
 -from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
  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, PickResourceForm)
 +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__)
  
@@@ -137,9 -110,9 +140,10 @@@ def signed_terms_required(func)
          return func(request, *args, **kwargs)
      return wrapper
  
 +
+ @require_http_methods(["GET", "POST"])
  @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.
  
      """
      template_name = login_template_name
      if request.user.is_authenticated():
 -        return HttpResponseRedirect(reverse('astakos.im.views.edit_profile'))
 +        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))
 +
  
+ @require_http_methods(["GET", "POST"])
  @login_required
  @signed_terms_required
  @transaction.commit_manually
@@@ -237,18 -212,18 +242,19 @@@ def invite(request, template_name='im/i
      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)
 +
  
+ @require_http_methods(["GET", "POST"])
  @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.
  
                  next = request.POST.get('next')
                  if next:
                      return redirect(next)
 -                msg = _('<p>Profile has been updated successfully</p>')
 -                messages.add_message(request, messages.SUCCESS, msg)
 +                msg = _('Profile has been updated successfully')
 +                messages.success(request, msg)
              except ValueError, ve:
 -                messages.add_message(request, messages.ERROR, 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))
  
 +
 +@transaction.commit_manually
+ @require_http_methods(["GET", "POST"])
 -def signup(request, template_name='im/signup.html', on_success='im/signup_complete.html', extra_context={}, backend=None):
 +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.
  
                  return render_response(on_success,
                                         context_instance=get_context(request, extra_context))
              except SendMailError, e:
 -                status = messages.ERROR
                  message = e.message
 -                messages.add_message(request, status, message)
 +                messages.error(request, message)
 +                transaction.rollback()
              except BaseException, e:
 -                status = messages.ERROR
                  message = _('Something went wrong.')
 -                messages.add_message(request, status, message)
 +                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))
  
 +
+ @require_http_methods(["GET", "POST"])
  @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.
  
              try:
                  send_feedback(msg, data, request.user, email_template_name)
              except SendMailError, e:
 -                message = e.message
 -                status = messages.ERROR
 +                messages.error(request, message)
              else:
                  message = _('Feedback successfully sent')
 -                status = messages.SUCCESS
 -            messages.add_message(request, status, 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))
 +
  
+ @require_http_methods(["GET", "POST"])
 -def logout(request, template='registration/logged_out.html', extra_context={}):
 +@signed_terms_required
 +def logout(request, template='registration/logged_out.html', extra_context=None):
      """
      Wraps `django.contrib.auth.logout` and delete the cookie.
      """
          response['Location'] = LOGOUT_NEXT
          response.status_code = 301
          return response
 -    messages.add_message(request, messages.SUCCESS, _('<p>You have successfully logged out.</p>'))
 +    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
  
 +
+ @require_http_methods(["GET", "POST"])
  @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.
              transaction.rollback()
              return index(request)
  
 +
+ @require_http_methods(["GET", "POST"])
 -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:
          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))
 +
  
+ @require_http_methods(["GET", "POST"])
  @signed_terms_required
  def change_password(request):
      return password_change(request,
 -                            post_change_redirect=reverse('astakos.im.views.edit_profile'),
 -                            password_change_form=ExtendedPasswordChangeForm)
 +                           post_change_redirect=reverse('edit_profile'),
 +                           password_change_form=ExtendedPasswordChangeForm)
  
- @signed_terms_required
+ @require_http_methods(["GET", "POST"])
  @login_required
+ @signed_terms_required
  @transaction.commit_manually
  def change_email(request, activation_key=None,
                   email_template_name='registration/email_change_email.txt',
  #ASTAKOS_EMAIL_CHANGE_EMAIL_SUBJECT = 'Email change on %s alpha2 testing' % SITENAME
  #ASTAKOS_PASSWORD_RESET_EMAIL_SUBJECT = 'Password reset on %s alpha2 testing' % SITENAME
  
 +# Set the quota holder component URI
 +#ASTAKOS_QUOTA_HOLDER_URL = ''
 +
 +# Set the cloud service properties
 +# SERVICES = getattr(settings, 'ASTAKOS_SERVICES',
 +#                    {'cyclades': {'url':'https://node1.example.com/ui/',
 +#                                  'quota': {'vm': 2}},
 +#                     'pithos+':  {'url':'https://node2.example.com/ui/',
 +#                                  'quota': {'diskspace': 50 * 1024 * 1024 * 1024}}})
 +
 +# Set the billing URI
 +#ASTAKOS_AQUARIUM_URL = ''
 +
 +# Set how many objects should be displayed per page
- #PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 10)
++#PAGINATE_BY = getattr(settings, 'ASTAKOS_PAGINATE_BY', 10)
++
+ # Enforce token renewal on password change/reset
+ NEWPASSWD_INVALIDATE_TOKEN = getattr(settings, 'ASTAKOS_NEWPASSWD_INVALIDATE_TOKEN', True)