Revision 34a76cdb

b/snf-astakos-app/astakos/im/forms.py
511 511

  
512 512

  
513 513
class EmailChangeForm(forms.ModelForm):
514

  
514 515
    class Meta:
515 516
        model = EmailChange
516 517
        fields = ('new_email_address',)
......
533 534

  
534 535

  
535 536
class SignApprovalTermsForm(forms.ModelForm):
537

  
536 538
    class Meta:
537 539
        model = AstakosUser
538 540
        fields = ("has_signed_terms",)
......
548 550

  
549 551

  
550 552
class InvitationForm(forms.ModelForm):
553

  
551 554
    username = forms.EmailField(label=_("Email"))
552 555

  
553 556
    def __init__(self, *args, **kwargs):
b/snf-astakos-app/astakos/im/functions.py
260 260

  
261 261
def send_change_email(ec, request, email_template_name='registration/email_change_email.txt'):
262 262
    try:
263
        url = reverse('email_change_confirm',
264
                      kwargs={'activation_key': ec.activation_key})
263
        url = ec.get_url()
265 264
        url = request.build_absolute_uri(url)
266 265
        t = loader.get_template(email_template_name)
267 266
        c = {'url': url, 'site_name': SITENAME}
b/snf-astakos-app/astakos/im/messages.py
72 72
MAX_INVITATION_NUMBER_REACHED   =           'There are no invitations left.'
73 73
GROUP_MAX_PARTICIPANT_NUMBER_REACHED    =   'Group maximum participant number has been reached.'
74 74
NO_APPROVAL_TERMS                       =   'There are no approval terms.'
75
PENDING_EMAIL_CHANGE_REQUEST            =   'There is already a pending change email request.'
75
PENDING_EMAIL_CHANGE_REQUEST            =   'There is already a pending change email request. ' + \
76
                                            'Submiting a new email will cancel any previous requests.'
76 77
OBJECT_CREATED_FAILED                   =   'The %(verbose_name)s creation failed: %(reason)s.'
77 78
GROUP_JOIN_FAILURE                      =   'Failed to join group.'
78 79
GROUPKIND_UNKNOWN                       =   'There is no such a group kind'
b/snf-astakos-app/astakos/im/models.py
570 570
        if q.count() != 0:
571 571
            raise ValidationError({'__all__': [_(astakos_messages.UNIQUE_EMAIL_IS_ACTIVE_CONSTRAIN_ERR)]})
572 572

  
573
    def email_change_is_pending(self):
574
        return self.emailchanges.count() > 0
575

  
573 576
    @property
574 577
    def signed_terms(self):
575 578
        term = get_latest_terms()
......
960 963

  
961 964

  
962 965
class EmailChangeManager(models.Manager):
966

  
963 967
    @transaction.commit_on_success
964 968
    def change_email(self, activation_key):
965 969
        """
......
993 997
                raise ValueError(_(astakos_messages.NEW_EMAIL_ADDR_RESERVED))
994 998
            # update user
995 999
            user = AstakosUser.objects.get(pk=email_change.user_id)
1000
            old_email = user.email
996 1001
            user.email = email_change.new_email_address
997 1002
            user.save()
998 1003
            email_change.delete()
1004
            msg = "User %d changed email from %s to %s" % (user.pk, old_email,
1005
                                                          user.email)
1006
            logger.log(LOGGING_LEVEL, msg)
999 1007
            return user
1000 1008
        except EmailChange.DoesNotExist:
1001 1009
            raise ValueError(_(astakos_messages.INVALID_ACTIVATION_KEY))
......
1005 1013
    new_email_address = models.EmailField(_(u'new e-mail address'),
1006 1014
                                          help_text=_(astakos_messages.EMAIL_CHANGE_NEW_ADDR_HELP))
1007 1015
    user = models.ForeignKey(
1008
        AstakosUser, unique=True, related_name='emailchange_user')
1016
        AstakosUser, unique=True, related_name='emailchanges')
1009 1017
    requested_at = models.DateTimeField(default=datetime.now())
1010 1018
    activation_key = models.CharField(
1011 1019
        max_length=40, unique=True, db_index=True)
1012 1020

  
1013 1021
    objects = EmailChangeManager()
1014 1022

  
1023
    def get_url(self):
1024
        return reverse('email_change_confirm',
1025
                      kwargs={'activation_key': self.activation_key})
1026

  
1015 1027
    def activation_key_expired(self):
1016 1028
        expiration_date = timedelta(days=EMAILCHANGE_ACTIVATION_DAYS)
1017 1029
        return self.requested_at + expiration_date < datetime.now()
b/snf-astakos-app/astakos/im/tests.py
47 47

  
48 48
from astakos.im import messages
49 49

  
50

  
51
astakos_settings.EMAILCHANGE_ENABLED = True
52

  
50 53
class ShibbolethClient(Client):
51 54
    """
52 55
    A shibboleth agnostic client.
......
624 627
        self.assertContains(r, "Password change for this account is not"
625 628
                                " supported")
626 629

  
630
class UserActionsTests(TestCase):
631

  
632
    def setUp(self):
633
        kind = GroupKind.objects.create(name="default")
634
        AstakosGroup.objects.create(name="default", kind=kind)
635

  
636
    def test_email_change(self):
637
        # to test existing email validation
638
        existing_user = get_local_user('existing@grnet.gr')
639

  
640
        # local user
641
        user = get_local_user('kpap@grnet.gr')
642

  
643
        # login as kpap
644
        self.client.login(username='kpap@grnet.gr', password='password')
