Commit 32c9230c by stephensanchez

Hooking the logistration page into the new HTTP endpoint for email opt in.

Update the PR based on Code Review comments, and additional tests.

Using underscore js
parent eacd5256
...@@ -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