Commit de1c0541 by Stephen Sanchez

Merge pull request #6079 from edx/sanchez/logistration-opt-in

Hooking the logistration page into the new HTTP endpoint for email opt in
parents a2dd3208 32c9230c
...@@ -14,10 +14,12 @@ from unittest import SkipTest, skipUnless ...@@ -14,10 +14,12 @@ from unittest import SkipTest, skipUnless
import ddt import ddt
from pytz import UTC from pytz import UTC
import mock import mock
from xmodule.modulestore.tests.factories import CourseFactory
from user_api.api import account as account_api, profile as profile_api from user_api.api import account as account_api, profile as profile_api
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from user_api.models import UserOrgTag
from user_api.tests.factories import UserPreferenceFactory from user_api.tests.factories import UserPreferenceFactory
from django_comment_common import models from django_comment_common import models
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
...@@ -1468,3 +1470,59 @@ class RegistrationViewTest(ApiTestCase): ...@@ -1468,3 +1470,59 @@ class RegistrationViewTest(ApiTestCase):
# Verify that the form description matches what we'd expect # Verify that the form description matches what we'd expect
form_desc = json.loads(response.content) form_desc = json.loads(response.content)
self.assertIn(expected_field, form_desc["fields"]) self.assertIn(expected_field, form_desc["fields"])
@ddt.ddt
class UpdateEmailOptInTestCase(ApiTestCase):
"""Tests the UpdateEmailOptInPreference view. """
USERNAME = "steve"
EMAIL = "steve@isawesome.com"
PASSWORD = "steveopolis"
def setUp(self):
""" Create a course and user, then log in. """
super(UpdateEmailOptInTestCase, self).setUp()
self.course = CourseFactory.create()
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.url = reverse("preferences_email_opt_in")
@ddt.data(
(u"True", u"True"),
(u"true", u"True"),
(u"TrUe", u"True"),
(u"Banana", u"False"),
(u"strawberries", u"False"),
(u"False", u"False"),
)
@ddt.unpack
def test_update_email_opt_in(self, opt, result):
"""Tests the email opt in preference"""
# Register, which should trigger an activation email
response = self.client.post(self.url, {
"course_id": unicode(self.course.id),
"email_opt_in": opt
})
self.assertHttpOK(response)
preference = UserOrgTag.objects.get(
user=self.user, org=self.course.id.org, key="email-optin"
)
self.assertEquals(preference.value, result)
@ddt.data(
(True, False),
(False, True),
(False, False)
)
@ddt.unpack
def test_update_email_opt_in_wrong_params(self, use_course_id, use_opt_in):
"""Tests the email opt in preference"""
params = {}
if use_course_id:
params["course_id"] = unicode(self.course.id)
if use_opt_in:
params["email_opt_in"] = u"True"
response = self.client.post(self.url, params)
self.assertHttpBadRequest(response)
...@@ -20,6 +20,12 @@ urlpatterns = patterns( ...@@ -20,6 +20,12 @@ urlpatterns = patterns(
r'^v1/forum_roles/(?P<name>[a-zA-Z]+)/users/$', r'^v1/forum_roles/(?P<name>[a-zA-Z]+)/users/$',
user_api_views.ForumRoleUsersListView.as_view() user_api_views.ForumRoleUsersListView.as_view()
), ),
url(
r'^v1/preferences/email_opt_in/$',
user_api_views.UpdateEmailOptInPreference.as_view(),
name="preferences_email_opt_in"
),
) )
if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'): if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
......
...@@ -9,10 +9,12 @@ from django.core.exceptions import ImproperlyConfigured ...@@ -9,10 +9,12 @@ from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
from opaque_keys.edx import locator
from rest_framework import authentication from rest_framework import authentication
from rest_framework import filters from rest_framework import filters
from rest_framework import generics from rest_framework import generics
from rest_framework import permissions from rest_framework import permissions
from rest_framework import status
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
...@@ -836,3 +838,32 @@ class PreferenceUsersListView(generics.ListAPIView): ...@@ -836,3 +838,32 @@ class PreferenceUsersListView(generics.ListAPIView):
def get_queryset(self): def get_queryset(self):
return User.objects.filter(preferences__key=self.kwargs["pref_key"]).prefetch_related("preferences") return User.objects.filter(preferences__key=self.kwargs["pref_key"]).prefetch_related("preferences")
class UpdateEmailOptInPreference(APIView):
"""View for updating the email opt in preference. """
authentication_classes = (authentication.SessionAuthentication,)
@method_decorator(require_post_params(["course_id", "email_opt_in"]))
@method_decorator(ensure_csrf_cookie)
def post(self, request):
""" Post function for updating the email opt in preference.
Allows the modification or creation of the email opt in preference at an
organizational level.
Args:
request (Request): The request should contain the following POST parameters:
* course_id: The slash separated course ID. Used to determine the organization
for this preference setting.
* email_opt_in: "True" or "False" to determine if the user is opting in for emails from
this organization. If the string does not match "True" (case insensitive) it will
assume False.
"""
course_id = request.DATA['course_id']
org = locator.CourseLocator.from_string(course_id).org
# Only check for true. All other values are False.
email_opt_in = request.DATA['email_opt_in'].lower() == 'true'
profile_api.update_email_opt_in(request.user, org, email_opt_in)
return HttpResponse(status=status.HTTP_200_OK)
...@@ -1046,6 +1046,7 @@ student_account_js = [ ...@@ -1046,6 +1046,7 @@ student_account_js = [
'js/utils/edx.utils.validate.js', 'js/utils/edx.utils.validate.js',
'js/src/utility.js', 'js/src/utility.js',
'js/student_account/enrollment.js', 'js/student_account/enrollment.js',
'js/student_account/emailoptin.js',
'js/student_account/shoppingcart.js', 'js/student_account/shoppingcart.js',
'js/student_account/models/LoginModel.js', 'js/student_account/models/LoginModel.js',
'js/student_account/models/RegisterModel.js', 'js/student_account/models/RegisterModel.js',
......
...@@ -293,6 +293,10 @@ ...@@ -293,6 +293,10 @@
exports: 'edx.student.account.EnrollmentInterface', exports: 'edx.student.account.EnrollmentInterface',
deps: ['jquery', 'jquery.cookie'] deps: ['jquery', 'jquery.cookie']
}, },
'js/student_account/emailoptin': {
exports: 'edx.student.account.EmailOptInInterface',
deps: ['jquery', 'jquery.cookie']
},
'js/student_account/shoppingcart': { 'js/student_account/shoppingcart': {
exports: 'edx.student.account.ShoppingCartInterface', exports: 'edx.student.account.ShoppingCartInterface',
deps: ['jquery', 'jquery.cookie', 'underscore'] deps: ['jquery', 'jquery.cookie', 'underscore']
...@@ -362,6 +366,7 @@ ...@@ -362,6 +366,7 @@
'js/student_account/models/PasswordResetModel', 'js/student_account/models/PasswordResetModel',
'js/student_account/models/RegisterModel', 'js/student_account/models/RegisterModel',
'js/student_account/views/FormView', 'js/student_account/views/FormView',
'js/student_account/emailoptin',
'js/student_account/enrollment', 'js/student_account/enrollment',
'js/student_account/shoppingcart', 'js/student_account/shoppingcart',
] ]
...@@ -383,6 +388,7 @@ ...@@ -383,6 +388,7 @@
'lms/include/js/spec/student_account/register_spec.js', 'lms/include/js/spec/student_account/register_spec.js',
'lms/include/js/spec/student_account/password_reset_spec.js', 'lms/include/js/spec/student_account/password_reset_spec.js',
'lms/include/js/spec/student_account/enrollment_spec.js', 'lms/include/js/spec/student_account/enrollment_spec.js',
'lms/include/js/spec/student_account/emailoptin_spec.js',
'lms/include/js/spec/student_account/shoppingcart_spec.js', 'lms/include/js/spec/student_account/shoppingcart_spec.js',
'lms/include/js/spec/student_profile/profile_spec.js' 'lms/include/js/spec/student_profile/profile_spec.js'
]); ]);
......
...@@ -5,7 +5,8 @@ define([ ...@@ -5,7 +5,8 @@ define([
'js/student_account/views/AccessView', 'js/student_account/views/AccessView',
'js/student_account/views/FormView', 'js/student_account/views/FormView',
'js/student_account/enrollment', 'js/student_account/enrollment',
'js/student_account/shoppingcart' 'js/student_account/shoppingcart',
'js/student_account/emailoptin'
], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) { ], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) {
describe('edx.student.account.AccessView', function() { describe('edx.student.account.AccessView', function() {
'use strict'; 'use strict';
......
define(['js/common_helpers/ajax_helpers', 'js/student_account/emailoptin'],
function( AjaxHelpers, EmailOptInInterface ) {
'use strict';
describe( 'edx.student.account.EmailOptInInterface', function() {
var COURSE_KEY = 'edX/DemoX/Fall',
EMAIL_OPT_IN = 'True',
EMAIL_OPT_IN_URL = '/user_api/v1/preferences/email_opt_in/';
it('Opts in for organization emails', function() {
// Spy on Ajax requests
var requests = AjaxHelpers.requests( this );
// Attempt to enroll the user
EmailOptInInterface.setPreference( COURSE_KEY, EMAIL_OPT_IN );
// Expect that the correct request was made to the server
AjaxHelpers.expectRequest(
requests, 'POST', EMAIL_OPT_IN_URL, 'course_id=edX%2FDemoX%2FFall&email_opt_in=True'
);
// Simulate a successful response from the server
AjaxHelpers.respondWithJson(requests, {});
});
});
}
);
var edx = edx || {};
(function($) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.EmailOptInInterface = {
urls: {
emailOptInUrl: '/user_api/v1/preferences/email_opt_in/'
},
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
/**
* Set the email opt in setting for the organization associated
* with this course.
* @param {string} courseKey Slash-separated course key.
* @param {string} emailOptIn The preference to opt in or out of organization emails.
*/
setPreference: function( courseKey, emailOptIn, context ) {
return $.ajax({
url: this.urls.emailOptInUrl,
type: 'POST',
data: {course_id: courseKey, email_opt_in: emailOptIn},
headers: this.headers,
context: context
});
}
};
})(jQuery);
...@@ -170,6 +170,33 @@ var edx = edx || {}; ...@@ -170,6 +170,33 @@ var edx = edx || {};
* Once authentication has completed successfully, a user may need to: * Once authentication has completed successfully, a user may need to:
* *
* - Enroll in a course. * - Enroll in a course.
* - Update email opt-in preferences
*
* These actions are delegated from the authComplete function to additional
* functions requiring authentication.
*
*/
authComplete: function() {
var emailOptIn = edx.student.account.EmailOptInInterface,
queryParams = this.queryParams();
// Set the email opt in preference.
if (!_.isUndefined(queryParams.emailOptIn) && queryParams.enrollmentAction) {
emailOptIn.setPreference(
decodeURIComponent(queryParams.courseId),
queryParams.emailOptIn,
this
).always(this.enrollment);
} else {
this.enrollment();
}
},
/**
* Designed to be invoked after authentication has completed. This function enrolls
* the student as requested.
*
* - Enroll in a course.
* - Add a course to the shopping cart. * - Add a course to the shopping cart.
* - Be redirected to the dashboard / track selection page / shopping cart. * - Be redirected to the dashboard / track selection page / shopping cart.
* *
...@@ -191,7 +218,7 @@ var edx = edx || {}; ...@@ -191,7 +218,7 @@ var edx = edx || {};
* ?course_id: The slash-separated course ID to enroll in or add to the cart. * ?course_id: The slash-separated course ID to enroll in or add to the cart.
* *
*/ */
authComplete: function() { enrollment: function() {
var enrollment = edx.student.account.EnrollmentInterface, var enrollment = edx.student.account.EnrollmentInterface,
shoppingcart = edx.student.account.ShoppingCartInterface, shoppingcart = edx.student.account.ShoppingCartInterface,
redirectUrl = '/dashboard', redirectUrl = '/dashboard',
...@@ -248,7 +275,8 @@ var edx = edx || {}; ...@@ -248,7 +275,8 @@ var edx = edx || {};
return { return {
next: $.url( '?next' ), next: $.url( '?next' ),
enrollmentAction: $.url( '?enrollment_action' ), enrollmentAction: $.url( '?enrollment_action' ),
courseId: $.url( '?course_id' ) courseId: $.url( '?course_id' ),
emailOptIn: $.url( '?email_opt_in')
}; };
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment