[2/5] Allow changing primary email address for accounts

Submitted by aldot on Nov. 10, 2015, 8:53 p.m.

Details

Message ID 1447188793-27228-3-git-send-email-rep.dot.nop@gmail.com
State Under Review
Delegated to: Stephen Finucane
Headers show

Commit Message

aldot Nov. 10, 2015, 8:53 p.m.
Signed-off-by: Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>
---
 docs/TODO                                    |  1 -
 patchwork/forms.py                           | 14 +++++++
 patchwork/models.py                          |  3 ++
 patchwork/templates/patchwork/profile.html   | 18 ++++++++-
 patchwork/templates/patchwork/user-data.html | 34 ++++++++++++++++
 patchwork/tests/test_user.py                 | 60 ++++++++++++++++++++++++++++
 patchwork/urls.py                            |  1 +
 patchwork/views/user.py                      | 42 ++++++++++++++++++-
 8 files changed, 170 insertions(+), 3 deletions(-)
 create mode 100644 patchwork/templates/patchwork/user-data.html

Comments

Stephen Finucane Dec. 21, 2015, 4:20 p.m.
On 10 Nov 21:53, Bernhard Reutner-Fischer wrote:
> Signed-off-by: Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>

Happy Christmas :) I rebased this and it seems to do what it says on
the tin...with one exception. Could you have a look?

[snip]

> +@login_required
> +def userdata(request, user_id):
> +    if request.method == 'POST':
> +        user = request.user
> +        old_email = user.email
> +        form = UpdateUserForm(request.POST, instance = user)
> +        if form.is_valid():
> +            email = form.clean_email()
> +            form.save()
> +            conf = EmailConfirmation.objects.get(user_id = user_id,
> +                    type = 'registration',
> +                    email = old_email)
> +            dups = EmailConfirmation.objects.filter(user_id = user_id,
> +                    email = email)
> +            dups.delete() # maybe just if dups.exists() but another roundtrip..
> +            conf.email = email
> +            conf.key = ''
> +            conf.save()
> +            person = get_object_or_404(Person, user_id = user_id,
> +                        email__iexact = old_email)
> +            dups = Person.objects.filter(user_id = user_id,
> +                        email__iexact = email).count()
> +            if dups:
> +                person.delete()
> +            else:
> +                person.email = email
> +                person.save()

[snip]

I'm not sure about the second part of this change. The change is
supposed to allow changing of email (and name) on a User object, so
I don't know why we're touching the Person object. This seems to
change the email associated with a patch which would mean these
patches are now going to reference the new email when downloading
them or whatnot. I'm not really sure if we want to break the
integrity of this data? For example, a patch I sent under my
personal email should remain "authored" by that email, regardless
of what email I'm now using for patchwork login.

Cheers,
Stephen

Patch hide | download patch | download mbox

diff --git a/docs/TODO b/docs/TODO
index 17d8cd1..b020afd 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -13,4 +13,3 @@ 
 * pwclient: integrate hash parser
 * pwclient: add example scripts (eg, git post-commit hook) to online help
 * pwclient: case-insensitive searches for author and project name
-* changing primary email addresses for accounts
diff --git a/patchwork/forms.py b/patchwork/forms.py
index 0327958..76b8f2d 100644
--- a/patchwork/forms.py
+++ b/patchwork/forms.py
@@ -53,6 +53,20 @@  class RegistrationForm(forms.Form):
     def clean(self):
         return self.cleaned_data
 
+class UpdateUserForm(forms.ModelForm):
+    class Meta:
+        model = User
+        fields = ['first_name', 'last_name', 'email']
+
+    # Can't easily reuse (parts of) RegistrationForm with current django :(
+    def clean_email(self):
+        value = self.cleaned_data['email']
+        try:
+            user = User.objects.get(email__iexact = value)
+        except User.DoesNotExist:
+            return self.cleaned_data['email']
+        raise forms.ValidationError('This email address is already in use\n')
+
 class LoginForm(forms.Form):
     username = forms.CharField(max_length = 30)
     password = forms.CharField(widget = forms.PasswordInput)
diff --git a/patchwork/models.py b/patchwork/models.py
index a2b9498..d8690b7 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -558,6 +558,9 @@  class EmailConfirmation(models.Model):
     date = models.DateTimeField(default=datetime.datetime.now)
     active = models.BooleanField(default=True)
 
+    def __unicode__(self):
+        return u'%s, %s: %s' % (self.user.username, self.type, self.email)
+
     def deactivate(self):
         self.active = False
         self.save()
diff --git a/patchwork/templates/patchwork/profile.html b/patchwork/templates/patchwork/profile.html
index 116d6d6..133a5a1 100644
--- a/patchwork/templates/patchwork/profile.html
+++ b/patchwork/templates/patchwork/profile.html
@@ -116,8 +116,24 @@  address.</p>
 
 
 <div class="box">
-<h2>Settings</h2>
+<h2>User data</h2>
+<form action="{% url 'patchwork.views.user.userdata' user_id=user.id %}"
+  method="post">
+  {% csrf_token %}
+ <table class="form">
+    {{ userform }}
+  <tr>
+   <td/>
+   <td>
+    <input type="submit" value="Apply"/>
+   </td>
+  </tr>
+ </table>
+</form>
+</div>
 
+<div class="box">
+<h2>Settings</h2>
 <form method="post">
  {% csrf_token %}
  <table class="form">
diff --git a/patchwork/templates/patchwork/user-data.html b/patchwork/templates/patchwork/user-data.html
new file mode 100644
index 0000000..a1d0e05
--- /dev/null
+++ b/patchwork/templates/patchwork/user-data.html
@@ -0,0 +1,34 @@ 
+{% extends "base.html" %}
+
+{% block title %}User Profile: {{ user.username }}{% endblock %}
+{% block heading %}User Profile: {{ user.username }}{% endblock %}
+
+{% block body %}
+
+<div class="leftcol">
+</div>
+
+<div class="rightcol">
+
+<div class="box">
+<h2>User data</h2>
+<form action="{% url 'patchwork.views.user.userdata' user_id=user.id %}"
+  method="post">
+  {% csrf_token %}
+ <table class="form">
+    {{ userform }}
+  <tr>
+   <td/>
+   <td>
+    <input type="submit" value="Apply"/>
+   </td>
+  </tr>
+ </table>
+</form>
+</div>
+
+</div>
+
+<p style="clear: both"></p>
+
+{% endblock %}
diff --git a/patchwork/tests/test_user.py b/patchwork/tests/test_user.py
index ab46126..9bc1214 100644
--- a/patchwork/tests/test_user.py
+++ b/patchwork/tests/test_user.py
@@ -176,6 +176,66 @@  class UserProfileTest(TestCase):
         user_profile = UserProfile.objects.get(user=self.user.user.id)
         self.assertEquals(user_profile.patches_per_page, old_ppp)
 
+    def testUserProfileUserData(self):
+        user = User.objects.get(id=self.user.user.id)
+        old_fn = user.first_name
+        old_ln = user.last_name
+        old_email = user.email
+        new_fn = u'new_firstname'
+        new_ln = u'new_lastname'
+        new_email = u'new-' + old_email
+
+        # setup appropriate EmailConfirmation for registration + userperson
+        conf = EmailConfirmation(type='registration',
+                                 email=user.email,
+                                 user=user)
+        conf.save()
+        self.client.get(_confirmation_url(conf))
+        self.client.post('/user/link/', {'email': self.user.secondary_email})
+        conf = EmailConfirmation.objects.get(email=self.user.secondary_email)
+        self.assertEqual(conf.type, 'userperson')
+        self.client.get(_confirmation_url(conf))
+        confs = EmailConfirmation.objects.filter(user_id=user.id) \
+                    .values_list('email', flat=True)
+        self.assertEqual(confs.count(), 2)
+        self.assertIn(self.user.email, confs)
+        self.assertIn(self.user.secondary_email, confs)
+
+        response = self.client.post('/user/userdata/%d/' % (user.id), {
+                'email': new_email,
+                'first_name': new_fn,
+                'last_name': new_ln})
+
+        user = User.objects.get(id=user.id)
+        self.assertRedirects(response, '/user/')
+        self.assertEqual(user.email, new_email)
+        self.assertEqual(user.first_name, new_fn)
+        self.assertEqual(user.last_name, new_ln)
+        confs = EmailConfirmation.objects.filter(user_id=user.id) \
+                    .values_list('email', flat=True)
+        self.assertEqual(confs.count(), 2)
+        self.assertIn(new_email, confs)
+        self.assertIn(self.user.secondary_email, confs)
+
+        # Now set the formerly secondary addr as the new primary.
+        new_email = self.user.secondary_email
+        response = self.client.post('/user/userdata/%d/' % (user.id), {
+                'email': new_email,
+                'first_name': new_fn,
+                'last_name': new_ln})
+
+        user = User.objects.get(id=user.id)
+        self.assertRedirects(response, '/user/')
+        self.assertEqual(user.email, new_email)
+        self.assertEqual(user.first_name, new_fn)
+        self.assertEqual(user.last_name, new_ln)
+        # The secondary addr should now be registration (not userperson)
+        # and should be the only confirmation -- the userperson should
+        # have been deleted
+        conf = EmailConfirmation.objects.get(user_id=user.id)
+        self.assertEqual(conf.email, new_email)
+        self.assertEqual(conf.type, 'registration')
+
 
 class UserPasswordChangeTest(TestCase):
     user = None
