Kaynağa Gözat

Check for existing email when updating profile

A user in Oscar is identified by email address instead of the
username. This is, however, not set as a ``unique`` constraint
in the user model in ``django.contrib.auth.models.User``. Checks
if an email already exists are carried out when a user registers
but are ignored when a registered user changes their
profile. This can lead to multiple users having the same email
address which should not happen.

I provide a failing test with a mixin that can be used in both
the ``UserForm`` and ``UserAndProfileForm`` to clean the email
field when validating the form. A ``ValidationError`` is raised
when a user with this email address already exists and is not
the currently edited instance (makes sure that profile updates
with unchanged email work still).

Fixes #324
master
Sebastian Vetter 13 yıl önce
ebeveyn
işleme
91cd80d6bc

+ 29
- 2
oscar/apps/customer/forms.py Dosyayı Görüntüle

10
 from django.contrib.auth import forms as auth_forms
10
 from django.contrib.auth import forms as auth_forms
11
 from django.conf import settings
11
 from django.conf import settings
12
 from django.core import validators
12
 from django.core import validators
13
+from django.core.exceptions import ValidationError
13
 from django.utils.http import int_to_base36
14
 from django.utils.http import int_to_base36
14
 from django.contrib.sites.models import get_current_site
15
 from django.contrib.sites.models import get_current_site
15
 from django.contrib.auth.tokens import default_token_generator
16
 from django.contrib.auth.tokens import default_token_generator
211
         return {}
212
         return {}
212
 
213
 
213
 
214
 
214
-class UserForm(forms.ModelForm):
215
+class CleanEmailMixin(object):
216
+
217
+    def clean_email(self):
218
+        """
219
+        Make sure that the email address is aways unique as it is
220
+        used instead of the username. This is necessary because the
221
+        unique-ness of email addresses is *not* enforced on the model
222
+        level in ``django.contrib.auth.models.User``.
223
+        """
224
+        email = self.cleaned_data['email']
225
+
226
+        try:
227
+            user = User.objects.get(email=email)
228
+        except User.DoesNotExist:
229
+            # this email address is unique so we don't have to worry
230
+            # about it
231
+            return email
232
+
233
+        if self.instance and self.instance.id != user.id:
234
+            raise ValidationError(
235
+                _("A user with this email address already exists")
236
+            )
237
+
238
+        return email
239
+
240
+
241
+class UserForm(forms.ModelForm, CleanEmailMixin):
215
 
242
 
216
     def __init__(self, user, *args, **kwargs):
243
     def __init__(self, user, *args, **kwargs):
217
         self.user = user
244
         self.user = user
229
 
256
 
230
     Profile = get_profile_class()
257
     Profile = get_profile_class()
231
 
258
 
232
-    class UserAndProfileForm(forms.ModelForm):
259
+    class UserAndProfileForm(forms.ModelForm, CleanEmailMixin):
233
 
260
 
234
         first_name = forms.CharField(label=_('First name'), max_length=128)
261
         first_name = forms.CharField(label=_('First name'), max_length=128)
235
         last_name = forms.CharField(label=_('Last name'), max_length=128)
262
         last_name = forms.CharField(label=_('Last name'), max_length=128)

+ 25
- 3
tests/functional/customer/profile_tests.py Dosyayı Görüntüle

1
-import httplib
2
 from mock import patch
1
 from mock import patch
3
 from decimal import Decimal as D
2
 from decimal import Decimal as D
4
 
3
 
5
 from django.contrib.auth.models import User
4
 from django.contrib.auth.models import User
6
 from django.core.urlresolvers import reverse
5
 from django.core.urlresolvers import reverse
7
-from django.test import TestCase
8
-from django.test.client import Client
9
 
6
 
10
 from oscar.test.helpers import create_product, create_order
7
 from oscar.test.helpers import create_product, create_order
11
 from oscar.test import ClientTestCase, WebTestCase
8
 from oscar.test import ClientTestCase, WebTestCase
45
         self.assertEqual('Barry', user.first_name)
42
         self.assertEqual('Barry', user.first_name)
46
         self.assertEqual('Chuckle', user.last_name)
43
         self.assertEqual('Chuckle', user.last_name)
47
 
44
 
45
+    def test_cant_update_their_email_address_if_it_already_exists(self):
46
+        User.objects.create_user(username='testuser', email='new@example.com',
47
+                                 password="somerandompassword")
48
+        self.assertEquals(User.objects.count(), 2)
49
+
50
+        profile_form_page = self.app.get(reverse('customer:profile-update'),
51
+                                user=self.user)
52
+        self.assertEqual(200, profile_form_page.status_code)
53
+        form = profile_form_page.forms['profile_form']
54
+        form['email'] = 'new@example.com'
55
+        form['first_name'] = 'Barry'
56
+        form['last_name'] = 'Chuckle'
57
+        response = form.submit()
58
+
59
+        user = User.objects.get(id=self.user.id)
60
+        self.assertEqual(self.email, user.email)
61
+
62
+        try:
63
+            User.objects.get(email='new@example.com')
64
+        except User.MultipleObjectsReturned:
65
+            self.fail("email for user changed to existing one")
66
+
67
+        self.assertContains(response,
68
+                            'A user with this email address already exists')
69
+
48
     def test_can_change_their_password(self):
70
     def test_can_change_their_password(self):
49
         password_form_page = self.app.get(reverse('customer:change-password'),
71
         password_form_page = self.app.get(reverse('customer:change-password'),
50
                                           user=self.user)
72
                                           user=self.user)

Loading…
İptal
Kaydet