From 986dcff6261fbf4751abb13c5f75215a31364418 Mon Sep 17 00:00:00 2001 From: wajeeha-khalid Date: Thu, 28 Jul 2016 19:13:18 +0500 Subject: [PATCH] MA-2536: get 'course_about' url in enrollment --- .../tests/test_course_settings.py | 3 ++ .../contentstore/tests/test_utils.py | 49 ------------------- cms/djangoapps/contentstore/utils.py | 36 -------------- cms/djangoapps/contentstore/views/course.py | 4 +- cms/envs/aws.py | 1 + cms/envs/bok_choy.env.json | 1 + cms/envs/bok_choy.py | 2 + cms/envs/common.py | 1 + cms/envs/dev.py | 1 + cms/envs/devstack.py | 1 + cms/envs/test.py | 1 + cms/startup.py | 5 ++ .../startup_configurations/__init__.py | 0 .../startup_configurations/validate_config.py | 42 ++++++++++++++++ common/djangoapps/util/course.py | 28 +++++++++++ common/djangoapps/util/tests/test_course.py | 36 ++++++++++++++ .../mobile_api/tests/test_milestones.py | 37 +++++++++++--- lms/djangoapps/mobile_api/testutils.py | 6 ++- .../mobile_api/users/serializers.py | 8 ++- lms/djangoapps/mobile_api/users/tests.py | 28 +++++++---- lms/envs/aws.py | 2 + lms/envs/bok_choy.env.json | 1 + lms/envs/bok_choy.py | 2 + lms/envs/common.py | 1 + lms/envs/dev.py | 1 + lms/envs/devstack.py | 2 + lms/envs/test.py | 2 + lms/startup.py | 5 ++ 28 files changed, 197 insertions(+), 109 deletions(-) create mode 100644 common/djangoapps/startup_configurations/__init__.py create mode 100644 common/djangoapps/startup_configurations/validate_config.py create mode 100644 common/djangoapps/util/course.py create mode 100644 common/djangoapps/util/tests/test_course.py diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index f18e1c98b3..2462981647 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -219,6 +219,7 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin): (False, True, False), (True, True, True), ) + @override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) def test_visibility_of_entrance_exam_section(self, feature_flags): """ Tests entrance exam section is available if ENTRANCE_EXAMS feature is enabled no matter any other @@ -1161,6 +1162,7 @@ id=\"course-enrollment-end-time\" value=\"\" placeholder=\"HH:MM\" autocomplete= self._verify_editable(self._get_course_details_response(False)) @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_MKTG_SITE': True}) + @override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) def test_course_details_with_enabled_setting_global_staff(self): """ Test that user enrollment end date is editable in response. @@ -1170,6 +1172,7 @@ id=\"course-enrollment-end-time\" value=\"\" placeholder=\"HH:MM\" autocomplete= self._verify_editable(self._get_course_details_response(True)) @mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_MKTG_SITE': True}) + @override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) def test_course_details_with_enabled_setting_non_global_staff(self): """ Test that user enrollment end date is not editable in response. diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py index c4b9e667e4..7d451f9834 100644 --- a/cms/djangoapps/contentstore/tests/test_utils.py +++ b/cms/djangoapps/contentstore/tests/test_utils.py @@ -2,10 +2,8 @@ import collections from datetime import datetime, timedelta -import mock from pytz import UTC from django.test import TestCase -from django.test.utils import override_settings from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase @@ -20,53 +18,6 @@ from contentstore.tests.utils import CourseTestCase class LMSLinksTestCase(TestCase): """ Tests for LMS links. """ - def about_page_test(self): - """ Get URL for about page, no marketing site """ - # default for ENABLE_MKTG_SITE is False. - self.assertEquals(self.get_about_page_link(), "//localhost:8000/courses/mitX/101/test/about") - - @override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) - def about_page_marketing_site_test(self): - """ Get URL for about page, marketing root present. """ - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): - self.assertEquals(self.get_about_page_link(), "//dummy-root/courses/mitX/101/test/about") - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': False}): - self.assertEquals(self.get_about_page_link(), "//localhost:8000/courses/mitX/101/test/about") - - @override_settings(MKTG_URLS={'ROOT': 'http://www.dummy'}) - def about_page_marketing_site_remove_http_test(self): - """ Get URL for about page, marketing root present, remove http://. """ - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): - self.assertEquals(self.get_about_page_link(), "//www.dummy/courses/mitX/101/test/about") - - @override_settings(MKTG_URLS={'ROOT': 'https://www.dummy'}) - def about_page_marketing_site_remove_https_test(self): - """ Get URL for about page, marketing root present, remove https://. """ - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): - self.assertEquals(self.get_about_page_link(), "//www.dummy/courses/mitX/101/test/about") - - @override_settings(MKTG_URLS={'ROOT': 'www.dummyhttps://x'}) - def about_page_marketing_site_https__edge_test(self): - """ Get URL for about page, only remove https:// at the beginning of the string. """ - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): - self.assertEquals(self.get_about_page_link(), "//www.dummyhttps://x/courses/mitX/101/test/about") - - @override_settings(MKTG_URLS={}) - def about_page_marketing_urls_not_set_test(self): - """ Error case. ENABLE_MKTG_SITE is True, but there is either no MKTG_URLS, or no MKTG_URLS Root property. """ - with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): - self.assertEquals(self.get_about_page_link(), None) - - @override_settings(LMS_BASE=None) - def about_page_no_lms_base_test(self): - """ No LMS_BASE, nor is ENABLE_MKTG_SITE True """ - self.assertEquals(self.get_about_page_link(), None) - - def get_about_page_link(self): - """ create mock course and return the about page link """ - course_key = SlashSeparatedCourseKey('mitX', '101', 'test') - return utils.get_lms_link_for_about_page(course_key) - def lms_link_test(self): """ Tests get_lms_link_for_item. """ course_key = SlashSeparatedCourseKey('mitX', '101', 'test') diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 03a2b6aae2..f493447437 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -104,42 +104,6 @@ def get_lms_link_for_item(location, preview=False): ) -def get_lms_link_for_about_page(course_key): - """ - Returns the url to the course about page from the location tuple. - """ - - assert isinstance(course_key, CourseKey) - - if settings.FEATURES.get('ENABLE_MKTG_SITE', False): - if not hasattr(settings, 'MKTG_URLS'): - log.exception("ENABLE_MKTG_SITE is True, but MKTG_URLS is not defined.") - return None - - marketing_urls = settings.MKTG_URLS - - # Root will be "https://www.edx.org". The complete URL will still not be exactly correct, - # but redirects exist from www.edx.org to get to the Drupal course about page URL. - about_base = marketing_urls.get('ROOT', None) - - if about_base is None: - log.exception('There is no ROOT defined in MKTG_URLS') - return None - - # Strip off https:// (or http://) to be consistent with the formatting of LMS_BASE. - about_base = re.sub(r"^https?://", "", about_base) - - elif settings.LMS_BASE is not None: - about_base = settings.LMS_BASE - else: - return None - - return u"//{about_base_url}/courses/{course_key}/about".format( - about_base_url=about_base, - course_key=course_key.to_deprecated_string() - ) - - # pylint: disable=invalid-name def get_lms_link_for_certificate_web_view(user_id, course_key, mode): """ diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index e68984ed5f..7d71f180d3 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -28,7 +28,6 @@ from .component import ( from .item import create_xblock_info from .library import LIBRARIES_ENABLED from ccx_keys.locator import CCXLocator -from contentstore import utils from contentstore.course_group_config import ( COHORT_SCHEME, GroupConfiguration, @@ -77,6 +76,7 @@ from student.auth import has_course_author_access, has_studio_write_access, has_ from student.roles import ( CourseInstructorRole, CourseStaffRole, CourseCreatorRole, GlobalStaff, UserBasedRole ) +from util.course import get_lms_link_for_about_page from util.date_utils import get_default_time_display from util.json_request import JsonResponse, JsonResponseBadRequest, expect_json from util.milestones_helpers import ( @@ -996,7 +996,7 @@ def settings_handler(request, course_key_string): settings_context = { 'context_course': course_module, 'course_locator': course_key, - 'lms_link_for_about_page': utils.get_lms_link_for_about_page(course_key), + 'lms_link_for_about_page': get_lms_link_for_about_page(course_key), 'course_image_url': course_image_url(course_module, 'course_image'), 'banner_image_url': course_image_url(course_module, 'banner_image'), 'video_thumbnail_image_url': course_image_url(course_module, 'video_thumbnail_image'), diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 964f7ba97a..b2b536926c 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -138,6 +138,7 @@ EMAIL_PORT = ENV_TOKENS.get('EMAIL_PORT', EMAIL_PORT) EMAIL_USE_TLS = ENV_TOKENS.get('EMAIL_USE_TLS', EMAIL_USE_TLS) LMS_BASE = ENV_TOKENS.get('LMS_BASE') +LMS_ROOT_URL = ENV_TOKENS.get('LMS_ROOT_URL') # Note that FEATURES['PREVIEW_LMS_BASE'] gets read in from the environment file. SITE_NAME = ENV_TOKENS['SITE_NAME'] diff --git a/cms/envs/bok_choy.env.json b/cms/envs/bok_choy.env.json index 9c2fde40ee..5697c729e0 100644 --- a/cms/envs/bok_choy.env.json +++ b/cms/envs/bok_choy.env.json @@ -88,6 +88,7 @@ "STORAGE_TYPE": "localfs" }, "LMS_BASE": "localhost:8003", + "LMS_ROOT_URL": "http://localhost:8003", "LOCAL_LOGLEVEL": "INFO", "LOGGING_ENV": "sandbox", "LOG_DIR": "** OVERRIDDEN **", diff --git a/cms/envs/bok_choy.py b/cms/envs/bok_choy.py index 3b370254fa..76bb50ca8d 100644 --- a/cms/envs/bok_choy.py +++ b/cms/envs/bok_choy.py @@ -120,6 +120,8 @@ MOCK_SEARCH_BACKING_FILE = ( # this secret key should be the same as lms/envs/bok_choy.py's SECRET_KEY = "very_secret_bok_choy_key" +LMS_ROOT_URL = "http://localhost:8000" + ##################################################################### # Lastly, see if the developer has any local overrides. try: diff --git a/cms/envs/common.py b/cms/envs/common.py index 7408b55f7b..b621022346 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -296,6 +296,7 @@ AUTHENTICATION_BACKENDS = ( ) LMS_BASE = None +LMS_ROOT_URL = "http://localhost:8000" # These are standard regexes for pulling out info like course_ids, usage_ids, etc. # They are used so that URLs with deprecated-format strings still work. diff --git a/cms/envs/dev.py b/cms/envs/dev.py index c72d5c2d41..6d04a115a0 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -60,6 +60,7 @@ DATABASES = { } LMS_BASE = "localhost:8000" +LMS_ROOT_URL = "http://{}".format(LMS_BASE) FEATURES['PREVIEW_LMS_BASE'] = "localhost:8000" REPOS = { diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index 7c127dc639..a3bdd31537 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -31,6 +31,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' ################################# LMS INTEGRATION ############################# LMS_BASE = "localhost:8000" +LMS_ROOT_URL = "http://{}".format(LMS_BASE) FEATURES['PREVIEW_LMS_BASE'] = "preview." + LMS_BASE ########################### PIPELINE ################################# diff --git a/cms/envs/test.py b/cms/envs/test.py index 479739b964..5074c48d98 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -141,6 +141,7 @@ if os.environ.get('DISABLE_MIGRATIONS'): MIGRATION_MODULES = NoOpMigrationModules() LMS_BASE = "localhost:8000" +LMS_ROOT_URL = "http://{}".format(LMS_BASE) FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost" diff --git a/cms/startup.py b/cms/startup.py index 37baf3b88d..66de90bd8f 100644 --- a/cms/startup.py +++ b/cms/startup.py @@ -5,6 +5,7 @@ Module with code executed during Studio startup from django.conf import settings # Force settings to run so that the python path is modified + settings.INSTALLED_APPS # pylint: disable=pointless-statement from openedx.core.lib.django_startup import autostartup @@ -18,6 +19,7 @@ from openedx.core.lib.xblock_utils import xblock_local_resource_url import xmodule.x_module import cms.lib.xblock.runtime +from startup_configurations.validate_config import validate_cms_config from openedx.core.djangoapps.theming.core import enable_theming from openedx.core.djangoapps.theming.helpers import is_comprehensive_theming_enabled @@ -47,6 +49,9 @@ def run(): xmodule.x_module.descriptor_global_handler_url = cms.lib.xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = xblock_local_resource_url + # validate configurations on startup + validate_cms_config(settings) + def add_mimetypes(): """ diff --git a/common/djangoapps/startup_configurations/__init__.py b/common/djangoapps/startup_configurations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/startup_configurations/validate_config.py b/common/djangoapps/startup_configurations/validate_config.py new file mode 100644 index 0000000000..fe3aebd957 --- /dev/null +++ b/common/djangoapps/startup_configurations/validate_config.py @@ -0,0 +1,42 @@ +""" +Common Functions to Validate Configurations +""" + + +def validate_lms_config(settings): + """ + Validates configurations for lms and raise ValueError if not valid + """ + validate_common_config(settings) + + # validate feature based configurations + validate_marketing_site_config(settings) + + +def validate_cms_config(settings): + """ + Validates configurations for lms and raise ValueError if not valid + """ + validate_common_config(settings) + + # validate feature based configurations + validate_marketing_site_config(settings) + + +def validate_common_config(settings): + """ + Validates configurations common for all apps + """ + if not getattr(settings, 'LMS_ROOT_URL', None): + raise ValueError("'LMS_ROOT_URL' is not defined.") + + +def validate_marketing_site_config(settings): + """ + Validates 'marketing site' related configurations + """ + if settings.FEATURES.get('ENABLE_MKTG_SITE'): + if not hasattr(settings, 'MKTG_URLS'): + raise ValueError("'ENABLE_MKTG_SITE' is True, but 'MKTG_URLS' is not defined.") + if not settings.MKTG_URLS.get('ROOT'): + raise ValueError("There is no 'ROOT' defined in 'MKTG_URLS'.") diff --git a/common/djangoapps/util/course.py b/common/djangoapps/util/course.py new file mode 100644 index 0000000000..46720b326d --- /dev/null +++ b/common/djangoapps/util/course.py @@ -0,0 +1,28 @@ +""" +Utility methods related to course +""" +import logging +from django.conf import settings + +from opaque_keys.edx.keys import CourseKey + +log = logging.getLogger(__name__) + + +def get_lms_link_for_about_page(course_key): + """ + Returns the url to the course about page. + """ + assert isinstance(course_key, CourseKey) + + if settings.FEATURES.get('ENABLE_MKTG_SITE'): + # Root will be "https://www.edx.org". The complete URL will still not be exactly correct, + # but redirects exist from www.edx.org to get to the Drupal course about page URL. + about_base = settings.MKTG_URLS['ROOT'] + else: + about_base = settings.LMS_ROOT_URL + + return u"{about_base_url}/courses/{course_key}/about".format( + about_base_url=about_base, + course_key=course_key.to_deprecated_string() + ) diff --git a/common/djangoapps/util/tests/test_course.py b/common/djangoapps/util/tests/test_course.py new file mode 100644 index 0000000000..491119546b --- /dev/null +++ b/common/djangoapps/util/tests/test_course.py @@ -0,0 +1,36 @@ +""" +Tests for course utils. +""" + +from django.test import TestCase, override_settings +import mock +from opaque_keys.edx.locations import SlashSeparatedCourseKey +from util.course import get_lms_link_for_about_page + + +class LmsLinksTestCase(TestCase): + """ Tests for LMS links. """ + + def test_about_page(self): + """ Get URL for about page, no marketing site """ + with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': False}): + self.assertEquals(self.get_about_page_link(), "http://localhost:8000/courses/mitX/101/test/about") + + @override_settings(MKTG_URLS={'ROOT': 'https://dummy-root'}) + def test_about_page_marketing_site(self): + """ Get URL for about page, marketing root present. """ + with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): + self.assertEquals(self.get_about_page_link(), "https://dummy-root/courses/mitX/101/test/about") + with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': False}): + self.assertEquals(self.get_about_page_link(), "http://localhost:8000/courses/mitX/101/test/about") + + @override_settings(MKTG_URLS={'ROOT': 'https://www.dummyhttps://x'}) + def test_about_page_marketing_site_https__edge(self): + """ Get URL for about page """ + with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}): + self.assertEquals(self.get_about_page_link(), "https://www.dummyhttps://x/courses/mitX/101/test/about") + + def get_about_page_link(self): + """ create mock course and return the about page link.""" + course_key = SlashSeparatedCourseKey('mitX', '101', 'test') + return get_lms_link_for_about_page(course_key) diff --git a/lms/djangoapps/mobile_api/tests/test_milestones.py b/lms/djangoapps/mobile_api/tests/test_milestones.py index 49577d8484..ef587922c2 100644 --- a/lms/djangoapps/mobile_api/tests/test_milestones.py +++ b/lms/djangoapps/mobile_api/tests/test_milestones.py @@ -1,6 +1,7 @@ """ Milestone related tests for the mobile_api """ +from django.conf import settings from mock import patch from courseware.access_response import MilestoneError @@ -26,14 +27,22 @@ class MobileAPIMilestonesMixin(object): ALLOW_ACCESS_TO_MILESTONE_COURSE = False # pylint: disable=invalid-name - @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) + @patch.dict(settings.FEATURES, { + 'ENABLE_PREREQUISITE_COURSES': True, + 'MILESTONES_APP': True, + 'ENABLE_MKTG_SITE': True, + }) def test_unfulfilled_prerequisite_course(self): """ Tests the case for an unfulfilled pre-requisite course """ self._add_prerequisite_course() self.init_course_access() self._verify_unfulfilled_milestone_response() - @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) + @patch.dict(settings.FEATURES, { + 'ENABLE_PREREQUISITE_COURSES': True, + 'MILESTONES_APP': True, + 'ENABLE_MKTG_SITE': True, + }) def test_unfulfilled_prerequisite_course_for_staff(self): self._add_prerequisite_course() self.user.is_staff = True @@ -41,7 +50,11 @@ class MobileAPIMilestonesMixin(object): self.init_course_access() self.api_response() - @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True}) + @patch.dict(settings.FEATURES, { + 'ENABLE_PREREQUISITE_COURSES': True, + 'MILESTONES_APP': True, + 'ENABLE_MKTG_SITE': True, + }) def test_fulfilled_prerequisite_course(self): """ Tests the case when a user fulfills existing pre-requisite course @@ -52,7 +65,11 @@ class MobileAPIMilestonesMixin(object): self.init_course_access() self.api_response() - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True}) + @patch.dict(settings.FEATURES, { + 'ENTRANCE_EXAMS': True, + 'MILESTONES_APP': True, + 'ENABLE_MKTG_SITE': True, + }) def test_unpassed_entrance_exam(self): """ Tests the case where the user has not passed the entrance exam @@ -61,7 +78,11 @@ class MobileAPIMilestonesMixin(object): self.init_course_access() self._verify_unfulfilled_milestone_response() - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True}) + @patch.dict(settings.FEATURES, { + 'ENTRANCE_EXAMS': True, + 'MILESTONES_APP': True, + 'ENABLE_MKTG_SITE': True, + }) def test_unpassed_entrance_exam_for_staff(self): self._add_entrance_exam() self.user.is_staff = True @@ -69,7 +90,11 @@ class MobileAPIMilestonesMixin(object): self.init_course_access() self.api_response() - @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True}) + @patch.dict(settings.FEATURES, { + 'ENTRANCE_EXAMS': True, + 'MILESTONES_APP': True, + 'ENABLE_MKTG_SITE': True, + }) def test_passed_entrance_exam(self): """ Tests access when user has passed the entrance exam diff --git a/lms/djangoapps/mobile_api/testutils.py b/lms/djangoapps/mobile_api/testutils.py index 1f1c93ad5d..d72a5246a6 100644 --- a/lms/djangoapps/mobile_api/testutils.py +++ b/lms/djangoapps/mobile_api/testutils.py @@ -11,6 +11,7 @@ Test utilities for mobile API tests: """ # pylint: disable=no-member from datetime import timedelta +from django.conf import settings from django.utils import timezone import ddt @@ -153,6 +154,7 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin): """Base implementation of initializing the user for each test.""" self.login_and_enroll(course_id) + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_success(self): self.init_course_access() @@ -166,7 +168,7 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin): response = self.api_response(expected_response_code=None, course_id=non_existent_course_id) self.verify_failure(response) # allow subclasses to override verification - @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) + @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False, 'ENABLE_MKTG_SITE': True}) def test_unreleased_course(self): # ensure the course always starts in the future # pylint: disable=attribute-defined-outside-init @@ -184,6 +186,7 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin): (None, False) ) @ddt.unpack + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_non_mobile_available(self, role, should_succeed): self.init_course_access() # set mobile_available to False for the test course @@ -202,6 +205,7 @@ class MobileCourseAccessTestMixin(MobileAPIMilestonesMixin): (None, False) ) @ddt.unpack + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_visible_to_staff_only_course(self, role, should_succeed): self.init_course_access() self.course.visible_to_staff_only = True diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index 1295af6dea..e759f358e5 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -1,12 +1,14 @@ """ Serializer for user API """ +from opaque_keys.edx.keys import CourseKey from rest_framework import serializers from rest_framework.reverse import reverse from courseware.access import has_access from student.models import CourseEnrollment, User from certificates.api import certificate_downloadable_status +from util.course import get_lms_link_for_about_page class CourseOverviewField(serializers.RelatedField): @@ -50,11 +52,7 @@ class CourseOverviewField(serializers.RelatedField): } }, 'course_image': course_overview.course_image_url, - 'course_about': reverse( - 'about_course', - kwargs={'course_id': course_id}, - request=request, - ), + 'course_about': get_lms_link_for_about_page(CourseKey.from_string(course_id)), 'course_updates': reverse( 'course-updates-list', kwargs={'course_id': course_id}, diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 94654db65f..dd8f7e2cb6 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -11,7 +11,7 @@ import pytz from django.conf import settings from django.utils import timezone from django.template import defaultfilters -from django.test import RequestFactory +from django.test import RequestFactory, override_settings from milestones.tests.utils import MilestonesTestCaseMixin from xmodule.course_module import DEFAULT_START_DATE from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory @@ -73,6 +73,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin): @attr('shard_2') @ddt.ddt +@override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin, MilestonesTestCaseMixin): """ @@ -86,7 +87,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest LAST_WEEK = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=7) ADVERTISED_START = "Spring 2016" - @patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + @patch.dict(settings.FEATURES, {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self, *args, **kwargs): super(TestUserEnrollmentApi, self).setUp() @@ -117,6 +118,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest courses = response.data self.assertEqual(len(courses), 0) + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_sort_order(self): self.login() @@ -134,9 +136,12 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest unicode(courses[num_courses - course_index - 1].id) ) - @patch.dict( - settings.FEATURES, {'ENABLE_PREREQUISITE_COURSES': True, 'MILESTONES_APP': True, 'DISABLE_START_DATES': False} - ) + @patch.dict(settings.FEATURES, { + 'ENABLE_PREREQUISITE_COURSES': True, + 'MILESTONES_APP': True, + 'DISABLE_START_DATES': False, + 'ENABLE_MKTG_SITE': True, + }) def test_courseware_access(self): self.login() @@ -181,7 +186,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest (DEFAULT_START_DATE, None, None, "empty"), ) @ddt.unpack - @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) + @patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False, 'ENABLE_MKTG_SITE': True}) def test_start_type_and_display(self, start, advertised_start, expected_display, expected_type): """ Tests that the correct start_type and start_display are returned in the @@ -195,6 +200,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest self.assertEqual(response.data[0]['course']['start_type'], expected_type) self.assertEqual(response.data[0]['course']['start_display'], expected_display) + @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) def test_no_certificate(self): self.login_and_enroll() @@ -222,21 +228,21 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest certificate_data = response.data[0]['certificate'] self.assertEquals(certificate_data['url'], certificate_url) - @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': False}) + @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': False, 'ENABLE_MKTG_SITE': True}) def test_pdf_certificate_with_html_cert_disabled(self): """ Tests PDF certificates with CERTIFICATES_HTML_VIEW set to False. """ self.verify_pdf_certificate() - @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True}) + @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True, 'ENABLE_MKTG_SITE': True}) def test_pdf_certificate_with_html_cert_enabled(self): """ Tests PDF certificates with CERTIFICATES_HTML_VIEW set to True. """ self.verify_pdf_certificate() - @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True}) + @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True, 'ENABLE_MKTG_SITE': True}) def test_web_certificate(self): CourseMode.objects.create( course_id=self.course.id, @@ -261,7 +267,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest ) ) - @patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True}) + @patch.dict(settings.FEATURES, {"ENABLE_DISCUSSION_SERVICE": True, 'ENABLE_MKTG_SITE': True}) def test_discussion_url(self): self.login_and_enroll() @@ -430,6 +436,8 @@ class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, @attr('shard_2') +@patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}) +@override_settings(MKTG_URLS={'ROOT': 'dummy-root'}) class TestCourseEnrollmentSerializer(MobileAPITestCase, MilestonesTestCaseMixin): """ Test the course enrollment serializer diff --git a/lms/envs/aws.py b/lms/envs/aws.py index c1a9e8cc7e..3b03ca6c98 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -188,6 +188,8 @@ REGISTRATION_EMAIL_PATTERNS_ALLOWED = ENV_TOKENS.get('REGISTRATION_EMAIL_PATTERN EDXMKTG_LOGGED_IN_COOKIE_NAME = ENV_TOKENS.get('EDXMKTG_LOGGED_IN_COOKIE_NAME', EDXMKTG_LOGGED_IN_COOKIE_NAME) EDXMKTG_USER_INFO_COOKIE_NAME = ENV_TOKENS.get('EDXMKTG_USER_INFO_COOKIE_NAME', EDXMKTG_USER_INFO_COOKIE_NAME) +LMS_ROOT_URL = ENV_TOKENS.get('LMS_ROOT_URL') + ENV_FEATURES = ENV_TOKENS.get('FEATURES', {}) for feature, value in ENV_FEATURES.items(): FEATURES[feature] = value diff --git a/lms/envs/bok_choy.env.json b/lms/envs/bok_choy.env.json index fe65d3faa2..4bb4250b82 100644 --- a/lms/envs/bok_choy.env.json +++ b/lms/envs/bok_choy.env.json @@ -96,6 +96,7 @@ "JWT_SECRET_KEY": "super-secret-key" }, "LMS_BASE": "localhost:8003", + "LMS_ROOT_URL": "http://localhost:8003", "LOCAL_LOGLEVEL": "INFO", "LOGGING_ENV": "sandbox", "LOG_DIR": "** OVERRIDDEN **", diff --git a/lms/envs/bok_choy.py b/lms/envs/bok_choy.py index cb742f4524..37c0fe861f 100644 --- a/lms/envs/bok_choy.py +++ b/lms/envs/bok_choy.py @@ -193,6 +193,8 @@ BADGING_BACKEND = 'lms.djangoapps.badges.backends.tests.dummy_backend.DummyBacke ECOMMERCE_API_URL = 'http://localhost:8043/api/v2/' ECOMMERCE_API_SIGNING_KEY = 'ecommerce-key' +LMS_ROOT_URL = "http://localhost:8000" + ##################################################################### # Lastly, see if the developer has any local overrides. try: diff --git a/lms/envs/common.py b/lms/envs/common.py index a09e1b89e1..3d9bb1e0cf 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -60,6 +60,7 @@ DISCUSSION_SETTINGS = { 'MAX_COMMENT_DEPTH': 2, } +LMS_ROOT_URL = "http://localhost:8000" # Features FEATURES = { diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 32faf4a872..f3dc4e8532 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -27,6 +27,7 @@ FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True FEATURES['ENABLE_GRADE_DOWNLOADS'] = True FEATURES['ENABLE_PAYMENT_FAKE'] = True +LMS_ROOT_URL = "http://localhost:8000" FEEDBACK_SUBMISSION_EMAIL = "dummy@example.com" diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 40c931dbc7..7243c31302 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -19,6 +19,8 @@ PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME', 'Devstack') CELERY_ALWAYS_EAGER = True HTTPS = 'off' +LMS_ROOT_URL = 'http://localhost:8000' + ################################ LOGGERS ###################################### # Silence noisy logs diff --git a/lms/envs/test.py b/lms/envs/test.py index 48c175ac88..32f530cb4b 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -585,3 +585,5 @@ OAUTH2_PROVIDER_APPLICATION_MODEL = 'oauth2_provider.Application' COURSE_CATALOG_API_URL = 'https://catalog.example.com/api/v1' COMPREHENSIVE_THEME_DIRS = [REPO_ROOT / "themes", REPO_ROOT / "common/test"] + +LMS_ROOT_URL = "http://localhost:8000" diff --git a/lms/startup.py b/lms/startup.py index 914e7d9000..b6b8bab768 100644 --- a/lms/startup.py +++ b/lms/startup.py @@ -6,6 +6,7 @@ import django from django.conf import settings # Force settings to run so that the python path is modified + settings.INSTALLED_APPS # pylint: disable=pointless-statement from openedx.core.lib.django_startup import autostartup @@ -20,6 +21,7 @@ from monkey_patch import ( import xmodule.x_module import lms_xblock.runtime +from startup_configurations.validate_config import validate_lms_config from openedx.core.djangoapps.theming.core import enable_theming from openedx.core.djangoapps.theming.helpers import is_comprehensive_theming_enabled @@ -82,6 +84,9 @@ def run(): xmodule.x_module.descriptor_global_handler_url = lms_xblock.runtime.handler_url xmodule.x_module.descriptor_global_local_resource_url = lms_xblock.runtime.local_resource_url + # validate configurations on startup + validate_lms_config(settings) + def add_mimetypes(): """