diff --git a/lms/djangoapps/notification_prefs/tests.py b/lms/djangoapps/notification_prefs/tests.py index 06a7521016..b7422dacb2 100644 --- a/lms/djangoapps/notification_prefs/tests.py +++ b/lms/djangoapps/notification_prefs/tests.py @@ -12,10 +12,11 @@ from notification_prefs import NOTIFICATION_PREF_KEY from notification_prefs.views import ajax_enable, ajax_disable, ajax_status, unsubscribe from student.tests.factories import UserFactory from user_api.models import UserPreference +from util.testing import UrlResetMixin @override_settings(SECRET_KEY="test secret key") -class NotificationPrefViewTest(TestCase): +class NotificationPrefViewTest(UrlResetMixin, TestCase): INITIALIZATION_VECTOR = "\x00" * 16 @classmethod @@ -23,7 +24,9 @@ class NotificationPrefViewTest(TestCase): # Make sure global state is set up appropriately Client().get("/") + @patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): + super(NotificationPrefViewTest, self).setUp() self.user = UserFactory.create(username="testuser") # Tokens are intentionally hard-coded instead of computed to help us # avoid breaking existing links. @@ -230,3 +233,17 @@ class NotificationPrefViewTest(TestCase): response = unsubscribe(request, self.tokens[self.user]) self.assertEqual(response.status_code, 200) self.assertNotPrefExists(self.user) + + def test_resubscribe_success(self): + def test_user(user): + # start without a pref key + self.assertFalse(UserPreference.objects.filter(user=user, key=NOTIFICATION_PREF_KEY)) + request = self.request_factory.get("dummy") + request.user = AnonymousUser() + response = unsubscribe(request, self.tokens[user], resubscribe=True) + self.assertEqual(response.status_code, 200) + # this new DB entry will have a new value, so can't use assertPrefValid. Just check existence + self.assertTrue(UserPreference.objects.filter(user=user, key=NOTIFICATION_PREF_KEY)) + + for user in self.tokens.keys(): + test_user(user) diff --git a/lms/djangoapps/notification_prefs/views.py b/lms/djangoapps/notification_prefs/views.py index 68e8c875da..f6a52144de 100644 --- a/lms/djangoapps/notification_prefs/views.py +++ b/lms/djangoapps/notification_prefs/views.py @@ -153,12 +153,13 @@ def ajax_status(request): @require_GET -def unsubscribe(request, token): +def unsubscribe(request, token, resubscribe=False): # pylint: disable=unused-argument """ - A view that disables notifications for a user who may not be authenticated + A view that disables or re-enables notifications for a user who may not be authenticated This view is meant to be the target of an unsubscribe link. The request must be a GET, and the `token` parameter must decrypt to a valid username. + The resubscribe feature allows "undo" of accidentally clicking on unsubscribe A 405 will be returned if the request method is not GET. A 404 will be returned if the token parameter does not decrypt to a valid username. On @@ -174,6 +175,13 @@ def unsubscribe(request, token): except User.DoesNotExist: raise Http404("username") - UserPreference.objects.filter(user=user, key=NOTIFICATION_PREF_KEY).delete() - - return render_to_response("unsubscribe.html", {}) + if not resubscribe: + UserPreference.objects.filter(user=user, key=NOTIFICATION_PREF_KEY).delete() + return render_to_response("unsubscribe.html", {'token': token}) + else: + UserPreference.objects.get_or_create(user=user, + key=NOTIFICATION_PREF_KEY, + defaults={ + "value": UsernameCipher.encrypt(user.username) + }) + return render_to_response("resubscribe.html", {'token': token}) diff --git a/lms/templates/resubscribe.html b/lms/templates/resubscribe.html new file mode 100644 index 0000000000..e0bcfffe96 --- /dev/null +++ b/lms/templates/resubscribe.html @@ -0,0 +1,24 @@ +<%! +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +from django.conf import settings +%> +<%inherit file="main.html" /> + +<%namespace name='static' file='static_content.html'/> + +
+ +
+

${_("Re-subscribe Successful!")}

+
+ +

+ ${_("You have re-enabled forum notification emails from {platform_name}. " + "Click {dashboard_link_start}here{link_end} to return to your dashboard. ").format( + platform_name=settings.PLATFORM_NAME, + dashboard_link_start="".format(reverse('dashboard')), + link_end="",)} +

+
+
diff --git a/lms/templates/unsubscribe.html b/lms/templates/unsubscribe.html index 6f8c042d76..af0043426f 100644 --- a/lms/templates/unsubscribe.html +++ b/lms/templates/unsubscribe.html @@ -1,4 +1,8 @@ -<%! from django.core.urlresolvers import reverse %> +<%! +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +from django.conf import settings +%> <%inherit file="main.html" /> <%namespace name='static' file='static_content.html'/> @@ -6,12 +10,17 @@
-

Unsubscribe Successful!

+

${_("Unsubscribe Successful!")}


- You will no longer receive notification emails from edX. - Click here to return to your dashboard. + ${_("You will no longer receive forum notification emails from {platform_name}. " + "Click {dashboard_link_start}here{link_end} to return to your dashboard. " + "If you did not mean to do this, click {undo_link_start}here{link_end} to re-subscribe.").format( + platform_name=settings.PLATFORM_NAME, + dashboard_link_start="".format(reverse('dashboard')), + undo_link_start="".format(reverse('resubscribe_forum_update', args=[token])), + link_end="",)}

diff --git a/lms/urls.py b/lms/urls.py index eabf4bf9b1..198f4b751c 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -339,6 +339,8 @@ if settings.COURSEWARE_ENABLED: url(r'^notification_prefs/disable/', 'notification_prefs.views.ajax_disable'), url(r'^notification_prefs/status/', 'notification_prefs.views.ajax_status'), url(r'^notification_prefs/unsubscribe/(?P[a-zA-Z0-9-_=]+)/', 'notification_prefs.views.unsubscribe'), + url(r'^notification_prefs/resubscribe/(?P[a-zA-Z0-9-_=]+)/', + 'notification_prefs.views.unsubscribe', {'resubscribe': True}, name="resubscribe_forum_update"), ) urlpatterns += ( # This MUST be the last view in the courseware--it's a catch-all for custom tabs.