diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index f3dca5d08f..8ca43a17bb 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -37,6 +37,7 @@ class ChooseModeView(View): enrollment_mode = CourseEnrollment.enrollment_mode_for_user(request.user, course_id) upgrade = request.GET.get('upgrade', False) + request.session['attempting_upgrade'] = upgrade # verified users do not need to register or upgrade if enrollment_mode == 'verified': diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index f8071c09c4..8f1ac09a06 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -35,9 +35,7 @@ from track import contexts from track.views import server_track from eventtracking import tracker - unenroll_done = Signal(providing_args=["course_enrollment"]) - log = logging.getLogger(__name__) AUDIT_LOG = logging.getLogger("audit") diff --git a/lms/djangoapps/courseware/features/certificates.feature b/lms/djangoapps/courseware/features/certificates.feature index 4a4ceb44b1..ced2a1c2a2 100644 --- a/lms/djangoapps/courseware/features/certificates.feature +++ b/lms/djangoapps/courseware/features/certificates.feature @@ -95,4 +95,4 @@ Feature: LMS.Verified certificates Then I see the course on my dashboard And I see that I am on the verified track And a "edx.course.enrollment.activated" server event is emitted - + And a "edx.course.enrollment.upgrade.succeeded" server event is emitted diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 93f35ebc46..7647b2c487 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -557,6 +557,7 @@ class CertificateItem(OrderItem): """ super(CertificateItem, cls).add_to_order(order, course_id, cost, currency=currency) + course_enrollment = CourseEnrollment.get_or_create_enrollment(order.user, course_id) # do some validation on the enrollment mode @@ -570,7 +571,7 @@ class CertificateItem(OrderItem): user=order.user, course_id=course_id, course_enrollment=course_enrollment, - mode=mode + mode=mode, ) item.status = order.status item.qty = 1 @@ -595,7 +596,6 @@ class CertificateItem(OrderItem): log.exception( "Could not submit verification attempt for enrollment {}".format(self.course_enrollment) ) - self.course_enrollment.change_mode(self.mode) self.course_enrollment.activate() diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index cf01204942..5a52ca0680 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -6,7 +6,7 @@ import StringIO from textwrap import dedent from boto.exception import BotoServerError # this is a super-class of SESError and catches connection errors -from mock import patch, MagicMock +from mock import patch, MagicMock, sentinel from django.core import mail from django.conf import settings from django.db import DatabaseError @@ -425,12 +425,21 @@ class CertificateItemTest(ModuleStoreTestCase): min_price=self.cost) course_mode.save() + patcher = patch('student.models.server_track') + self.mock_server_track = patcher.start() + self.addCleanup(patcher.stop) + crum_patcher = patch('student.models.crum.get_current_request') + self.mock_get_current_request = crum_patcher.start() + self.addCleanup(crum_patcher.stop) + self.mock_get_current_request.return_value = sentinel.request + def test_existing_enrollment(self): CourseEnrollment.enroll(self.user, self.course_id) cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') # verify that we are still enrolled self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id)) + self.mock_server_track.reset_mock() cart.purchase() enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id) self.assertEquals(enrollment.mode, u'verified') diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index 0d23dc0419..d1ab9ab24f 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -20,7 +20,7 @@ from student.models import CourseEnrollment from course_modes.models import CourseMode from edxmako.shortcuts import render_to_response from shoppingcart.processors import render_purchase_form_html -from mock import patch, Mock +from mock import patch, Mock, sentinel def mock_render_purchase_form_html(*args, **kwargs): @@ -39,6 +39,8 @@ postpay_mock = Mock() @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) class ShoppingCartViewsTests(ModuleStoreTestCase): def setUp(self): + patcher = patch('student.models.server_track') + self.mock_server_track = patcher.start() self.user = UserFactory.create() self.user.set_password('password') self.user.save() @@ -53,6 +55,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): self.verified_course_id = 'org/test/Test_Course' CourseFactory.create(org='org', number='test', run='course1', display_name='Test Course') self.cart = Order.get_cart_for_user(self.user) + self.addCleanup(patcher.stop) def login_user(self): self.client.login(username=self.user.username, password="password") @@ -83,6 +86,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): def test_add_course_to_cart_success(self): self.login_user() + reverse('shoppingcart.views.add_course_to_cart', args=[self.course_id]) resp = self.client.post(reverse('shoppingcart.views.add_course_to_cart', args=[self.course_id])) self.assertEqual(resp.status_code, 200) self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_id)) @@ -202,6 +206,55 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): self.assertIn(cert_item, context['order_items']) self.assertFalse(context['any_refunds']) + @patch('shoppingcart.views.render_to_response', render_mock) + def test_show_receipt_success_with_upgrade(self): + + reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_id) + cert_item = CertificateItem.add_to_order(self.cart, self.verified_course_id, self.cost, 'honor') + self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123') + + self.login_user() + + # When we come from the upgrade flow, we'll have a session variable showing that + s = self.client.session + s['attempting_upgrade'] = True + s.save() + + self.mock_server_track.reset_mock() + resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id])) + + # Once they've upgraded, they're no longer *attempting* to upgrade + attempting_upgrade = self.client.session.get('attempting_upgrade', False) + self.assertFalse(attempting_upgrade) + + self.assertEqual(resp.status_code, 200) + self.assertIn('FirstNameTesting123', resp.content) + self.assertIn('80.00', resp.content) + + + ((template, context), _) = render_mock.call_args + + # When we come from the upgrade flow, we get these context variables + + + self.assertEqual(template, 'shoppingcart/receipt.html') + self.assertEqual(context['order'], self.cart) + self.assertIn(reg_item, context['order_items']) + self.assertIn(cert_item, context['order_items']) + self.assertFalse(context['any_refunds']) + + course_enrollment = CourseEnrollment.get_or_create_enrollment(self.user, self.course_id) + course_enrollment.emit_event('edx.course.enrollment.upgrade.succeeded') + self.mock_server_track.assert_any_call( + None, + 'edx.course.enrollment.upgrade.succeeded', + { + 'user_id': course_enrollment.user.id, + 'course_id': course_enrollment.course_id, + 'mode': course_enrollment.mode + } + ) + @patch('shoppingcart.views.render_to_response', render_mock) def test_show_receipt_success_refund(self): reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course_id) diff --git a/lms/djangoapps/shoppingcart/views.py b/lms/djangoapps/shoppingcart/views.py index 26cf3e5a51..22bccbf28c 100644 --- a/lms/djangoapps/shoppingcart/views.py +++ b/lms/djangoapps/shoppingcart/views.py @@ -12,11 +12,14 @@ from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required from edxmako.shortcuts import render_to_response from .models import Order, PaidCourseRegistration, OrderItem +from student.models import CourseEnrollment from .processors import process_postpay_callback, render_purchase_form_html from .exceptions import ItemAlreadyInCartException, AlreadyEnrolledInCourseException, CourseDoesNotExistException log = logging.getLogger("shoppingcart") +EVENT_NAME_USER_UPGRADED = 'edx.course.enrollment.upgrade.succeeded' + @require_POST def add_course_to_cart(request, course_id): @@ -124,6 +127,13 @@ def show_receipt(request, ordernum): receipt_template = order_items[0].single_item_receipt_template context.update(order_items[0].single_item_receipt_context) + # Only orders where order_items.count() == 1 might be attempting to upgrade + attempting_upgrade = request.session.get('attempting_upgrade', False) + if attempting_upgrade: + course_enrollment = CourseEnrollment.get_or_create_enrollment(request.user, order_items[0].course_id) + course_enrollment.emit_event(EVENT_NAME_USER_UPGRADED) + request.session['attempting_upgrade'] = False + return render_to_response(receipt_template, context) diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index b666f7545c..523eecfe96 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -2,6 +2,7 @@ <%! from django.core.urlresolvers import reverse + import waffle %> <%inherit file="main.html" /> @@ -22,6 +23,21 @@ $(this).closest('.message.is-expandable').toggleClass('is-expanded'); } + $("#upgrade-to-verified").click(function(event) { + user = $(event.target).data("user"); + course = $(event.target).data("course-id"); + Logger.log('edx.course.enrollment.upgrade.clicked', [user, course], null); + % if waffle.flag_is_active(request, 'alternate_upsell_copy'): + analytics.track("Clicked on Alternate Upsell Copy", { + course: course + }); + % else: + analytics.track("Clicked on Regular Upsell Copy", { + course: course + }); + % endif + }); + $(".email-settings").click(function(event) { $("#email_settings_course_id").val( $(event.target).data("course-id") ); $("#email_settings_course_number").text( $(event.target).data("course-number") ); diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index aa7b1b84b0..3569e365ee 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -61,13 +61,17 @@ <%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/> % endif - %if course_mode_info['show_upsell']: + % if course_mode_info['show_upsell']: