commit 6e3bb9c8c3bfd570d49a7f0360a6db357e6ecea7
Author: Kostas Papadimitriou <kpap@grnet.gr>
Date:   Thu Dec 12 18:00:25 2013 +0200

    astakos: Change email functionality improvements
    
    - Keep track of account email changes. Do not remove EmailChange objects
      after they've been consumed.
    - Allow user to cancel pending email change.
    - Notify user for pending email changes in profile view.
    - Warn admins in account creation emails if email used during signup process
      has been already used in the past by other users.
    - Additional option ``--email-history`` in ``user-show``. If set, user email
      changes history table is appended to the output of the command.
    - Utility command to display usage history for a specific email address.
    - Additional tests included.

diff --git a/snf-astakos-app/astakos/im/forms.py b/snf-astakos-app/astakos/im/forms.py
index d5f228d..2d7d48c 100644
--- a/snf-astakos-app/astakos/im/forms.py
+++ b/snf-astakos-app/astakos/im/forms.py
@@ -471,12 +471,17 @@ class EmailChangeForm(forms.ModelForm):
              commit=True, **kwargs):
         ec = super(EmailChangeForm, self).save(commit=False, **kwargs)
         ec.user = request.user
+
         # delete pending email changes
         request.user.emailchanges.all().delete()
 
         activation_key = hashlib.sha1(
             str(random()) + smart_str(ec.new_email_address))
+        cancel_key = hashlib.sha1(
+            str(random()) + smart_str("cancel_%s" % ec.new_email_address))
         ec.activation_key = activation_key.hexdigest()
+        ec.cancel_key = activation_key.hexdigest()
+
         if commit:
             ec.save(**kwargs)
         send_change_email(ec, request, email_template_name=email_template_name)
diff --git a/snf-astakos-app/astakos/im/management/commands/email-address-log.py b/snf-astakos-app/astakos/im/management/commands/email-address-log.py
new file mode 100644
index 0000000..ed390fe
--- /dev/null
+++ b/snf-astakos-app/astakos/im/management/commands/email-address-log.py
@@ -0,0 +1,69 @@
+# Copyright 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.core.management.base import CommandError
+from optparse import make_option
+
+from django.db.models import Q
+
+from astakos.im import util
+
+from snf_django.management.commands import SynnefoCommand
+from snf_django.management import utils
+
+import uuid
+
+
+class Command(SynnefoCommand):
+    args = "<email>"
+    help = "Show email address log"
+
+    option_list = SynnefoCommand.option_list + (
+        make_option('--user-field',
+                    action='store',
+                    dest='user_field',
+                    default='uuid',
+                    help="User field to display in user column"),
+    )
+
+    def handle(self, *args, **options):
+        if len(args) != 1:
+            raise CommandError("Please provide an email address")
+
+        email = args[0]
+        fields = ('in use', 'user', 'used from', 'used until')
+        log = util.email_address_log(email,
+                                     user_field=options.get('user_field',
+                                                            'uuid'))
+        utils.pprint_table(self.stdout, log, fields,
+                           options["output_format"], vertical=False)
diff --git a/snf-astakos-app/astakos/im/management/commands/user-show.py b/snf-astakos-app/astakos/im/management/commands/user-show.py
index 9a84834..bbd3bea 100644
--- a/snf-astakos-app/astakos/im/management/commands/user-show.py
+++ b/snf-astakos-app/astakos/im/management/commands/user-show.py
@@ -35,7 +35,8 @@ from django.core.management.base import CommandError
 from optparse import make_option
 
 from django.db.models import Q
-from astakos.im.models import AstakosUser, get_latest_terms, Project
+from astakos.im.models import AstakosUser, get_latest_terms, Project, \
+                              EmailChange
 from astakos.im.quotas import list_user_quotas
 
 from synnefo.lib.ordereddict import OrderedDict