diff --git a/patchwork/urls.py b/patchwork/urls.py
index d9da7db..561cf98 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -46,6 +46,7 @@  urlpatterns = patterns('',
 
     (r'^user/link/$', 'patchwork.views.user.link'),
     (r'^user/unlink/(?P<person_id>[^/]+)/$', 'patchwork.views.user.unlink'),
+    (r'^user/userdata/(?P<user_id>[^/]+)/$', 'patchwork.views.user.userdata'),
 
     # password change
     url(r'^user/password-change/$', auth_views.password_change,
diff --git a/patchwork/views/user.py b/patchwork/views/user.py
index 126ecc9..533a02a 100644
--- a/patchwork/views/user.py
+++ b/patchwork/views/user.py
@@ -27,7 +27,7 @@  from django.http import HttpResponseRedirect
 from patchwork.models import Project, Bundle, Person, EmailConfirmation, \
          State, EmailOptout
 from patchwork.forms import UserProfileForm, UserPersonLinkForm, \
-         RegistrationForm
+         RegistrationForm, UpdateUserForm
 from patchwork.filters import DelegateFilter
 from patchwork.views import generic_list
 from django.template.loader import render_to_string
@@ -99,14 +99,20 @@  def profile(request):
     if request.method == 'POST':
         form = UserProfileForm(instance = request.user.profile,
                 data = request.POST)
+        userform = UpdateUserForm(instance = request.user,
+                data = request.POST)
         if form.is_valid():
             form.save()
+        if userform.is_valid():
+            userform.save()
     else:
         form = UserProfileForm(instance = request.user.profile)
+        userform = UpdateUserForm(instance = request.user)
 
     context.project = request.user.profile.primary_project
     context['bundles'] = Bundle.objects.filter(owner = request.user)
     context['profileform'] = form
+    context['userform'] = userform
 
     optout_query = '%s.%s IN (SELECT %s FROM %s)' % (
                         Person._meta.db_table,
@@ -178,6 +184,40 @@  def unlink(request, person_id):
     url = django.core.urlresolvers.reverse('patchwork.views.user.profile')
     return HttpResponseRedirect(url)
 
+@login_required
+def userdata(request, user_id):
+    if request.method == 'POST':
+        user = request.user
+        old_email = user.email
+        form = UpdateUserForm(request.POST, instance = user)
+        if form.is_valid():
+            email = form.clean_email()
+            form.save()
+            conf = EmailConfirmation.objects.get(user_id = user_id,
+                    type = 'registration',
+                    email = old_email)
+            dups = EmailConfirmation.objects.filter(user_id = user_id,
+                    email = email)
+            dups.delete() # maybe just if dups.exists() but another roundtrip..
+            conf.email = email
+            conf.key = ''
+            conf.save()
+            person = get_object_or_404(Person, user_id = user_id,
+                        email__iexact = old_email)
+            dups = Person.objects.filter(user_id = user_id,
+                        email__iexact = email).count()
+            if dups:
+                person.delete()
+            else:
+                person.email = email
+                person.save()
+        else:
+            context = PatchworkRequestContext(request)
+            context['userform'] = form
+            return render_to_response('patchwork/user-data.html', context)
+
+    url = django.core.urlresolvers.reverse('patchwork.views.user.profile')
+    return HttpResponseRedirect(url)
 
 @login_required
 def todo_lists(request):