645
        r = self.client.get('/im/profile', follow=True)
646
        user = r.context['request'].user
647
        self.assertTrue(user.is_authenticated())
648

  
649
        # change email is enabled
650
        r = self.client.get('/im/email_change')
651
        self.assertEqual(r.status_code, 200)
652
        self.assertFalse(user.email_change_is_pending())
653

  
654
        # request email change to an existing email fails
655
        data = {'new_email_address': 'existing@grnet.gr'}
656
        r = self.client.post('/im/email_change', data)
657
        self.assertContains(r, messages.EMAIL_USED)
658

  
659
        # proper email change
660
        data = {'new_email_address': 'kpap@gmail.com'}
661
        r = self.client.post('/im/email_change', data, follow=True)
662
        self.assertRedirects(r, '/im/profile')
663
        self.assertContains(r, messages.EMAIL_CHANGE_REGISTERED)
664
        change1 = EmailChange.objects.get()
665

  
666
        # user sees a warning
667
        r = self.client.get('/im/email_change')
668
        self.assertEqual(r.status_code, 200)
669
        self.assertContains(r, messages.PENDING_EMAIL_CHANGE_REQUEST)
670
        self.assertTrue(user.email_change_is_pending())
671

  
672
        # link was sent
673
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 0)
674
        self.assertEqual(len(get_mailbox('kpap@gmail.com')), 1)
675

  
676
        # proper email change
677
        data = {'new_email_address': 'kpap@yahoo.com'}
678
        r = self.client.post('/im/email_change', data, follow=True)
679
        self.assertRedirects(r, '/im/profile')
680
        self.assertContains(r, messages.EMAIL_CHANGE_REGISTERED)
681
        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 0)
682
        self.assertEqual(len(get_mailbox('kpap@yahoo.com')), 1)
683
        change2 = EmailChange.objects.get()
684

  
685
        r = self.client.get(change1.get_url())
686
        self.assertEquals(r.status_code, 302)
687
        self.client.logout()
688

  
689
        r = self.client.post('/im/local?next=' + change2.get_url(),
690
                             {'username': 'kpap@grnet.gr',
691
                              'password': 'password',
692
                              'next': change2.get_url()},
693
                             follow=True)
694
        self.assertRedirects(r, '/im/profile')
695
        user = r.context['request'].user
696
        self.assertEquals(user.email, 'kpap@yahoo.com')
697
        self.assertEquals(user.username, 'kpap@yahoo.com')
698

  
699

  
700
        self.client.logout()
701
        r = self.client.post('/im/local?next=' + change2.get_url(),
702
                             {'username': 'kpap@grnet.gr',
703
                              'password': 'password',
704
                              'next': change2.get_url()},
705
                             follow=True)
706
        self.assertContains(r, "Please enter a correct username and password")
707
        self.assertEqual(user.emailchanges.count(), 0)
708

  
b/snf-astakos-app/astakos/im/views.py
670 670
                 confirm_template_name='registration/email_change_done.html',
671 671
                 extra_context=None):
672 672
    extra_context = extra_context or {}
673

  
674

  
673 675
    if activation_key:
674 676
        try:
675 677
            user = EmailChange.objects.change_email(activation_key)
......
679 681
                auth_logout(request)
680 682
                response = prepare_response(request, user)
681 683
                transaction.commit()
682
                return response
684
                return HttpResponseRedirect(reverse('edit_profile'))
683 685
        except ValueError, e:
684 686
            messages.error(request, e)
687
            transaction.rollback()
688
            return HttpResponseRedirect(reverse('index'))
689

  
685 690
        return render_response(confirm_template_name,
686
                               modified_user=user if 'user' in locals(
687
                               ) else None,
688
                               context_instance=get_context(request,
691
                               modified_user=user if 'user' in locals() \
692
                               else None, context_instance=get_context(request,
689 693
                                                            extra_context))
690 694

  
691 695
    if not request.user.is_authenticated():
692 696
        path = quote(request.get_full_path())
693 697
        url = request.build_absolute_uri(reverse('index'))
694 698
        return HttpResponseRedirect(url + '?next=' + path)
699

  
700
    # clean up expired email changes
701
    if request.user.email_change_is_pending():
702
        change = request.user.emailchanges.get()
703
        if change.activation_key_expired():
704
            change.delete()
705
            transaction.commit()
706
            return HttpResponseRedirect(reverse('email_change'))
707

  
695 708
    form = EmailChangeForm(request.POST or None)
696 709
    if request.method == 'POST' and form.is_valid():
697 710
        try:
711
            # delete pending email changes
712
            request.user.emailchanges.all().delete()
698 713
            ec = form.save(email_template_name, request)
699 714
        except SendMailError, e:
700 715
            msg = e
701 716
            messages.error(request, msg)
702 717
            transaction.rollback()
703
        except IntegrityError, e:
704
            msg = _(astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
705
            messages.error(request, msg)
718
            return HttpResponseRedirect(reverse('edit_profile'))
706 719
        else:
707 720
            msg = _(astakos_messages.EMAIL_CHANGE_REGISTERED)
708 721
            messages.success(request, msg)
709 722
            transaction.commit()
723
            return HttpResponseRedirect(reverse('edit_profile'))
724

  
725
    if request.user.email_change_is_pending():
726
        messages.warning(request, astakos_messages.PENDING_EMAIL_CHANGE_REQUEST)
727

  
710 728
    return render_response(
711 729
        form_template_name,
712 730
        form=form,

Also available in: Unified diff