Commit 384f22ff by Calen Pennington

Add per-user metadata to course pages to make experimentation easier

parent cc6f8be8
...@@ -12,6 +12,7 @@ from django.db import models ...@@ -12,6 +12,7 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from openedx.core.djangoapps.xmodule_django.models import CourseKeyField from openedx.core.djangoapps.xmodule_django.models import CourseKeyField
from request_cache.middleware import RequestCache, ns_request_cached from request_cache.middleware import RequestCache, ns_request_cached
...@@ -693,6 +694,59 @@ def invalidate_course_mode_cache(sender, **kwargs): # pylint: disable=unused-a ...@@ -693,6 +694,59 @@ def invalidate_course_mode_cache(sender, **kwargs): # pylint: disable=unused-a
RequestCache.clear_request_cache(name=CourseMode.CACHE_NAMESPACE) RequestCache.clear_request_cache(name=CourseMode.CACHE_NAMESPACE)
def get_cosmetic_verified_display_price(course):
"""
Returns the minimum verified cert course price as a string preceded by correct currency, or 'Free'.
"""
return get_course_prices(course, verified_only=True)[1]
def get_cosmetic_display_price(course):
"""
Returns the course price as a string preceded by correct currency, or 'Free'.
"""
return get_course_prices(course)[1]
def get_course_prices(course, verified_only=False):
"""
Return registration_price and cosmetic_display_prices.
registration_price is the minimum price for the course across all course modes.
cosmetic_display_prices is the course price as a string preceded by correct currency, or 'Free'.
"""
# Find the
if verified_only:
registration_price = CourseMode.min_course_price_for_verified_for_currency(
course.id,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
else:
registration_price = CourseMode.min_course_price_for_currency(
course.id,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
currency_symbol = settings.PAID_COURSE_REGISTRATION_CURRENCY[1]
if registration_price > 0:
price = registration_price
# Handle course overview objects which have no cosmetic_display_price
elif hasattr(course, 'cosmetic_display_price'):
price = course.cosmetic_display_price
else:
price = None
if price:
# Translators: This will look like '$50', where {currency_symbol} is a symbol such as '$' and {price} is a
# numerical amount in that currency. Adjust this display as needed for your language.
cosmetic_display_price = _("{currency_symbol}{price}").format(currency_symbol=currency_symbol, price=price)
else:
# Translators: This refers to the cost of the course. In this case, the course costs nothing so it is free.
cosmetic_display_price = _('Free')
return registration_price, force_text(cosmetic_display_price)
class CourseModesArchive(models.Model): class CourseModesArchive(models.Model):
""" """
Store the past values of course_mode that a course had in the past. We decided on having Store the past values of course_mode that a course had in the past. We decided on having
......
...@@ -11,13 +11,18 @@ from datetime import datetime, timedelta ...@@ -11,13 +11,18 @@ from datetime import datetime, timedelta
import ddt import ddt
import pytz import pytz
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase from django.test import TestCase, override_settings
from mock import patch
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from course_modes.helpers import enrollment_mode_display from course_modes.helpers import enrollment_mode_display
from course_modes.models import CourseMode, Mode, invalidate_course_mode_cache from course_modes.models import CourseMode, Mode, invalidate_course_mode_cache, get_cosmetic_display_price
from course_modes.tests.factories import CourseModeFactory from course_modes.tests.factories import CourseModeFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
)
@ddt.ddt @ddt.ddt
...@@ -474,3 +479,26 @@ class CourseModeModelTest(TestCase): ...@@ -474,3 +479,26 @@ class CourseModeModelTest(TestCase):
self.assertTrue(is_error_expected, "Did not expect a ValidationError to be thrown.") self.assertTrue(is_error_expected, "Did not expect a ValidationError to be thrown.")
else: else:
self.assertFalse(is_error_expected, "Expected a ValidationError to be thrown.") self.assertFalse(is_error_expected, "Expected a ValidationError to be thrown.")
class TestDisplayPrices(ModuleStoreTestCase):
@override_settings(PAID_COURSE_REGISTRATION_CURRENCY=["USD", "$"])
def test_get_cosmetic_display_price(self):
"""
Check that get_cosmetic_display_price() returns the correct price given its inputs.
"""
course = CourseFactory.create()
registration_price = 99
course.cosmetic_display_price = 10
with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
# Since registration_price is set, it overrides the cosmetic_display_price and should be returned
self.assertEqual(get_cosmetic_display_price(course), "$99")
registration_price = 0
with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
# Since registration_price is not set, cosmetic_display_price should be returned
self.assertEqual(get_cosmetic_display_price(course), "$10")
course.cosmetic_display_price = 0
# Since both prices are not set, there is no price, thus "Free"
self.assertEqual(get_cosmetic_display_price(course), "Free")
...@@ -237,18 +237,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): ...@@ -237,18 +237,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of sql queries to default, # # of sql queries to default,
# # of mongo queries, # # of mongo queries,
# ) # )
('no_overrides', 1, True, False): (23, 1), ('no_overrides', 1, True, False): (24, 1),
('no_overrides', 2, True, False): (23, 1), ('no_overrides', 2, True, False): (24, 1),
('no_overrides', 3, True, False): (23, 1), ('no_overrides', 3, True, False): (24, 1),
('ccx', 1, True, False): (23, 1), ('ccx', 1, True, False): (24, 1),
('ccx', 2, True, False): (23, 1), ('ccx', 2, True, False): (24, 1),
('ccx', 3, True, False): (23, 1), ('ccx', 3, True, False): (24, 1),
('no_overrides', 1, False, False): (23, 1), ('no_overrides', 1, False, False): (24, 1),
('no_overrides', 2, False, False): (23, 1), ('no_overrides', 2, False, False): (24, 1),
('no_overrides', 3, False, False): (23, 1), ('no_overrides', 3, False, False): (24, 1),
('ccx', 1, False, False): (23, 1), ('ccx', 1, False, False): (24, 1),
('ccx', 2, False, False): (23, 1), ('ccx', 2, False, False): (24, 1),
('ccx', 3, False, False): (23, 1), ('ccx', 3, False, False): (24, 1),
} }
...@@ -260,19 +260,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): ...@@ -260,19 +260,19 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True __test__ = True
TEST_DATA = { TEST_DATA = {
('no_overrides', 1, True, False): (23, 3), ('no_overrides', 1, True, False): (24, 3),
('no_overrides', 2, True, False): (23, 3), ('no_overrides', 2, True, False): (24, 3),
('no_overrides', 3, True, False): (23, 3), ('no_overrides', 3, True, False): (24, 3),
('ccx', 1, True, False): (23, 3), ('ccx', 1, True, False): (24, 3),
('ccx', 2, True, False): (23, 3), ('ccx', 2, True, False): (24, 3),
('ccx', 3, True, False): (23, 3), ('ccx', 3, True, False): (24, 3),
('ccx', 1, True, True): (24, 3), ('ccx', 1, True, True): (25, 3),
('ccx', 2, True, True): (24, 3), ('ccx', 2, True, True): (25, 3),
('ccx', 3, True, True): (24, 3), ('ccx', 3, True, True): (25, 3),
('no_overrides', 1, False, False): (23, 3), ('no_overrides', 1, False, False): (24, 3),
('no_overrides', 2, False, False): (23, 3), ('no_overrides', 2, False, False): (24, 3),
('no_overrides', 3, False, False): (23, 3), ('no_overrides', 3, False, False): (24, 3),
('ccx', 1, False, False): (23, 3), ('ccx', 1, False, False): (24, 3),
('ccx', 2, False, False): (23, 3), ('ccx', 2, False, False): (24, 3),
('ccx', 3, False, False): (23, 3), ('ccx', 3, False, False): (24, 3),
} }
...@@ -388,7 +388,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest ...@@ -388,7 +388,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
def test_num_queries_instructor_paced(self): def test_num_queries_instructor_paced(self):
self.fetch_course_info_with_queries(self.instructor_paced_course, 25, 3) self.fetch_course_info_with_queries(self.instructor_paced_course, 26, 3)
def test_num_queries_self_paced(self): def test_num_queries_self_paced(self):
self.fetch_course_info_with_queries(self.self_paced_course, 25, 3) self.fetch_course_info_with_queries(self.self_paced_course, 26, 3)
...@@ -211,8 +211,8 @@ class IndexQueryTestCase(ModuleStoreTestCase): ...@@ -211,8 +211,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS = 20 NUM_PROBLEMS = 20
@ddt.data( @ddt.data(
(ModuleStoreEnum.Type.mongo, 10, 144), (ModuleStoreEnum.Type.mongo, 10, 145),
(ModuleStoreEnum.Type.split, 4, 144), (ModuleStoreEnum.Type.split, 4, 145),
) )
@ddt.unpack @ddt.unpack
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count): def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
...@@ -577,26 +577,6 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -577,26 +577,6 @@ class ViewsTestCase(ModuleStoreTestCase):
response = self.client.get(request_url) response = self.client.get(request_url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
@override_settings(PAID_COURSE_REGISTRATION_CURRENCY=["USD", "$"])
def test_get_cosmetic_display_price(self):
"""
Check that get_cosmetic_display_price() returns the correct price given its inputs.
"""
registration_price = 99
self.course.cosmetic_display_price = 10
with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
# Since registration_price is set, it overrides the cosmetic_display_price and should be returned
self.assertEqual(views.get_cosmetic_display_price(self.course), "$99")
registration_price = 0
with patch('course_modes.models.CourseMode.min_course_price_for_currency', return_value=registration_price):
# Since registration_price is not set, cosmetic_display_price should be returned
self.assertEqual(views.get_cosmetic_display_price(self.course), "$10")
self.course.cosmetic_display_price = 0
# Since both prices are not set, there is no price, thus "Free"
self.assertEqual(views.get_cosmetic_display_price(self.course), "Free")
def test_jump_to_invalid(self): def test_jump_to_invalid(self):
# TODO add a test for invalid location # TODO add a test for invalid location
# TODO add a test for no data * # TODO add a test for no data *
...@@ -1464,12 +1444,12 @@ class ProgressPageTests(ProgressPageBaseTests): ...@@ -1464,12 +1444,12 @@ class ProgressPageTests(ProgressPageBaseTests):
"""Test that query counts remain the same for self-paced and instructor-paced courses.""" """Test that query counts remain the same for self-paced and instructor-paced courses."""
SelfPacedConfiguration(enabled=self_paced_enabled).save() SelfPacedConfiguration(enabled=self_paced_enabled).save()
self.setup_course(self_paced=self_paced) self.setup_course(self_paced=self_paced)
with self.assertNumQueries(40, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST), check_mongo_calls(1): with self.assertNumQueries(41, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST), check_mongo_calls(1):
self._get_progress_page() self._get_progress_page()
@ddt.data( @ddt.data(
(False, 40, 26), (False, 41, 27),
(True, 33, 22) (True, 34, 23)
) )
@ddt.unpack @ddt.unpack
def test_progress_queries(self, enable_waffle, initial, subsequent): def test_progress_queries(self, enable_waffle, initial, subsequent):
......
...@@ -22,6 +22,7 @@ from web_fragments.fragment import Fragment ...@@ -22,6 +22,7 @@ from web_fragments.fragment import Fragment
from edxmako.shortcuts import render_to_response, render_to_string from edxmako.shortcuts import render_to_response, render_to_string
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from lms.djangoapps.gating.api import get_entrance_exam_score_ratio, get_entrance_exam_usage_key from lms.djangoapps.gating.api import get_entrance_exam_score_ratio, get_entrance_exam_usage_key
from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory
from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.crawlers.models import CrawlersConfig
...@@ -34,6 +35,7 @@ from openedx.features.course_experience.views.course_sock import CourseSockFragm ...@@ -34,6 +35,7 @@ from openedx.features.course_experience.views.course_sock import CourseSockFragm
from openedx.features.enterprise_support.api import data_sharing_consent_required from openedx.features.enterprise_support.api import data_sharing_consent_required
from shoppingcart.models import CourseRegistrationCode from shoppingcart.models import CourseRegistrationCode
from student.views import is_course_blocked from student.views import is_course_blocked
from student.models import CourseEnrollment
from util.views import ensure_valid_course_key from util.views import ensure_valid_course_key
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.x_module import STUDENT_VIEW from xmodule.x_module import STUDENT_VIEW
...@@ -52,8 +54,6 @@ from ..model_data import FieldDataCache ...@@ -52,8 +54,6 @@ from ..model_data import FieldDataCache
from ..module_render import get_module_for_descriptor, toc_for_course from ..module_render import get_module_for_descriptor, toc_for_course
from .views import ( from .views import (
CourseTabView, CourseTabView,
check_and_get_upgrade_link,
get_cosmetic_verified_display_price
) )
log = logging.getLogger("edx.courseware.views.index") log = logging.getLogger("edx.courseware.views.index")
...@@ -325,6 +325,7 @@ class CoursewareIndex(View): ...@@ -325,6 +325,7 @@ class CoursewareIndex(View):
""" """
course_url_name = default_course_url_name(self.course.id) course_url_name = default_course_url_name(self.course.id)
course_url = reverse(course_url_name, kwargs={'course_id': unicode(self.course.id)}) course_url = reverse(course_url_name, kwargs={'course_id': unicode(self.course.id)})
courseware_context = { courseware_context = {
'csrf': csrf(self.request)['csrf_token'], 'csrf': csrf(self.request)['csrf_token'],
'course': self.course, 'course': self.course,
...@@ -344,11 +345,14 @@ class CoursewareIndex(View): ...@@ -344,11 +345,14 @@ class CoursewareIndex(View):
'section_title': None, 'section_title': None,
'sequence_title': None, 'sequence_title': None,
'disable_accordion': COURSE_OUTLINE_PAGE_FLAG.is_enabled(self.course.id), 'disable_accordion': COURSE_OUTLINE_PAGE_FLAG.is_enabled(self.course.id),
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
'upgrade_link': check_and_get_upgrade_link(request, self.effective_user, self.course.id),
'upgrade_price': get_cosmetic_verified_display_price(self.course),
# ENDTODO
} }
courseware_context.update(
get_experiment_user_metadata_context(
request,
self.course,
self.effective_user,
)
)
table_of_contents = toc_for_course( table_of_contents = toc_for_course(
self.effective_user, self.effective_user,
self.request, self.request,
......
...@@ -14,7 +14,7 @@ import waffle ...@@ -14,7 +14,7 @@ import waffle
from certificates import api as certs_api from certificates import api as certs_api
from certificates.models import CertificateStatuses from certificates.models import CertificateStatuses
from commerce.utils import EcommerceService from commerce.utils import EcommerceService
from course_modes.models import CourseMode from course_modes.models import (CourseMode, get_course_prices)
from courseware.access import has_access, has_ccx_coach_role from courseware.access import has_access, has_ccx_coach_role
from courseware.access_utils import check_course_open_for_learner from courseware.access_utils import check_course_open_for_learner
from courseware.courses import ( from courseware.courses import (
...@@ -61,6 +61,7 @@ from ipware.ip import get_ip ...@@ -61,6 +61,7 @@ from ipware.ip import get_ip
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
from lms.djangoapps.ccx.utils import prep_course_for_grading from lms.djangoapps.ccx.utils import prep_course_for_grading
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory
from lms.djangoapps.instructor.enrollment import uses_shib from lms.djangoapps.instructor.enrollment import uses_shib
from lms.djangoapps.instructor.views.api import require_global_staff from lms.djangoapps.instructor.views.api import require_global_staff
...@@ -322,13 +323,14 @@ def course_info(request, course_id): ...@@ -322,13 +323,14 @@ def course_info(request, course_id):
'dates_fragment': dates_fragment, 'dates_fragment': dates_fragment,
'url_to_enroll': CourseTabView.url_to_enroll(course_key), 'url_to_enroll': CourseTabView.url_to_enroll(course_key),
'course_tools': course_tools, 'course_tools': course_tools,
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
'course_tools': course_tools,
# ENDTODO
} }
context.update(
get_experiment_user_metadata_context(
request,
course,
user,
)
)
# Get the URL of the user's last position in order to display the 'where you were last' message # Get the URL of the user's last position in order to display the 'where you were last' message
context['resume_course_url'] = None context['resume_course_url'] = None
...@@ -348,20 +350,6 @@ def course_info(request, course_id): ...@@ -348,20 +350,6 @@ def course_info(request, course_id):
UPGRADE_COOKIE_NAME = 'show_upgrade_notification' UPGRADE_COOKIE_NAME = 'show_upgrade_notification'
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
def check_and_get_upgrade_link(request, user, course_id):
upgrade_link = None
if request.user.is_authenticated():
upgrade_data = VerifiedUpgradeDeadlineDate(None, user, course_id=course_id)
if upgrade_data.is_enabled:
upgrade_link = upgrade_data.link
request.need_to_set_upgrade_cookie = True
return upgrade_link
# ENDTODO
class StaticCourseTabView(EdxFragmentView): class StaticCourseTabView(EdxFragmentView):
""" """
View that displays a static course tab with a given name. View that displays a static course tab with a given name.
...@@ -521,7 +509,8 @@ class CourseTabView(EdxFragmentView): ...@@ -521,7 +509,8 @@ class CourseTabView(EdxFragmentView):
# Disable student view button if user is staff and # Disable student view button if user is staff and
# course is not yet visible to students. # course is not yet visible to students.
supports_preview_menu = False supports_preview_menu = False
return {
context = {
'course': course, 'course': course,
'tab': tab, 'tab': tab,
'active_page': tab.get('type', None), 'active_page': tab.get('type', None),
...@@ -530,11 +519,15 @@ class CourseTabView(EdxFragmentView): ...@@ -530,11 +519,15 @@ class CourseTabView(EdxFragmentView):
'supports_preview_menu': supports_preview_menu, 'supports_preview_menu': supports_preview_menu,
'uses_pattern_library': True, 'uses_pattern_library': True,
'disable_courseware_js': True, 'disable_courseware_js': True,
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
'upgrade_link': check_and_get_upgrade_link(request, request.user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
# ENDTODO
} }
context.update(
get_experiment_user_metadata_context(
request,
course,
request.user,
)
)
return context
def render_to_fragment(self, request, course=None, page_context=None, **kwargs): def render_to_fragment(self, request, course=None, page_context=None, **kwargs):
""" """
...@@ -585,59 +578,6 @@ def registered_for_course(course, user): ...@@ -585,59 +578,6 @@ def registered_for_course(course, user):
return False return False
def get_cosmetic_verified_display_price(course):
"""
Returns the minimum verified cert course price as a string preceded by correct currency, or 'Free'.
"""
return get_course_prices(course, verified_only=True)[1]
def get_cosmetic_display_price(course):
"""
Returns the course price as a string preceded by correct currency, or 'Free'.
"""
return get_course_prices(course)[1]
def get_course_prices(course, verified_only=False):
"""
Return registration_price and cosmetic_display_prices.
registration_price is the minimum price for the course across all course modes.
cosmetic_display_prices is the course price as a string preceded by correct currency, or 'Free'.
"""
# Find the
if verified_only:
registration_price = CourseMode.min_course_price_for_verified_for_currency(
course.id,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
else:
registration_price = CourseMode.min_course_price_for_currency(
course.id,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]
)
currency_symbol = settings.PAID_COURSE_REGISTRATION_CURRENCY[1]
if registration_price > 0:
price = registration_price
# Handle course overview objects which have no cosmetic_display_price
elif hasattr(course, 'cosmetic_display_price'):
price = course.cosmetic_display_price
else:
price = None
if price:
# Translators: This will look like '$50', where {currency_symbol} is a symbol such as '$' and {price} is a
# numerical amount in that currency. Adjust this display as needed for your language.
cosmetic_display_price = _("{currency_symbol}{price}").format(currency_symbol=currency_symbol, price=price)
else:
# Translators: This refers to the cost of the course. In this case, the course costs nothing so it is free.
cosmetic_display_price = _('Free')
return registration_price, cosmetic_display_price
class EnrollStaffView(View): class EnrollStaffView(View):
""" """
Displays view for registering in the course to a global staff user. Displays view for registering in the course to a global staff user.
...@@ -927,7 +867,6 @@ def _progress(request, course_key, student_id): ...@@ -927,7 +867,6 @@ def _progress(request, course_key, student_id):
grade_summary = course_grade.summary grade_summary = course_grade.summary
studio_url = get_studio_url(course, 'settings/grading') studio_url = get_studio_url(course, 'settings/grading')
# checking certificate generation configuration # checking certificate generation configuration
enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(student, course_key) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(student, course_key)
...@@ -943,11 +882,14 @@ def _progress(request, course_key, student_id): ...@@ -943,11 +882,14 @@ def _progress(request, course_key, student_id):
'passed': is_course_passed(course, grade_summary), 'passed': is_course_passed(course, grade_summary),
'credit_course_requirements': _credit_course_requirements(course_key, student), 'credit_course_requirements': _credit_course_requirements(course_key, student),
'certificate_data': _get_cert_data(student, course, course_key, is_active, enrollment_mode), 'certificate_data': _get_cert_data(student, course, course_key, is_active, enrollment_mode),
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
'upgrade_link': check_and_get_upgrade_link(request, student, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
# ENDTODO
} }
context.update(
get_experiment_user_metadata_context(
request,
course,
student,
)
)
with outer_atomic(): with outer_atomic():
response = render_to_response('courseware/progress.html', context) response = render_to_response('courseware/progress.html', context)
......
...@@ -24,6 +24,7 @@ from rest_framework import status ...@@ -24,6 +24,7 @@ from rest_framework import status
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
import django_comment_client.utils as utils import django_comment_client.utils as utils
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
import lms.lib.comment_client as cc import lms.lib.comment_client as cc
from courseware.access import has_access from courseware.access import has_access
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
...@@ -44,7 +45,6 @@ from django_comment_client.utils import ( ...@@ -44,7 +45,6 @@ from django_comment_client.utils import (
strip_none strip_none
) )
from django_comment_common.utils import ThreadContext, get_course_discussion_settings, set_course_discussion_settings from django_comment_common.utils import ThreadContext, get_course_discussion_settings, set_course_discussion_settings
from lms.djangoapps.courseware.views.views import check_and_get_upgrade_link, get_cosmetic_verified_display_price
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from student.models import CourseEnrollment from student.models import CourseEnrollment
from util.json_request import JsonResponse, expect_json from util.json_request import JsonResponse, expect_json
...@@ -481,13 +481,16 @@ def _create_discussion_board_context(request, base_context, thread=None): ...@@ -481,13 +481,16 @@ def _create_discussion_board_context(request, base_context, thread=None):
'category_map': course_settings["category_map"], 'category_map': course_settings["category_map"],
'course_settings': course_settings, 'course_settings': course_settings,
'is_commentable_divided': is_commentable_divided(course_key, discussion_id, course_discussion_settings), 'is_commentable_divided': is_commentable_divided(course_key, discussion_id, course_discussion_settings),
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
'upgrade_link': check_and_get_upgrade_link(request, user, course.id),
'upgrade_price': get_cosmetic_verified_display_price(course),
# ENDTODO
# If the default topic id is None the front-end code will look for a topic that contains "General" # If the default topic id is None the front-end code will look for a topic that contains "General"
'discussion_default_topic_id': _get_discussion_default_topic_id(course), 'discussion_default_topic_id': _get_discussion_default_topic_id(course),
}) })
context.update(
get_experiment_user_metadata_context(
request,
course,
user,
)
)
return context return context
......
from student.models import CourseEnrollment
from course_modes.models import (
get_cosmetic_verified_display_price
)
from courseware.date_summary import (
VerifiedUpgradeDeadlineDate
)
def check_and_get_upgrade_link(request, user, course_id):
"""
For an authenticated user, return a link to allow them to upgrade
in the specified course.
"""
if request.user.is_authenticated():
upgrade_data = VerifiedUpgradeDeadlineDate(None, user, course_id=course_id)
if upgrade_data.is_enabled:
request.need_to_set_upgrade_cookie = True
return upgrade_data
return None
def get_experiment_user_metadata_context(request, course, user):
"""
Return a context dictionary with the keys used by the user_metadata.html.
"""
enrollment_mode = None
enrollment_time = None
try:
enrollment = CourseEnrollment.objects.get(user_id=user.id, course_id=course.id)
if enrollment.is_active:
enrollment_mode = enrollment.mode
enrollment_time = enrollment.created
except CourseEnrollment.DoesNotExist:
pass # Not enrolled, used the default None values
upgrade_data = check_and_get_upgrade_link(request, user, course.id)
return {
'upgrade_link': upgrade_data and upgrade_data.link,
'upgrade_price': get_cosmetic_verified_display_price(course),
'enrollment_mode': enrollment_mode,
'enrollment_time': enrollment_time,
'pacing_type': 'self_paced' if course.self_paced else 'instructor_paced',
'upgrade_deadline': upgrade_data and upgrade_data.date,
'course_key': course.id,
}
...@@ -104,6 +104,7 @@ from pipeline_mako import render_require_js_path_overrides ...@@ -104,6 +104,7 @@ from pipeline_mako import render_require_js_path_overrides
<%block name="head_extra"/> <%block name="head_extra"/>
<%include file="/courseware/experiments.html"/> <%include file="/courseware/experiments.html"/>
<%include file="user_metadata.html"/>
<%static:optional_include_mako file="head-extra.html" is_theming_enabled="True" /> <%static:optional_include_mako file="head-extra.html" is_theming_enabled="True" />
<%include file="widgets/optimizely.html" /> <%include file="widgets/optimizely.html" />
......
<%page expression_filter="h"/>
<%!
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from eventtracking import tracker
from opaque_keys.edx.keys import CourseKey
%>
<%
user_metadata = {
key: context.get(key)
for key in (
'username',
'user_id',
'course_id',
'enrollment_mode',
'upgrade_link',
'upgrade_deadline',
'upgrade_price',
'pacing_type',
)
}
if user:
user_metadata['username'] = user.username
user_metadata['user_id'] = user.id
for datekey in ('schedule_start', 'enrollment_time'):
user_metadata[datekey] = (
context.get(datekey).isoformat() if context.get(datekey) else None
)
course_key = context.get('course_key')
if course and not course_key:
course_key = course.id
if course_key:
if isinstance(course_key, CourseKey):
user_metadata['course_key_fields'] = {
'org': course_key.org,
'course': course_key.course,
'run': course_key.run,
}
if not course_id:
user_metadata['course_id'] = unicode(course_key)
elif isinstance(course_key, basestring):
user_metadata['course_id'] = course_key
%>
<script type="application/json" id="user-metadata">
${user_metadata | n, dump_js_escaped_json}
</script>
...@@ -160,7 +160,7 @@ class TestCourseHomePage(CourseHomePageTestCase): ...@@ -160,7 +160,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
course_home_url(self.course) course_home_url(self.course)
# Fetch the view and verify the query counts # Fetch the view and verify the query counts
with self.assertNumQueries(40, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with self.assertNumQueries(41, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
with check_mongo_calls(4): with check_mongo_calls(4):
url = course_home_url(self.course) url = course_home_url(self.course)
self.client.get(url) self.client.get(url)
......
...@@ -127,7 +127,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase): ...@@ -127,7 +127,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
course_updates_url(self.course) course_updates_url(self.course)
# Fetch the view and verify that the query counts haven't changed # Fetch the view and verify that the query counts haven't changed
with self.assertNumQueries(31, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with self.assertNumQueries(32, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
with check_mongo_calls(4): with check_mongo_calls(4):
url = course_updates_url(self.course) url = course_updates_url(self.course)
self.client.get(url) self.client.get(url)
...@@ -6,9 +6,8 @@ from opaque_keys.edx.keys import CourseKey ...@@ -6,9 +6,8 @@ from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
from student.models import CourseEnrollment from student.models import CourseEnrollment
from course_modes.models import CourseMode from course_modes.models import CourseMode, get_cosmetic_verified_display_price
from courseware.date_summary import VerifiedUpgradeDeadlineDate from courseware.date_summary import VerifiedUpgradeDeadlineDate
from courseware.views.views import get_cosmetic_verified_display_price
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
......
...@@ -2392,7 +2392,7 @@ class MakoTemplateLinter(BaseLinter): ...@@ -2392,7 +2392,7 @@ class MakoTemplateLinter(BaseLinter):
contexts = [{'index': 0, 'type': 'html'}] contexts = [{'index': 0, 'type': 'html'}]
javascript_types = [ javascript_types = [
'text/javascript', 'text/ecmascript', 'application/ecmascript', 'application/javascript', 'text/javascript', 'text/ecmascript', 'application/ecmascript', 'application/javascript',
'text/x-mathjax-config', 'json/xblock-args' 'text/x-mathjax-config', 'json/xblock-args', 'application/json',
] ]
html_types = ['text/template'] html_types = ['text/template']
for context in contexts_re.finditer(mako_template): for context in contexts_re.finditer(mako_template):
......
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