Browse Source

Don't allow multiple unconfirmed product alerts for anonymous users.

This is to prevent the possibility of spamming a user by generating lots of alert confirmation emails for product alerts.

Also allow unconfirmed alerts to be cancelled - we send a cancellation link in the confirmation email, so we should let users cancel an alert if they don't want to confirm it.

Fixes #2401.
master
Samir Shah 8 years ago
parent
commit
17193a4d08

+ 2
- 2
src/oscar/apps/customer/abstract_models.py View File

@@ -340,7 +340,7 @@ class AbstractProductAlert(models.Model):
340 340
     key = models.CharField(_("Key"), max_length=128, blank=True, db_index=True)
341 341
 
342 342
     # An alert can have two different statuses for authenticated
343
-    # users ``ACTIVE`` and ``INACTIVE`` and anonymous users have an
343
+    # users ``ACTIVE`` and ``CANCELLED`` and anonymous users have an
344 344
     # additional status ``UNCONFIRMED``. For anonymous users a confirmation
345 345
     # and unsubscription key are generated when an instance is saved for
346 346
     # the first time and can be used to confirm and unsubscribe the
@@ -379,7 +379,7 @@ class AbstractProductAlert(models.Model):
379 379
 
380 380
     @property
381 381
     def can_be_cancelled(self):
382
-        return self.status == self.ACTIVE
382
+        return self.status in (self.ACTIVE, self.UNCONFIRMED)
383 383
 
384 384
     @property
385 385
     def is_cancelled(self):

+ 10
- 0
src/oscar/apps/customer/forms.py View File

@@ -406,6 +406,16 @@ class ProductAlertForm(forms.ModelForm):
406 406
             else:
407 407
                 raise forms.ValidationError(_(
408 408
                     "There is already an active stock alert for %s") % email)
409
+
410
+            # Check that the email address hasn't got other unconfirmed alerts.
411
+            # If they do then we don't want to spam them with more until they
412
+            # have confirmed or cancelled the existing alert.
413
+            if ProductAlert.objects.filter(email__iexact=email,
414
+                                    status=ProductAlert.UNCONFIRMED).count():
415
+                raise forms.ValidationError(_(
416
+                    "%s has been sent a confirmation email for another product "
417
+                    "alert on this site. Please confirm or cancel that request "
418
+                    "before signing up for more alerts.") % email)
409 419
         elif user_is_authenticated(self.user):
410 420
             try:
411 421
                 ProductAlert.objects.get(product=self.product,

+ 30
- 2
tests/functional/customer/test_alert.py View File

@@ -3,6 +3,8 @@ import os
3 3
 import warnings
4 4
 
5 5
 from django_webtest import WebTest
6
+
7
+from django.contrib.auth.models import AnonymousUser
6 8
 from django.core.urlresolvers import reverse
7 9
 from django.core import mail
8 10
 from django.test import TestCase
@@ -10,9 +12,10 @@ from oscar.utils.deprecation import RemovedInOscar20Warning
10 12
 
11 13
 from oscar.apps.customer.alerts.utils import (send_alert_confirmation,
12 14
     send_product_alerts)
15
+from oscar.apps.customer.forms import ProductAlertForm
13 16
 from oscar.apps.customer.models import ProductAlert
14
-from oscar.test.factories import create_product, create_stockrecord
15
-from oscar.test.factories import UserFactory
17
+from oscar.test.factories import (
18
+    create_product, create_stockrecord, ProductAlertFactory, UserFactory)
16 19
 
17 20
 
18 21
 class TestAUser(WebTest):
@@ -87,6 +90,31 @@ class TestAnAnonymousUser(WebTest):
87 90
         self.assertEqual(ProductAlert.UNCONFIRMED, alert.status)
88 91
         self.assertEqual(alert.product, product)
89 92
 
93
+    def test_can_cancel_unconfirmed_stock_alert(self):
94
+        alert = ProductAlertFactory(
95
+            user=None, email='john@smith.com', status=ProductAlert.UNCONFIRMED)
96
+        self.app.get(
97
+            reverse('customer:alerts-cancel-by-key', kwargs={'key': alert.key}))
98
+        alert.refresh_from_db()
99
+        self.assertTrue(alert.is_cancelled)
100
+
101
+    def test_cannot_create_multiple_unconfirmed_alerts(self):
102
+        # Create an unconfirmed alert
103
+        alert = ProductAlertFactory(
104
+            user=None, email='john@smith.com', status=ProductAlert.UNCONFIRMED)
105
+
106
+        # Alert form should not allow creation of additional alerts.
107
+        form = ProductAlertForm(
108
+            user=AnonymousUser(),
109
+            product=create_product(num_in_stock=0),
110
+            data={'email': 'john@smith.com'},
111
+        )
112
+
113
+        self.assertFalse(form.is_valid())
114
+        self.assertIn(
115
+            "john@smith.com has been sent a confirmation email for another "
116
+            "product alert on this site.", form.errors['__all__'][0])
117
+
90 118
 
91 119
 class TestHurryMode(TestCase):
92 120
 

Loading…
Cancel
Save