@@ -52,6 +53,11 @@ class Command(SynnefoCommand):
     help = "Show user info"
 
     option_list = SynnefoCommand.option_list + (
+        make_option('--email-history',
+                    action='store_true',
+                    dest='list_email_history',
+                    default=False,
+                    help="List account email changes history"),
         make_option('--quota',
                     action='store_true',
                     dest='list_quotas',
@@ -153,6 +159,24 @@ class Command(SynnefoCommand):
                                        options["output_format"],
                                        title="Project Memberships")
 
+            if options["list_email_history"] and user.is_accepted:
+                labels = ('email', 'date_set', 'replaced')
+                activation_email = [user.accepted_email, user.moderated_at,
+                                    None]
+                consumed_emails = EmailChange.objects.consumed()
+                emails = list(consumed_emails.filter(user=user).values_list(
+                    'new_email_address', 'consumed_at',
+                    'replaced_email_address'))
+                emails.insert(0, activation_email)
+                if user.email_change_is_pending():
+                    change = user.get_pending_email_change()
+                    emails.append([u"%s" % change.new_email_address,
+                                   "pending", None])
+                self.stdout.write("\n")
+                utils.pprint_table(self.stdout, emails, labels,
+                                   options["output_format"],
+                                   title="Email history")
+
 
 def memberships(user):
     ms = user.projectmembership_set.all()
diff --git a/snf-astakos-app/astakos/im/messages.py b/snf-astakos-app/astakos/im/messages.py
index b393ac3..ae693dd 100644
--- a/snf-astakos-app/astakos/im/messages.py
+++ b/snf-astakos-app/astakos/im/messages.py
@@ -78,6 +78,7 @@ FEEDBACK_SENT = (
     'Thank you for contacting us. We will process your message carefully '
     'and get back to you.')
 EMAIL_CHANGED = 'The email of your account changed successfully.'
+INVALID_CHANGE_EMAIL_ACTIVATION_KEY = "Invalid change email url."
 EMAIL_CHANGE_REGISTERED = (
     'Your request for changing your email has been registered successfully. '
     'A verification email will be sent to your new address.')
@@ -102,6 +103,11 @@ PROJECT_MAX_PARTICIPANT_NUMBER_REACHED = (
     'This Project reached its maximum number of members. No other member '
     'can be added.')
 NO_APPROVAL_TERMS = 'There are no terms of service to approve.'
+PENDING_EMAIL_CHANGE_WARNING = ('A request for an email change (%(email)s) '
+                                'is pending for your profile<br />'
+                                '<a href="%(cancel_url)s">Cancel pending '
+                                'request</a>')
+EMAIL_CHANGE_REQUEST_CANCELED = 'Email change request canceled successfully.'
 PENDING_EMAIL_CHANGE_REQUEST = (
     'It seems there is already a pending request for an email change. '
     'Submitting a new request to change your email will cancel all previous '
diff --git a/snf-astakos-app/astakos/im/migrations/0062_auto__add_field_emailchange_replaced_email_address__add_field_emailcha.py b/snf-astakos-app/astakos/im/migrations/0062_auto__add_field_emailchange_replaced_email_address__add_field_emailcha.py
new file mode 100644
index 0000000..404671e
--- /dev/null
+++ b/snf-astakos-app/astakos/im/migrations/0062_auto__add_field_emailchange_replaced_email_address__add_field_emailcha.py
@@ -0,0 +1,328 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Removing unique constraint on 'EmailChange', fields ['user']
+        db.delete_unique('im_emailchange', ['user_id'])
+
+        # Adding field 'EmailChange.replaced_email_address'
+        db.add_column('im_emailchange', 'replaced_email_address', self.gf('django.db.models.fields.EmailField')(default=None, max_length=75, null=True, blank=True), keep_default=False)
+
+        # Adding field 'EmailChange.is_consumed'
+        db.add_column('im_emailchange', 'is_consumed', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
+
+        # Adding field 'EmailChange.consumed_at'
+        db.add_column('im_emailchange', 'consumed_at', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'EmailChange.replaced_email_address'
+        db.delete_column('im_emailchange', 'replaced_email_address')
+
+        # Deleting field 'EmailChange.is_consumed'
+        db.delete_column('im_emailchange', 'is_consumed')
+
+        # Deleting field 'EmailChange.consumed_at'
+        db.delete_column('im_emailchange', 'consumed_at')
+
+        # Adding unique constraint on 'EmailChange', fields ['user']
+        db.create_unique('im_emailchange', ['user_id'])
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 13, 28, 22, 687483)'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 13, 28, 22, 687417)'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'im.additionalmail': {
+            'Meta': {'object_name': 'AdditionalMail'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.approvalterms': {
+            'Meta': {'object_name': 'ApprovalTerms'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.astakosuser': {
+            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+            'accepted_email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'accepted_policy': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_rejected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'moderated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'moderated_data': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+            'rejected_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'verification_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}),
+            'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.astakosuserauthprovider': {
+            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'capacity': ('django.db.models.fields.BigIntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.authproviderpolicyprofile': {
+            'Meta': {'ordering': "['priority']", 'object_name': 'AuthProviderPolicyProfile'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['auth.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_exclusive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'policy_add': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_automoderate': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_create': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'policy_login': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_remove': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_required': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_switch': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.chain': {
+            'Meta': {'object_name': 'Chain'},
+            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.component': {
+            'Meta': {'object_name': 'Component'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'base_url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'})
+        },
+        'im.emailchange': {
+            'Meta': {'object_name': 'EmailChange'},
+            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'consumed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'replaced_email_address': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.endpoint': {
+            'Meta': {'object_name': 'Endpoint'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'endpoints'", 'to': "orm['im.Service']"})
+        },
+        'im.endpointdata': {
+            'Meta': {'unique_together': "(('endpoint', 'key'),)", 'object_name': 'EndpointData'},
+            'endpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data'", 'to': "orm['im.Endpoint']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'im.invitation': {
+            'Meta': {'object_name': 'Invitation'},
+            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        'im.pendingthirdpartyuser': {
+            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'im.project': {
+            'Meta': {'object_name': 'Project'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_column': "'id'"}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True', 'null': 'True', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'im.projectapplication': {
+            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
+            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
+            'chain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'chained_apps'", 'db_column': "'chain'", 'to': "orm['im.Project']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
+            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
+            'response': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'response_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'responded_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'response_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'waive_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'waived_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'waive_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'waive_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.projectlock': {
+            'Meta': {'object_name': 'ProjectLock'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.projectlog': {
+            'Meta': {'object_name': 'ProjectLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.Project']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectmembership': {
+            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'im.projectmembershiplog': {
+            'Meta': {'object_name': 'ProjectMembershipLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'membership': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.ProjectMembership']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectresourcegrant': {
+            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'member_capacity': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
+            'project_capacity': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        'im.resource': {
+            'Meta': {'object_name': 'Resource'},
+            'api_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'service_origin': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'service_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'ui_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'uplimit': ('django.db.models.fields.BigIntegerField', [], {'default': '0'})
+        },
+        'im.service': {
+            'Meta': {'object_name': 'Service'},
+            'component': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Component']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.sessioncatalog': {
+            'Meta': {'object_name': 'SessionCatalog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.usersetting': {
+            'Meta': {'unique_together': "(('user', 'setting'),)", 'object_name': 'UserSetting'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'value': ('django.db.models.fields.IntegerField', [], {})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0063_auto__add_field_emailchange_cancel_key.py b/snf-astakos-app/astakos/im/migrations/0063_auto__add_field_emailchange_cancel_key.py
new file mode 100644
index 0000000..b125a34
--- /dev/null
+++ b/snf-astakos-app/astakos/im/migrations/0063_auto__add_field_emailchange_cancel_key.py
@@ -0,0 +1,311 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding field 'EmailChange.cancel_key'
+        db.add_column('im_emailchange', 'cancel_key', self.gf('django.db.models.fields.CharField')(default=None, max_length=40, null=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'EmailChange.cancel_key'
+        db.delete_column('im_emailchange', 'cancel_key')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 14, 55, 27, 416068)'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 14, 55, 27, 416003)'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'im.additionalmail': {
+            'Meta': {'object_name': 'AdditionalMail'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.approvalterms': {
+            'Meta': {'object_name': 'ApprovalTerms'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.astakosuser': {
+            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+            'accepted_email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'accepted_policy': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_rejected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'moderated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'moderated_data': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+            'rejected_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'verification_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}),
+            'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.astakosuserauthprovider': {
+            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'capacity': ('django.db.models.fields.BigIntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.authproviderpolicyprofile': {
+            'Meta': {'ordering': "['priority']", 'object_name': 'AuthProviderPolicyProfile'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['auth.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_exclusive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'policy_add': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_automoderate': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_create': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'policy_login': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_remove': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_required': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_switch': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.chain': {
+            'Meta': {'object_name': 'Chain'},
+            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.component': {
+            'Meta': {'object_name': 'Component'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'base_url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'})
+        },
+        'im.emailchange': {
+            'Meta': {'object_name': 'EmailChange'},
+            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'cancel_key': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '40', 'null': 'True'}),
+            'consumed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'replaced_email_address': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.endpoint': {
+            'Meta': {'object_name': 'Endpoint'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'endpoints'", 'to': "orm['im.Service']"})
+        },
+        'im.endpointdata': {
+            'Meta': {'unique_together': "(('endpoint', 'key'),)", 'object_name': 'EndpointData'},
+            'endpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data'", 'to': "orm['im.Endpoint']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'im.invitation': {
+            'Meta': {'object_name': 'Invitation'},
+            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        'im.pendingthirdpartyuser': {
+            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'im.project': {
+            'Meta': {'object_name': 'Project'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_column': "'id'"}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True', 'null': 'True', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'im.projectapplication': {
+            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
+            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
+            'chain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'chained_apps'", 'db_column': "'chain'", 'to': "orm['im.Project']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
+            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
+            'response': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'response_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'responded_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'response_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'waive_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'waived_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'waive_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'waive_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.projectlock': {
+            'Meta': {'object_name': 'ProjectLock'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.projectlog': {
+            'Meta': {'object_name': 'ProjectLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.Project']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectmembership': {
+            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'im.projectmembershiplog': {
+            'Meta': {'object_name': 'ProjectMembershipLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'membership': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.ProjectMembership']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectresourcegrant': {
+            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'member_capacity': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
+            'project_capacity': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        'im.resource': {
+            'Meta': {'object_name': 'Resource'},
+            'api_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'service_origin': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'service_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'ui_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'uplimit': ('django.db.models.fields.BigIntegerField', [], {'default': '0'})
+        },
+        'im.service': {
+            'Meta': {'object_name': 'Service'},
+            'component': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Component']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.sessioncatalog': {
+            'Meta': {'object_name': 'SessionCatalog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.usersetting': {
+            'Meta': {'unique_together': "(('user', 'setting'),)", 'object_name': 'UserSetting'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'value': ('django.db.models.fields.IntegerField', [], {})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0064_email_change_cancel_keys.py b/snf-astakos-app/astakos/im/migrations/0064_email_change_cancel_keys.py
new file mode 100644
index 0000000..bd57452
--- /dev/null
+++ b/snf-astakos-app/astakos/im/migrations/0064_email_change_cancel_keys.py
@@ -0,0 +1,317 @@
+# encoding: utf-8
+import datetime
+import hashlib
+
+from random import random
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+from django.utils.encoding import smart_str
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        email_changes = orm.EmailChange.objects.filter()
+        for email_change in email_changes:
+            sha1 = hashlib.sha1
+            rnd = str(random())
+            key = smart_str("cancel_%s" % email_change.new_email_address)
+            email_change.cancel_key = sha1(rnd + key).hexdigest()
+            email_change.save()
+
+
+    def backwards(self, orm):
+        pass
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 14, 58, 2, 791889)'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 14, 58, 2, 791827)'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'im.additionalmail': {
+            'Meta': {'object_name': 'AdditionalMail'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.approvalterms': {
+            'Meta': {'object_name': 'ApprovalTerms'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.astakosuser': {
+            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+            'accepted_email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'accepted_policy': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_rejected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'moderated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'moderated_data': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+            'rejected_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'verification_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}),
+            'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.astakosuserauthprovider': {
+            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'capacity': ('django.db.models.fields.BigIntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.authproviderpolicyprofile': {
+            'Meta': {'ordering': "['priority']", 'object_name': 'AuthProviderPolicyProfile'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['auth.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_exclusive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'policy_add': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_automoderate': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_create': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'policy_login': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_remove': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_required': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_switch': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.chain': {
+            'Meta': {'object_name': 'Chain'},
+            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.component': {
+            'Meta': {'object_name': 'Component'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'base_url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'})
+        },
+        'im.emailchange': {
+            'Meta': {'object_name': 'EmailChange'},
+            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'cancel_key': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '40', 'null': 'True'}),
+            'consumed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'replaced_email_address': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.endpoint': {
+            'Meta': {'object_name': 'Endpoint'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'endpoints'", 'to': "orm['im.Service']"})
+        },
+        'im.endpointdata': {
+            'Meta': {'unique_together': "(('endpoint', 'key'),)", 'object_name': 'EndpointData'},
+            'endpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data'", 'to': "orm['im.Endpoint']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'im.invitation': {
+            'Meta': {'object_name': 'Invitation'},
+            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        'im.pendingthirdpartyuser': {
+            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'im.project': {
+            'Meta': {'object_name': 'Project'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_column': "'id'"}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True', 'null': 'True', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'im.projectapplication': {
+            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
+            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
+            'chain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'chained_apps'", 'db_column': "'chain'", 'to': "orm['im.Project']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
+            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
+            'response': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'response_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'responded_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'response_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'waive_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'waived_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'waive_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'waive_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.projectlock': {
+            'Meta': {'object_name': 'ProjectLock'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.projectlog': {
+            'Meta': {'object_name': 'ProjectLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.Project']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectmembership': {
+            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'im.projectmembershiplog': {
+            'Meta': {'object_name': 'ProjectMembershipLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'membership': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.ProjectMembership']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectresourcegrant': {
+            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'member_capacity': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
+            'project_capacity': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        'im.resource': {
+            'Meta': {'object_name': 'Resource'},
+            'api_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'service_origin': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'service_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'ui_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'uplimit': ('django.db.models.fields.BigIntegerField', [], {'default': '0'})
+        },
+        'im.service': {
+            'Meta': {'object_name': 'Service'},
+            'component': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Component']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.sessioncatalog': {
+            'Meta': {'object_name': 'SessionCatalog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.usersetting': {
+            'Meta': {'unique_together': "(('user', 'setting'),)", 'object_name': 'UserSetting'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'value': ('django.db.models.fields.IntegerField', [], {})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/migrations/0065_auto__chg_field_emailchange_cancel_key__add_unique_emailchange_cancel_.py b/snf-astakos-app/astakos/im/migrations/0065_auto__chg_field_emailchange_cancel_key__add_unique_emailchange_cancel_.py
new file mode 100644
index 0000000..8c0f5b4
--- /dev/null
+++ b/snf-astakos-app/astakos/im/migrations/0065_auto__chg_field_emailchange_cancel_key__add_unique_emailchange_cancel_.py
@@ -0,0 +1,323 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Changing field 'EmailChange.cancel_key'
+        db.alter_column('im_emailchange', 'cancel_key', self.gf('django.db.models.fields.CharField')(default='', unique=True, max_length=40))
+
+        # Adding index on 'EmailChange', fields ['cancel_key']
+        db.create_index('im_emailchange', ['cancel_key'])
+
+        # Adding unique constraint on 'EmailChange', fields ['cancel_key']
+        db.create_unique('im_emailchange', ['cancel_key'])
+
+
+    def backwards(self, orm):
+        
+        # Removing unique constraint on 'EmailChange', fields ['cancel_key']
+        db.delete_unique('im_emailchange', ['cancel_key'])
+
+        # Removing index on 'EmailChange', fields ['cancel_key']
+        db.delete_index('im_emailchange', ['cancel_key'])
+
+        # Changing field 'EmailChange.cancel_key'
+        db.alter_column('im_emailchange', 'cancel_key', self.gf('django.db.models.fields.CharField')(max_length=40, null=True))
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 16, 1, 26, 544559)'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 12, 12, 16, 1, 26, 544491)'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'im.additionalmail': {
+            'Meta': {'object_name': 'AdditionalMail'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.approvalterms': {
+            'Meta': {'object_name': 'ApprovalTerms'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.astakosuser': {
+            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+            'accepted_email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'accepted_policy': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'deactivated_reason': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True'}),
+            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_rejected': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'moderated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'moderated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'moderated_data': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+            'rejected_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'verification_code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}),
+            'verified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.astakosuserauthprovider': {
+            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'capacity': ('django.db.models.fields.BigIntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.authproviderpolicyprofile': {
+            'Meta': {'ordering': "['priority']", 'object_name': 'AuthProviderPolicyProfile'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['auth.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_exclusive': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'policy_add': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_automoderate': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_create': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True'}),
+            'policy_login': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_remove': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_required': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'policy_switch': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'priority': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authpolicy_profiles'", 'symmetrical': 'False', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.chain': {
+            'Meta': {'object_name': 'Chain'},
+            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.component': {
+            'Meta': {'object_name': 'Component'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'base_url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'})
+        },
+        'im.emailchange': {
+            'Meta': {'object_name': 'EmailChange'},
+            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'cancel_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'consumed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'replaced_email_address': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'requested_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.endpoint': {
+            'Meta': {'object_name': 'Endpoint'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'endpoints'", 'to': "orm['im.Service']"})
+        },
+        'im.endpointdata': {
+            'Meta': {'unique_together': "(('endpoint', 'key'),)", 'object_name': 'EndpointData'},
+            'endpoint': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'data'", 'to': "orm['im.Endpoint']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
+        },
+        'im.invitation': {
+            'Meta': {'object_name': 'Invitation'},
+            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        'im.pendingthirdpartyuser': {
+            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'im.project': {
+            'Meta': {'object_name': 'Project'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.BigIntegerField', [], {'primary_key': 'True', 'db_column': "'id'"}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True', 'null': 'True', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'im.projectapplication': {
+            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
+            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
+            'chain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'chained_apps'", 'db_column': "'chain'", 'to': "orm['im.Project']"}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
+            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
+            'response': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'response_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'responded_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'response_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'waive_actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'waived_apps'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'waive_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'waive_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'im.projectlock': {
+            'Meta': {'object_name': 'ProjectLock'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.projectlog': {
+            'Meta': {'object_name': 'ProjectLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.Project']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectmembership': {
+            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'im.projectmembershiplog': {
+            'Meta': {'object_name': 'ProjectMembershipLog'},
+            'actor': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']", 'null': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date': ('django.db.models.fields.DateTimeField', [], {}),
+            'from_state': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'membership': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'log'", 'to': "orm['im.ProjectMembership']"}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'to_state': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'im.projectresourcegrant': {
+            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'member_capacity': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
+            'project_capacity': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        'im.resource': {
+            'Meta': {'object_name': 'Resource'},
+            'api_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'service_origin': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+            'service_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'ui_visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'uplimit': ('django.db.models.fields.BigIntegerField', [], {'default': '0'})
+        },
+        'im.service': {
+            'Meta': {'object_name': 'Service'},
+            'component': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Component']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.sessioncatalog': {
+            'Meta': {'object_name': 'SessionCatalog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.usersetting': {
+            'Meta': {'unique_together': "(('user', 'setting'),)", 'object_name': 'UserSetting'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'setting': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'value': ('django.db.models.fields.IntegerField', [], {})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py
index f368b47..a70379e 100644
--- a/snf-astakos-app/astakos/im/models.py
+++ b/snf-astakos-app/astakos/im/models.py
@@ -596,6 +596,14 @@ class AstakosUser(User):
     def email_change_is_pending(self):
         return self.emailchanges.count() > 0
 
+    def get_pending_email_change(self):
+        return self.emailchanges.get()
+
+    @property
+    def email_address_log(self):
+        from astakos.im.util import email_address_log
+        return email_address_log(self.email, skip_current=True)
+
     @property
     def status_display(self):
         msg = ""
@@ -609,7 +617,7 @@ class AstakosUser(User):
             msg = "Pending email verification"
         if not self.moderated:
             msg = "Pending moderation"
-        if not self.is_active and self.email_verified:
+        if not self.is_active and self.is_accepted():
             msg = "Accepted/Inactive"
             if self.deactivated_reason:
                 msg += " (%s)" % (self.deactivated_reason)
@@ -1052,6 +1060,17 @@ class Invitation(models.Model):
 
 class EmailChangeManager(models.Manager):
 
+    def get_query_set(self):
+        """
+        Exclude consumed email change objects by default.
+        """
+        qs = super(EmailChangeManager, self).get_query_set()
+        return qs.filter(is_consumed=False)
+
+    def consumed(self):
+        qs = super(EmailChangeManager, self).get_query_set()
+        return qs.filter(is_consumed=True)
+
     @transaction.commit_on_success
     def change_email(self, activation_key):
         """
@@ -1090,7 +1109,9 @@ class EmailChangeManager(models.Manager):
             old_email = user.email
             user.set_email(email_change.new_email_address)
             user.save()
-            email_change.delete()
+            email_change.replaced_email_address = old_email
+            email_change.set_consumed()
+            email_change.save()
             msg = "User %s changed email from %s to %s"
             logger.log(astakos_settings.LOGGING_LEVEL, msg, user.log_display,
                        old_email, user.email)
@@ -1105,23 +1126,45 @@ class EmailChange(models.Model):
         help_text=_('Provide a new email address. Until you verify the new '
                     'address by following the activation link that will be '
                     'sent to it, your old email address will remain active.'))
+    replaced_email_address = models.EmailField(blank=True, null=True,
+                                               default=None)
     user = models.ForeignKey(
-        AstakosUser, unique=True, related_name='emailchanges')
+        AstakosUser, related_name='emailchanges')
     requested_at = models.DateTimeField(auto_now_add=True)
     activation_key = models.CharField(
         max_length=40, unique=True, db_index=True)
+    cancel_key = models.CharField(max_length=40, unique=True, db_index=True)
+    is_consumed = models.BooleanField(default=False)
+    consumed_at = models.DateTimeField(_('Consumption date'),
+                                    null=True, blank=True)
 
     objects = EmailChangeManager()
 
+    def set_consumed(self):
+        self.is_consumed = True
+        self.consumed_at = datetime.now()
+
     def get_url(self):
-        return reverse('email_change_confirm',
-                       kwargs={'activation_key': self.activation_key})
+        return reverse('email_change_confirm', kwargs={
+            'activation_key': self.activation_key
+        })
+
+    def get_cancel_url(self):
+        return reverse('email_change_cancel', kwargs={
+            'cancel_key': self.cancel_key
+        })
 
     def activation_key_expired(self):
         expiration_date = timedelta(
             days=astakos_settings.EMAILCHANGE_ACTIVATION_DAYS)
         return self.requested_at + expiration_date < datetime.now()
 
+    def __unicode__(self):
+        return "%r from:%r to:%r consumed:%r" % (
+            self.__class__, self.new_email_address,
+            self.replaced_email_address, self.consumed_at)
+
+
 
 class AdditionalMail(models.Model):
     """
diff --git a/snf-astakos-app/astakos/im/templates/im/account_activated_notification.txt b/snf-astakos-app/astakos/im/templates/im/account_activated_notification.txt
index fe13ffd..de87a21 100644
--- a/snf-astakos-app/astakos/im/templates/im/account_activated_notification.txt
+++ b/snf-astakos-app/astakos/im/templates/im/account_activated_notification.txt
@@ -8,4 +8,13 @@ Level:             {{user.level}}
 Invitations:       {{user.invitations}}
 Accepted reason:   {{user.accepted_policy}}
 
+{% if user.email_address_log %}
+WARNING: The account email address seems to have already been used in the past:
+
+user uuid                               from                      until
+------------------------------------    ------------------------  -----------------------
+{% for log in user.email_address_log %}{{ log.1 }}    {{ log.2 }}  {{ log.3 }}
+{% endfor %}
+{% endif %}
+
 Authentication provider: {{ user.get_auth_provider|safe }}
diff --git a/snf-astakos-app/astakos/im/templates/im/account_pending_moderation_notification.txt b/snf-astakos-app/astakos/im/templates/im/account_pending_moderation_notification.txt
index bfe3bbe..96c662d 100644
--- a/snf-astakos-app/astakos/im/templates/im/account_pending_moderation_notification.txt
+++ b/snf-astakos-app/astakos/im/templates/im/account_pending_moderation_notification.txt
@@ -7,6 +7,15 @@ Is active:      {{user.is_active}}
 Level:          {{user.level}}
 Invitations:    {{user.invitations}}
 
+{% if user.email_address_log %}
+WARNING: The account email address seems to have already been used in the past:
+
+user uuid                               from                      until
+------------------------------------    ------------------------  -----------------------
+{% for log in user.email_address_log %}{{ log.1 }}    {{ log.2 }}  {{ log.3 }}
+{% endfor %}
+{% endif %}
+
 For its activation you can use the command line tool
 
-snf-manage user-modify {{ user.pk }} --accept
\ No newline at end of file
+snf-manage user-modify {{ user.pk }} --accept
diff --git a/snf-astakos-app/astakos/im/templatetags/filters.py b/snf-astakos-app/astakos/im/templatetags/filters.py
index ace8fbe..8f75516 100644
--- a/snf-astakos-app/astakos/im/templatetags/filters.py
+++ b/snf-astakos-app/astakos/im/templatetags/filters.py
@@ -207,3 +207,12 @@ def resource_grants(project_definition):
         return dict((e[0], e[1]) for e in grants)
     except:
         return {}
+
+
+@register.filter
+def email_address_log(v, max=18, append="..."):
+    length = len(v)
+    if length > max:
+        return v[:max] + append
+    else:
+        return v
diff --git a/snf-astakos-app/astakos/im/tests/auth.py b/snf-astakos-app/astakos/im/tests/auth.py
index 488472e..38f56f5 100644
--- a/snf-astakos-app/astakos/im/tests/auth.py
+++ b/snf-astakos-app/astakos/im/tests/auth.py
@@ -685,11 +685,15 @@ class UserActionsTests(TestCase):
         r = self.client.get(ui_url('email_change'))
         self.assertEqual(r.status_code, 200)
         self.assertFalse(user.email_change_is_pending())
+        self.assertEqual(EmailChange.objects.consumed().count(), 0)
+        self.assertEqual(EmailChange.objects.filter().count(), 0)
 
         # request email change to an existing email fails
         data = {'new_email_address': 'existing@synnefo.org'}
         r = self.client.post(ui_url('email_change'), data)
         self.assertContains(r, messages.EMAIL_USED)
+        self.assertEqual(EmailChange.objects.consumed().count(), 0)
+        self.assertEqual(EmailChange.objects.filter().count(), 0)
 
         # proper email change
         data = {'new_email_address': 'kpap@gmail.com'}
@@ -697,6 +701,8 @@ class UserActionsTests(TestCase):
         self.assertRedirects(r, ui_url('profile'))
         self.assertContains(r, messages.EMAIL_CHANGE_REGISTERED)
         change1 = EmailChange.objects.get()
+        self.assertEqual(EmailChange.objects.consumed().count(), 0)
+        self.assertEqual(EmailChange.objects.filter().count(), 1)
 
         # user sees a warning
         r = self.client.get(ui_url('email_change'))
@@ -704,6 +710,12 @@ class UserActionsTests(TestCase):
         self.assertContains(r, messages.PENDING_EMAIL_CHANGE_REQUEST)
         self.assertTrue(user.email_change_is_pending())
 
+        # also in profile page
+        r = self.client.get(ui_url('profile'))
+        self.assertEqual(r.status_code, 200)
+        self.assertContains(r, messages.PENDING_EMAIL_CHANGE_WARNING[:15])
+        self.assertTrue(user.email_change_is_pending())
+
         # link was sent
         self.assertEqual(len(get_mailbox('kpap@synnefo.org')), 0)
         self.assertEqual(len(get_mailbox('kpap@gmail.com')), 1)
@@ -716,11 +728,18 @@ class UserActionsTests(TestCase):
         self.assertEqual(len(get_mailbox('kpap@synnefo.org')), 0)
         self.assertEqual(len(get_mailbox('kpap@yahoo.com')), 1)
         change2 = EmailChange.objects.get()
+        self.assertEqual(EmailChange.objects.consumed().count(), 0)
+        self.assertEqual(EmailChange.objects.filter().count(), 1)
 
-        r = self.client.get(change1.get_url())
-        self.assertEquals(r.status_code, 404)
+        # invalidated
+        r = self.client.get(change1.get_url(), follow=True)
+        self.assertEquals(r.status_code, 200)
+        self.assertContains(r, messages.INVALID_CHANGE_EMAIL_ACTIVATION_KEY)
         self.client.logout()
+        self.assertEqual(EmailChange.objects.consumed().count(), 0)
+        self.assertEqual(EmailChange.objects.filter().count(), 1)
 
+        # use of another user email change url results to 403 response
         invalid_client = Client()
         r = invalid_client.post(ui_url('local?'),
                                 {'username': 'existing@synnefo.org',
@@ -728,6 +747,7 @@ class UserActionsTests(TestCase):
         r = invalid_client.get(change2.get_url(), follow=True)
         self.assertEquals(r.status_code, 403)
 
+        # consume change url
         r = self.client.post(ui_url('local?next=' + change2.get_url()),
                              {'username': 'kpap@synnefo.org',
                               'password': 'password',
@@ -737,6 +757,32 @@ class UserActionsTests(TestCase):
         user = r.context['request'].user
         self.assertEquals(user.email, 'kpap@yahoo.com')
         self.assertEquals(user.username, 'kpap@yahoo.com')
+        self.assertEqual(EmailChange.objects.consumed().count(), 1)
+        self.assertEqual(EmailChange.objects.filter().count(), 0)
+
+        # request another email change
+        data = {'new_email_address': 'kpap@gmail.com'}
+        # both users request for the same email change
+        r = invalid_client.post(ui_url('email_change'), data, follow=True)
+        r = self.client.post(ui_url('email_change'), data, follow=True)
+        self.assertRedirects(r, ui_url('profile'))
+        self.assertEqual(EmailChange.objects.consumed().count(), 1)
+        # two email changes pending, one for each user
+        self.assertEqual(EmailChange.objects.filter().count(), 2)
+        # first user consumes email change
+        change_email_url = user.emailchanges.get().get_url()
+        self.client.get(change_email_url)
+        consumed = EmailChange.objects.consumed()
+        self.assertEqual(consumed.filter(user__id=user.pk).count(), 2)
+        # one left pending, will be invalidated once accessed, since the
+        # kpap@gmail.com is not available any more
+        self.assertEqual(EmailChange.objects.filter().count(), 1)
+
+        # second user fails to consume
+        second_user = AstakosUser.objects.get(email="existing@synnefo.org")
+        second_change_email_url = second_user.emailchanges.get().get_url()
+        r = invalid_client.get(second_change_email_url, follow=True)
+        self.assertEqual(EmailChange.objects.filter().count(), 0)
 
         self.client.logout()
         r = self.client.post(ui_url('local?next=' + change2.get_url()),
diff --git a/snf-astakos-app/astakos/im/urls.py b/snf-astakos-app/astakos/im/urls.py
index 860e9af..c4f0a27 100644
--- a/snf-astakos-app/astakos/im/urls.py
+++ b/snf-astakos-app/astakos/im/urls.py
@@ -121,6 +121,8 @@ if settings.EMAILCHANGE_ENABLED:
     urlpatterns += patterns(
         'astakos.im.views',
         url(r'^email_change/?$', 'change_email', {}, name='email_change'),
+        url(r'^email_change/cancel/(?P<cancel_key>\w+)?$',
+            'change_email_cancel', {}, name='email_change_cancel'),
         url(r'^email_change/confirm/(?P<activation_key>\w+)/?$',
             'change_email', {},
             name='email_change_confirm'))
diff --git a/snf-astakos-app/astakos/im/util.py b/snf-astakos-app/astakos/im/util.py
index 2c9ce91..edd8cec 100644
--- a/snf-astakos-app/astakos/im/util.py
+++ b/snf-astakos-app/astakos/im/util.py
@@ -36,7 +36,7 @@ import time
 import urllib
 
 from urlparse import urlparse
-from datetime import tzinfo, timedelta
+from datetime import tzinfo, timedelta, datetime
 
 from django.http import HttpResponse, HttpResponseBadRequest, urlencode
 from django.template import RequestContext
@@ -45,8 +45,9 @@ from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 from django.core.exceptions import ValidationError, ObjectDoesNotExist
 from django.utils.translation import ugettext as _
+from django.db.models import Q
 
-from astakos.im.models import AstakosUser, Invitation
+from astakos.im.models import AstakosUser, Invitation, EmailChange
 from astakos.im.functions import login
 from astakos.im import settings
 
@@ -242,6 +243,58 @@ def reserved_email(email):
     return AstakosUser.objects.user_exists(email)
 
 
+def email_address_log(email, skip_current=False, user_field='uuid'):
+    user_value = lambda u: user_field(u) if callable(user_field) else \
+                 getattr(u, user_field)
+    log = []
+    if reserved_email(email) and not skip_current:
+        user = AstakosUser.objects.get_by_identifier(email)
+        email_date = user.moderated_at
+        if user.emailchanges.consumed().count():
+            try:
+                change = user.emailchanges.consumed().order_by('-consumed_at')
+                email_date = change[0].consumed_at
+            except IndexError:
+                email_date = None
+        until_date = None
+        if user.is_accepted():
+            if not skip_current:
+                log.append((True, user_value(user), email_date, until_date))
+        else:
+            status = user.status_display
+            log.append((True, user_value(user), status, None))
+
+    emailq = Q(new_email_address__iexact=email) | \
+             Q(replaced_email_address__iexact=email)
+    changes = EmailChange.objects.consumed().filter(emailq).order_by(
+                                                                'consumed_at')
+    for c in changes:
+        date_from = None
+        date_until = None
+
+        if c.new_email_address.lower() == email.lower():
+            date_from = c.consumed_at
+            date_until = None
+            try:
+                next_change = c.user.emailchanges.filter(
+                    consumed_at__gt=c.consumed_at)[0]
+                date_until = next_change.consumed_at
+            except IndexError:
+                pass
+        else:
+            date_from = c.user.moderated_at
+            date_until = c.consumed_at
+
+        log.append((False, user_value(c.user), date_from, date_until))
+
+    for c in EmailChange.objects.filter(new_email_address__iexact=email):
+        log.append((False, user_value(c.user), "pending", None))
+
+    log = sorted(log, key=lambda k: datetime.now() if not k[2] or \
+                 isinstance(k[2], basestring) else k[2])
+    return log
+
+
 def reserved_verified_email(email):
     return AstakosUser.objects.verified_user_exists(email)
 
diff --git a/snf-astakos-app/astakos/im/views/im.py b/snf-astakos-app/astakos/im/views/im.py
index a4c663b..c784623 100644
--- a/snf-astakos-app/astakos/im/views/im.py
+++ b/snf-astakos-app/astakos/im/views/im.py
@@ -310,6 +310,7 @@ def edit_profile(request, template_name='im/profile.html', extra_context=None):
         instance=request.user,
         session_key=request.session.session_key
     )
+
     extra_context['next'] = request.GET.get('next')
     if request.method == 'POST':
         form = ProfileForm(
@@ -353,6 +354,16 @@ def edit_profile(request, template_name='im/profile.html', extra_context=None):
     user_available_providers = request.user.get_available_auth_providers()
 
     extra_context['services'] = Component.catalog().values()
+
+    if request.method == "GET":
+        if request.user.email_change_is_pending():
+            change_email = request.user.get_pending_email_change()
+            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_WARNING) % {
+                'email': change_email.new_email_address,
+                'cancel_url': change_email.get_cancel_url()
+            }
+            messages.warning(request, msg)
+
     return render_response(template_name,
                            profile_form=form,
                            user_providers=user_providers,
@@ -710,6 +721,21 @@ def approval_terms(request, term_id=None,
                                                             extra_context))
 
 
+@require_http_methods(["GET"])
+@required_auth_methods_assigned(allow_access=True)
+@login_required
+@cookie_fix
+@signed_terms_required
+@transaction.commit_on_success
+def change_email_cancel(request, cancel_key):
+    email_change = get_object_or_404(EmailChange, cancel_key=cancel_key)
+    email_change.delete()
+    msg = _(astakos_messages.EMAIL_CHANGE_REQUEST_CANCELED)
+    messages.success(request, msg)
+    return HttpResponseRedirect(reverse('edit_profile'))
+
+
+
 @require_http_methods(["GET", "POST"])
 @cookie_fix
 @transaction.commit_on_success
@@ -723,15 +749,17 @@ def change_email(request, activation_key=None,
     if not settings.EMAILCHANGE_ENABLED:
         raise PermissionDenied
 
+    objects = EmailChange.objects
     if activation_key:
         try:
             try:
-                email_change = EmailChange.objects.get(
-                    activation_key=activation_key)
+                email_change = objects.get(activation_key=activation_key)
             except EmailChange.DoesNotExist:
-                logger.error("[change-email] Invalid or used activation "
+                messages.error(request,
+                       _(astakos_messages.INVALID_CHANGE_EMAIL_ACTIVATION_KEY))
+                logger.error("[change-email] Invalid change email activation "
                              "code, %s", activation_key)
-                raise Http404
+                return HttpResponseRedirect(reverse('index'))
 
             if (
                 request.user.is_authenticated() and
@@ -741,16 +769,23 @@ def change_email(request, activation_key=None,
                 user = EmailChange.objects.change_email(activation_key)
                 msg = _(astakos_messages.EMAIL_CHANGED)
                 messages.success(request, msg)
-                transaction.commit()
                 return HttpResponseRedirect(reverse('edit_profile'))
             else:
                 logger.error("[change-email] Access from invalid user, %s %s",
                              email_change.user, request.user.log_display)
                 raise PermissionDenied
-        except ValueError, e:
-            messages.error(request, e)
-            transaction.rollback()
-            return HttpResponseRedirect(reverse('index'))
+        except ValueError:
+            # this means that new email which corresponds to this activation url
+            # is no longer available
+
+            # invalidate change email object
+            email_change.delete()
+            logger.error("[change-email] Email change with code '%s' failed. "
+                         "Email address '%s' no longer available",
+                         activation_key, email_change.new_email_address)
+            messages.error(request, astakos_messages.NEW_EMAIL_ADDR_RESERVED)
+            return HttpResponseRedirect(reverse('edit_profile'))
+
 
         return render_response(confirm_template_name,
                                modified_user=user if 'user' in locals()
@@ -758,6 +793,7 @@ def change_email(request, activation_key=None,
                                context_instance=get_context(request,
                                                             extra_context))
 
+    # redirect to login
     if not request.user.is_authenticated():
         path = quote(request.get_full_path())
         url = request.build_absolute_uri(reverse('index'))
@@ -768,7 +804,6 @@ def change_email(request, activation_key=None,
         change = request.user.emailchanges.get()
         if change.activation_key_expired():
             change.delete()
-            transaction.commit()
             return HttpResponseRedirect(reverse('email_change'))
 
     form = EmailChangeForm(request.POST or None)
@@ -776,7 +811,6 @@ def change_email(request, activation_key=None,
         ec = form.save(request, email_template_name, request)
         msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
         messages.success(request, msg)
-        transaction.commit()
         return HttpResponseRedirect(reverse('edit_profile'))
 
     if request.user.email_change_is_pending():
