From e8e713c508f64f081880ea4e850a95d2ba73240f Mon Sep 17 00:00:00 2001 From: Malik Shahzad Date: Mon, 2 May 2016 14:37:30 +0500 Subject: [PATCH 1/5] Add CourseDetails model to course_about template context. --- lms/djangoapps/courseware/views.py | 3 ++ .../core/djangoapps/models/course_details.py | 42 +++++++++++-------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 6a9418e570..a825933bd7 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -38,6 +38,7 @@ import shoppingcart import survey.utils import survey.views from certificates import api as certs_api +from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.core.lib.gating import api as gating_api from commerce.utils import EcommerceService from course_modes.models import CourseMode @@ -883,6 +884,7 @@ def course_about(request, course_id): with modulestore().bulk_operations(course_key): permission = get_permission_for_course_about() course = get_course_with_access(request.user, permission, course_key) + course_details = CourseDetails.populate(course) modes = CourseMode.modes_for_course_dict(course_key) if theming_helpers.get_value('ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)): @@ -962,6 +964,7 @@ def course_about(request, course_id): context = { 'course': course, + 'course_details': course_details, 'staff_access': staff_access, 'studio_url': studio_url, 'registered': registered, diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py index 0db50a3803..6f0a2de250 100644 --- a/openedx/core/djangoapps/models/course_details.py +++ b/openedx/core/djangoapps/models/course_details.py @@ -94,27 +94,33 @@ class CourseDetails(object): Fetch the course details for the given course from persistence and return a CourseDetails model. """ - descriptor = modulestore().get_course(course_key) - course_details = cls(course_key.org, course_key.course, course_key.run) + return cls.populate(modulestore().get_course(course_key)) - course_details.start_date = descriptor.start - course_details.end_date = descriptor.end - course_details.enrollment_start = descriptor.enrollment_start - course_details.enrollment_end = descriptor.enrollment_end - course_details.pre_requisite_courses = descriptor.pre_requisite_courses - course_details.course_image_name = descriptor.course_image - course_details.course_image_asset_path = course_image_url(descriptor, 'course_image') - course_details.banner_image_name = descriptor.banner_image - course_details.banner_image_asset_path = course_image_url(descriptor, 'banner_image') - course_details.video_thumbnail_image_name = descriptor.video_thumbnail_image - course_details.video_thumbnail_image_asset_path = course_image_url(descriptor, 'video_thumbnail_image') - course_details.language = descriptor.language - course_details.self_paced = descriptor.self_paced - course_details.learning_info = descriptor.learning_info - course_details.instructor_info = descriptor.instructor_info + @classmethod + def populate(cls, course_descriptor): + """ + Returns a fully populated CourseDetails model given the course descriptor + """ + course_key = course_descriptor.id + course_details = cls(course_key.org, course_key.course, course_key.run) + course_details.start_date = course_descriptor.start + course_details.end_date = course_descriptor.end + course_details.enrollment_start = course_descriptor.enrollment_start + course_details.enrollment_end = course_descriptor.enrollment_end + course_details.pre_requisite_courses = course_descriptor.pre_requisite_courses + course_details.course_image_name = course_descriptor.course_image + course_details.course_image_asset_path = course_image_url(course_descriptor, 'course_image') + course_details.banner_image_name = course_descriptor.banner_image + course_details.banner_image_asset_path = course_image_url(course_descriptor, 'banner_image') + course_details.video_thumbnail_image_name = course_descriptor.video_thumbnail_image + course_details.video_thumbnail_image_asset_path = course_image_url(course_descriptor, 'video_thumbnail_image') + course_details.language = course_descriptor.language + course_details.self_paced = course_descriptor.self_paced + course_details.learning_info = course_descriptor.learning_info + course_details.instructor_info = course_descriptor.instructor_info # Default course license is "All Rights Reserved" - course_details.license = getattr(descriptor, "license", "all-rights-reserved") + course_details.license = getattr(course_descriptor, "license", "all-rights-reserved") course_details.intro_video = cls.fetch_youtube_video_id(course_key) From 9c71395de1235e666d1c174181880299d8e4e84b Mon Sep 17 00:00:00 2001 From: Douglas Hall Date: Wed, 4 May 2016 13:47:20 -0400 Subject: [PATCH 2/5] Check microsite configurations for PLATFORM_NAME setting before defaulting to global setting --- lms/djangoapps/student_account/views.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 1231143e6f..6be8e300cb 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -20,7 +20,6 @@ from django.views.decorators.http import require_http_methods from lang_pref.api import released_languages, all_languages from edxmako.shortcuts import render_to_response -from microsite_configuration import microsite from external_auth.login_and_register import ( login as external_auth_login, @@ -37,6 +36,7 @@ from third_party_auth import pipeline from third_party_auth.decorators import xframe_allow_whitelisted from util.bad_request_rate_limiter import BadRequestRateLimiter +from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site, get_value as get_themed_value from openedx.core.djangoapps.user_api.accounts.api import request_password_change from openedx.core.djangoapps.user_api.errors import UserNotFound @@ -67,11 +67,12 @@ def login_and_registration_form(request, initial_mode="login"): # Retrieve the form descriptions from the user API form_descriptions = _get_form_descriptions(request) - # If this is a microsite, revert to the old login/registration pages. + # If this is a themed site, revert to the old login/registration pages. # We need to do this for now to support existing themes. - # Microsites can use the new logistration page by setting - # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their microsites configuration file. - if microsite.is_request_in_microsite() and not microsite.get_value('ENABLE_COMBINED_LOGIN_REGISTRATION', False): + # Themed sites can use the new logistration page by setting + # 'ENABLE_COMBINED_LOGIN_REGISTRATION' in their theme/microsite + # configuration settings. + if is_request_in_themed_site() and not get_themed_value('ENABLE_COMBINED_LOGIN_REGISTRATION', False): if initial_mode == "login": return old_login_view(request) elif initial_mode == "register": @@ -102,7 +103,7 @@ def login_and_registration_form(request, initial_mode="login"): 'initial_mode': initial_mode, 'third_party_auth': _third_party_auth_context(request, redirect_to), 'third_party_auth_hint': third_party_auth_hint or '', - 'platform_name': settings.PLATFORM_NAME, + 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), # Include form descriptions retrieved from the user API. # We could have the JS client make these requests directly, @@ -389,7 +390,7 @@ def account_settings_context(request): 'options': all_languages(), } }, - 'platform_name': settings.PLATFORM_NAME, + 'platform_name': get_themed_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}), 'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}), 'disable_courseware_js': True, From ee7f0302b006d7dbf17f60c7c2b1051fb9ca89a1 Mon Sep 17 00:00:00 2001 From: Douglas Hall Date: Wed, 4 May 2016 23:45:16 -0400 Subject: [PATCH 3/5] Check microsite configurations for PLATFORM_NAME setting before defaulting to global setting --- openedx/core/djangoapps/user_api/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openedx/core/djangoapps/user_api/views.py b/openedx/core/djangoapps/user_api/views.py index 065c08aaff..4061cdb9fa 100644 --- a/openedx/core/djangoapps/user_api/views.py +++ b/openedx/core/djangoapps/user_api/views.py @@ -20,7 +20,6 @@ from rest_framework.views import APIView from rest_framework.exceptions import ParseError from django_countries import countries from opaque_keys.edx.locations import SlashSeparatedCourseKey -from microsite_configuration import microsite from openedx.core.lib.api.permissions import ApiKeyHeaderPermission import third_party_auth @@ -29,6 +28,7 @@ from edxmako.shortcuts import marketing_link from student.forms import get_registration_extension_form from student.views import create_account_with_params from student.cookies import set_logged_in_cookies +from openedx.core.djangoapps.theming.helpers import get_value as get_themed_value from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser from util.json_request import JsonResponse from .preferences.api import update_email_opt_in @@ -189,7 +189,7 @@ class RegistrationView(APIView): # Backwards compatibility: Honor code is required by default, unless # explicitly set to "optional" in Django settings. - self._extra_fields_setting = copy.deepcopy(microsite.get_value('REGISTRATION_EXTRA_FIELDS')) + self._extra_fields_setting = copy.deepcopy(get_themed_value('REGISTRATION_EXTRA_FIELDS')) if not self._extra_fields_setting: self._extra_fields_setting = copy.deepcopy(settings.REGISTRATION_EXTRA_FIELDS) self._extra_fields_setting["honor_code"] = self._extra_fields_setting.get("honor_code", "required") @@ -687,14 +687,14 @@ class RegistrationView(APIView): # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. label = _(u"I agree to the {platform_name} {terms_of_service}.").format( - platform_name=settings.PLATFORM_NAME, + platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_link ) # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. error_msg = _(u"You must agree to the {platform_name} {terms_of_service}.").format( - platform_name=settings.PLATFORM_NAME, + platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_link ) @@ -730,14 +730,14 @@ class RegistrationView(APIView): # Translators: "Terms of service" is a legal document users must agree to # in order to register a new account. label = _(u"I agree to the {platform_name} {terms_of_service}.").format( - platform_name=settings.PLATFORM_NAME, + platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_link ) # Translators: "Terms of service" is a legal document users must agree to # in order to register a new account. error_msg = _(u"You must agree to the {platform_name} {terms_of_service}.").format( - platform_name=settings.PLATFORM_NAME, + platform_name=get_themed_value("PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_link ) From ab8093ccd6e55980268e28119eba9e0d621215fa Mon Sep 17 00:00:00 2001 From: Douglas Hall Date: Mon, 2 May 2016 19:05:55 -0400 Subject: [PATCH 4/5] Allow content gating to work with library_content blocks --- lms/djangoapps/courseware/grades.py | 2 +- .../courseware/tests/test_grades.py | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py index 0529a27bef..b1ce6d43e2 100644 --- a/lms/djangoapps/courseware/grades.py +++ b/lms/djangoapps/courseware/grades.py @@ -839,7 +839,7 @@ def _calculate_score_for_modules(user_id, course, modules): # Iterate over all of the exam modules to get score percentage of user for each of them module_percentages = [] - ignore_categories = ['course', 'chapter', 'sequential', 'vertical', 'randomize'] + ignore_categories = ['course', 'chapter', 'sequential', 'vertical', 'randomize', 'library_content'] for index, module in enumerate(modules): if module.category not in ignore_categories and (module.graded or module.has_score): module_score = scores_client.get(locations[index]) diff --git a/lms/djangoapps/courseware/tests/test_grades.py b/lms/djangoapps/courseware/tests/test_grades.py index 5fce957284..c62657347e 100644 --- a/lms/djangoapps/courseware/tests/test_grades.py +++ b/lms/djangoapps/courseware/tests/test_grades.py @@ -366,13 +366,19 @@ class TestGetModuleScore(LoginEnrollmentTestCase, SharedModuleStoreTestCase): cls.seq1 = ItemFactory.create( parent=cls.chapter, category='sequential', - display_name="Test Sequential", + display_name="Test Sequential 1", graded=True ) cls.seq2 = ItemFactory.create( parent=cls.chapter, category='sequential', - display_name="Test Sequential", + display_name="Test Sequential 2", + graded=True + ) + cls.seq3 = ItemFactory.create( + parent=cls.chapter, + category='sequential', + display_name="Test Sequential 3", graded=True ) cls.vert1 = ItemFactory.create( @@ -385,11 +391,21 @@ class TestGetModuleScore(LoginEnrollmentTestCase, SharedModuleStoreTestCase): category='vertical', display_name='Test Vertical 2' ) + cls.vert3 = ItemFactory.create( + parent=cls.seq3, + category='vertical', + display_name='Test Vertical 3' + ) cls.randomize = ItemFactory.create( parent=cls.vert2, category='randomize', display_name='Test Randomize' ) + cls.library_content = ItemFactory.create( + parent=cls.vert3, + category='library_content', + display_name='Test Library Content' + ) problem_xml = MultipleChoiceResponseXMLFactory().build_xml( question_text='The correct answer is Choice 3', choices=[False, False, True, False], @@ -420,6 +436,19 @@ class TestGetModuleScore(LoginEnrollmentTestCase, SharedModuleStoreTestCase): data=problem_xml ) + cls.problem5 = ItemFactory.create( + parent=cls.library_content, + category="problem", + display_name="Test Problem 5", + data=problem_xml + ) + cls.problem6 = ItemFactory.create( + parent=cls.library_content, + category="problem", + display_name="Test Problem 6", + data=problem_xml + ) + def setUp(self): """ Set up test course @@ -485,6 +514,16 @@ class TestGetModuleScore(LoginEnrollmentTestCase, SharedModuleStoreTestCase): score = get_module_score(self.request.user, self.course, self.seq2) self.assertEqual(score, 1.0) + def test_get_module_score_with_library_content(self): + """ + Test test_get_module_score_with_library_content + """ + answer_problem(self.course, self.request, self.problem5) + answer_problem(self.course, self.request, self.problem6) + + score = get_module_score(self.request.user, self.course, self.seq3) + self.assertEqual(score, 1.0) + def answer_problem(course, request, problem, score=1): """ From bdc398de1742b799877e3ec570c27109835347a8 Mon Sep 17 00:00:00 2001 From: Afzal Wali Date: Tue, 3 May 2016 17:48:53 +0500 Subject: [PATCH 5/5] Bumped the proctoring version. --- requirements/edx/github.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index 595ca1cbc6..ee1ab94962 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -90,7 +90,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.2#egg=xblock-utils==1.0.2 -e git+https://github.com/edx/edx-reverification-block.git@0.0.5#egg=edx-reverification-block==0.0.5 git+https://github.com/edx/edx-user-state-client.git@1.0.1#egg=edx-user-state-client==1.0.1 git+https://github.com/edx/xblock-lti-consumer.git@v1.0.6#egg=xblock-lti-consumer==1.0.6 -git+https://github.com/edx/edx-proctoring.git@0.12.16#egg=edx-proctoring==0.12.16 +git+https://github.com/edx/edx-proctoring.git@0.12.17#egg=edx-proctoring==0.12.17 # Third Party XBlocks -e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga