diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a05b78e883..fc452f7acd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_task/ lms/djangoapps/mobile_api/ +openedx/core/djangoapps/commerce/ @openedx/2u-infinity openedx/core/djangoapps/credentials @openedx/2U-aperture openedx/core/djangoapps/credit @openedx/2U-aperture openedx/core/djangoapps/enrollments/ @openedx/2U-aperture @@ -22,6 +23,7 @@ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/oauth_dispatch openedx/core/djangoapps/user_api/ @openedx/2U-aperture openedx/core/djangoapps/user_authn/ @openedx/2U-vanguards +openedx/core/djangoapps/verified_track_content/ @openedx/2u-infinity openedx/features/course_experience/ xmodule/ @@ -36,16 +38,18 @@ common/djangoapps/track/ lms/djangoapps/certificates/ @openedx/2U-aperture # Discovery -common/djangoapps/course_modes/ +common/djangoapps/course_modes/ @openedx/2U-aperture common/djangoapps/enrollment/ -lms/djangoapps/branding/ @openedx/2U-aperture -lms/djangoapps/commerce/ -lms/djangoapps/experiments/ @openedx/2U-aperture -lms/djangoapps/learner_dashboard/ @openedx/2U-aperture -lms/djangoapps/learner_home/ @openedx/2U-aperture -openedx/features/content_type_gating/ +common/djangoapps/entitlements/ @openedx/2U-aperture +lms/djangoapps/branding/ @openedx/2U-aperture +lms/djangoapps/commerce/ @openedx/2u-infinity +lms/djangoapps/experiments/ @openedx/2u-infinity +lms/djangoapps/gating/ @openedx/2u-infinity +lms/djangoapps/learner_dashboard/ @openedx/2U-aperture +lms/djangoapps/learner_home/ @openedx/2U-aperture +openedx/features/content_type_gating/ @openedx/2u-infinity openedx/features/course_duration_limits/ -openedx/features/discounts/ +openedx/features/discounts/ @openedx/2u-infinity # Ping Axim On-call if someone uses the QuickStart # https://docs.openedx.org/en/latest/developers/quickstarts/first_openedx_pr.html diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index a3b0527aad..458e00fc6b 100644 --- a/.github/workflows/ci-static-analysis.yml +++ b/.github/workflows/ci-static-analysis.yml @@ -10,7 +10,7 @@ jobs: matrix: python-version: - "3.11" - os: ["ubuntu-latest"] + os: ["ubuntu-22.04"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml index f253d48e4f..624caddd53 100644 --- a/.github/workflows/migrations-check.yml +++ b/.github/workflows/migrations-check.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] python-version: - "3.11" # 'pinned' is used to install the latest patch version of Django @@ -126,7 +126,7 @@ jobs: if: always() needs: - check_migrations - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Decide whether the needed jobs succeeded or failed # uses: re-actors/alls-green@v1.2.1 diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 144cc77a3d..8860aced7f 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -8,25 +8,25 @@ on: jobs: run-pylint: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: include: - module-name: lms-1 - path: "--django-settings-module=lms.envs.test lms/djangoapps/badges/ lms/djangoapps/branding/ lms/djangoapps/bulk_email/ lms/djangoapps/bulk_enroll/ lms/djangoapps/bulk_user_retirement/ lms/djangoapps/ccx/ lms/djangoapps/certificates/ lms/djangoapps/commerce/ lms/djangoapps/course_api/ lms/djangoapps/course_blocks/ lms/djangoapps/course_home_api/ lms/djangoapps/course_wiki/ lms/djangoapps/coursewarehistoryextended/ lms/djangoapps/debug/ lms/djangoapps/courseware/ lms/djangoapps/course_goals/ lms/djangoapps/rss_proxy/" + path: "lms/djangoapps/badges/ lms/djangoapps/branding/ lms/djangoapps/bulk_email/ lms/djangoapps/bulk_enroll/ lms/djangoapps/bulk_user_retirement/ lms/djangoapps/ccx/ lms/djangoapps/certificates/ lms/djangoapps/commerce/ lms/djangoapps/course_api/ lms/djangoapps/course_blocks/ lms/djangoapps/course_home_api/ lms/djangoapps/course_wiki/ lms/djangoapps/coursewarehistoryextended/ lms/djangoapps/debug/ lms/djangoapps/courseware/ lms/djangoapps/course_goals/ lms/djangoapps/rss_proxy/" - module-name: lms-2 - path: "--django-settings-module=lms.envs.test lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/learner_home/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/djangoapps/mfe_config_api/ lms/envs/ lms/lib/ lms/tests.py" + path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/learner_home/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/djangoapps/mfe_config_api/ lms/envs/ lms/lib/ lms/tests.py" - module-name: openedx-1 - path: "--django-settings-module=lms.envs.test openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/content_staging/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/" + path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/content_staging/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/" - module-name: openedx-2 - path: "--django-settings-module=lms.envs.test openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/notifications/ openedx/core/djangoapps/staticfiles/ openedx/core/djangoapps/content_tagging/" + path: "openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/notifications/ openedx/core/djangoapps/staticfiles/ openedx/core/djangoapps/content_tagging/" - module-name: common - path: "--django-settings-module=lms.envs.test common pavelib" + path: "common pavelib" - module-name: cms - path: "--django-settings-module=cms.envs.test cms" + path: "cms" - module-name: xmodule - path: "--django-settings-module=lms.envs.test xmodule" + path: "xmodule" name: pylint ${{ matrix.module-name }} steps: @@ -75,7 +75,7 @@ jobs: if: always() needs: - run-pylint - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Decide whether the needed jobs succeeded or failed # uses: re-actors/alls-green@v1.2.1 diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 5445d70e3b..8461012349 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] python-version: - "3.11" node-version: [20] diff --git a/.github/workflows/static-assets-check.yml b/.github/workflows/static-assets-check.yml index 0a417f9b1c..4fe66e2a77 100644 --- a/.github/workflows/static-assets-check.yml +++ b/.github/workflows/static-assets-check.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] python-version: - "3.11" node-version: [18, 20] diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5fef1c8352..854677b93c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,7 +15,7 @@ concurrency: jobs: run-tests: name: ${{ matrix.shard_name }}(py=${{ matrix.python-version }},dj=${{ matrix.django-version }},mongo=${{ matrix.mongo-version }}) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: python-version: @@ -164,7 +164,7 @@ jobs: overwrite: true collect-and-verify: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Setup Python @@ -229,7 +229,7 @@ jobs: # https://github.com/orgs/community/discussions/33579 success: name: Unit tests successful - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 if: always() needs: [run-tests] steps: @@ -240,7 +240,7 @@ jobs: jobs: ${{ toJSON(needs) }} compile-warnings-report: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [run-tests] steps: - uses: actions/checkout@v4 @@ -268,7 +268,7 @@ jobs: overwrite: true merge-artifacts: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [compile-warnings-report] steps: - name: Merge Pytest Warnings JSON Artifacts @@ -288,7 +288,7 @@ jobs: # Combine and upload coverage reports. coverage: if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false)) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [run-tests] strategy: matrix: diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py index f84290ba83..ae0e6ea467 100644 --- a/cms/djangoapps/contentstore/config/waffle.py +++ b/cms/djangoapps/contentstore/config/waffle.py @@ -4,7 +4,7 @@ waffle switches for the contentstore app. """ -from edx_toggles.toggles import WaffleFlag, WaffleSwitch +from edx_toggles.toggles import WaffleSwitch from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag @@ -26,20 +26,6 @@ SHOW_REVIEW_RULES_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=togg f'{WAFFLE_NAMESPACE}.show_review_rules', __name__, LOG_PREFIX ) -# Waffle flag to redirect to the library authoring MFE. -# .. toggle_name: contentstore.library_authoring_mfe -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Toggles the new micro-frontend-based implementation of the library authoring experience. -# .. toggle_use_cases: temporary, open_edx -# .. toggle_creation_date: 2020-08-03 -# .. toggle_target_removal_date: 2020-12-31 -# .. toggle_warning: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and ENABLE_LIBRARY_AUTHORING_MICROFRONTEND. -# .. toggle_tickets: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/4106944527/Libraries+Relaunch+Proposal+For+Product+Review -REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = WaffleFlag( - f'{WAFFLE_NAMESPACE}.library_authoring_mfe', __name__, LOG_PREFIX -) - # .. toggle_name: studio.custom_relative_dates # .. toggle_implementation: CourseWaffleFlag diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/home.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/home.py index 0aa06d8b8d..4c3e2a4321 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/home.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/home.py @@ -59,10 +59,8 @@ class CourseHomeSerializer(serializers.Serializer): libraries = LibraryViewSerializer(many=True, required=False, allow_null=True) libraries_enabled = serializers.BooleanField() taxonomies_enabled = serializers.BooleanField() - library_authoring_mfe_url = serializers.CharField() taxonomy_list_mfe_url = serializers.CharField() optimization_enabled = serializers.BooleanField() - redirect_to_library_authoring_mfe = serializers.BooleanField() request_course_creator_url = serializers.CharField() rerun_creator_status = serializers.BooleanField() show_new_library_button = serializers.BooleanField() diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/home.py b/cms/djangoapps/contentstore/rest_api/v1/views/home.py index d72042cff6..ff476090ee 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/home.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/home.py @@ -59,9 +59,7 @@ class HomePageView(APIView): "in_process_course_actions": [], "libraries": [], "libraries_enabled": true, - "library_authoring_mfe_url": "//localhost:3001/course/course-v1:edX+P315+2T2023", "optimization_enabled": true, - "redirect_to_library_authoring_mfe": false, "request_course_creator_url": "/request_course_creator", "rerun_creator_status": true, "show_new_library_button": true, diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py index a8b4cf5e39..c3c9652e5d 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py @@ -52,10 +52,8 @@ class HomePageViewTest(CourseTestCase): "libraries": [], "libraries_enabled": True, "taxonomies_enabled": True, - "library_authoring_mfe_url": settings.LIBRARY_AUTHORING_MICROFRONTEND_URL, "taxonomy_list_mfe_url": 'http://course-authoring-mfe/taxonomies', "optimization_enabled": False, - "redirect_to_library_authoring_mfe": False, "request_course_creator_url": "/request_course_creator", "rerun_creator_status": True, "show_new_library_button": True, diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 214193918e..035e44d5ce 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -98,7 +98,7 @@ from cms.djangoapps.contentstore.toggles import ( ) from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.djangoapps.models.settings.course_metadata import CourseMetadata -from xmodule.library_tools import LibraryToolsService +from xmodule.library_tools import LegacyLibraryToolsService from xmodule.course_block import DEFAULT_START_DATE # lint-amnesty, pylint: disable=wrong-import-order from xmodule.data import CertificatesDisplayBehaviors from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order @@ -1265,7 +1265,7 @@ def load_services_for_studio(runtime, user): "settings": SettingsService(), "lti-configuration": ConfigurationService(CourseAllowPIISharingInLTIFlag), "teams_configuration": TeamsConfigurationService(), - "library_tools": LibraryToolsService(modulestore(), user.id) + "library_tools": LegacyLibraryToolsService(modulestore(), user.id) } runtime._services.update(services) # lint-amnesty, pylint: disable=protected-access @@ -1671,9 +1671,7 @@ def get_home_context(request, no_course=False): ENABLE_GLOBAL_STAFF_OPTIMIZATION, ) from cms.djangoapps.contentstore.views.library import ( - LIBRARY_AUTHORING_MICROFRONTEND_URL, LIBRARIES_ENABLED, - should_redirect_to_library_authoring_mfe, user_can_view_create_library_button, ) @@ -1699,12 +1697,9 @@ def get_home_context(request, no_course=False): 'in_process_course_actions': in_process_course_actions, 'libraries_enabled': LIBRARIES_ENABLED, 'taxonomies_enabled': not is_tagging_feature_disabled(), - 'redirect_to_library_authoring_mfe': should_redirect_to_library_authoring_mfe(), - 'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL, 'taxonomy_list_mfe_url': get_taxonomy_list_url(), 'libraries': libraries, - 'show_new_library_button': user_can_view_create_library_button(user) - and not should_redirect_to_library_authoring_mfe(), + 'show_new_library_button': user_can_view_create_library_button(user), 'user': user, 'request_course_creator_url': reverse('request_course_creator'), 'course_creator_status': _get_course_creator_status(user), @@ -2202,7 +2197,7 @@ class StudioPermissionsService: Deprecated. To be replaced by a more general authorization service. - Only used by LibraryContentBlock (and library_tools.py). + Only used by LegacyLibraryContentBlock (and library_tools.py). """ def __init__(self, user): diff --git a/cms/djangoapps/contentstore/views/library.py b/cms/djangoapps/contentstore/views/library.py index 8c314caa66..340cadb4e2 100644 --- a/cms/djangoapps/contentstore/views/library.py +++ b/cms/djangoapps/contentstore/views/library.py @@ -41,7 +41,6 @@ from common.djangoapps.student.roles import ( ) from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest, expect_json -from ..config.waffle import REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND from ..utils import add_instructor, reverse_library_url from .component import CONTAINER_TEMPLATES, get_component_templates from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import create_xblock_info @@ -52,21 +51,6 @@ __all__ = ['library_handler', 'manage_library_users'] log = logging.getLogger(__name__) LIBRARIES_ENABLED = settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES', False) -ENABLE_LIBRARY_AUTHORING_MICROFRONTEND = settings.FEATURES.get('ENABLE_LIBRARY_AUTHORING_MICROFRONTEND', False) -LIBRARY_AUTHORING_MICROFRONTEND_URL = settings.LIBRARY_AUTHORING_MICROFRONTEND_URL - - -def should_redirect_to_library_authoring_mfe(): - """ - Boolean helper method, returns whether or not to redirect to the Library - Authoring MFE based on settings and flags. - """ - - return ( - ENABLE_LIBRARY_AUTHORING_MICROFRONTEND and - LIBRARY_AUTHORING_MICROFRONTEND_URL and - REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND.is_enabled() - ) def _user_can_create_library_for_org(user, org=None): diff --git a/cms/djangoapps/contentstore/views/tests/test_block.py b/cms/djangoapps/contentstore/views/tests/test_block.py index 80a2535598..f3e20b45b2 100644 --- a/cms/djangoapps/contentstore/views/tests/test_block.py +++ b/cms/djangoapps/contentstore/views/tests/test_block.py @@ -982,7 +982,7 @@ class TestDuplicateItem(ItemTest, DuplicateHelper, OpenEdxEventsTestMixin): def test_duplicate_library_content_block(self): # pylint: disable=too-many-statements """ - Test the LibraryContentBlock's special duplication process. + Test the LegacyLibraryContentBlock's special duplication process. """ store = modulestore() diff --git a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py index 76dba57f1d..7979a422a3 100644 --- a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py +++ b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py @@ -7,13 +7,11 @@ import ddt from opaque_keys.edx.keys import UsageKey from rest_framework.test import APIClient from openedx_tagging.core.tagging.models import Tag -from organizations.models import Organization from xmodule.modulestore.django import contentstore, modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, upload_file_to_course -from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory +from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory, LibraryFactory from cms.djangoapps.contentstore.utils import reverse_usage_url -from openedx.core.djangoapps.content_libraries import api as library_api from openedx.core.djangoapps.content_tagging import api as tagging_api CLIPBOARD_ENDPOINT = "/api/content-staging/v1/clipboard/" @@ -165,12 +163,12 @@ class ClipboardPasteTestCase(ModuleStoreTestCase): publish_item=True, ).location - library = ClipboardLibraryContentPasteTestCase.setup_library() + library = ClipboardPasteFromV1LibraryTestCase.setup_library() with self.store.bulk_operations(course_key): library_content_block_key = BlockFactory.create( parent=self.store.get_item(unit_key), category="library_content", - source_library_id=str(library.key), + source_library_id=str(library.context_key), display_name="LC Block", publish_item=True, ).location @@ -393,27 +391,27 @@ class ClipboardPasteTestCase(ModuleStoreTestCase): assert source_pic2_hash != dest_pic2_hash # Because there was a conflict, this file was unchanged. -class ClipboardLibraryContentPasteTestCase(ModuleStoreTestCase): +class ClipboardPasteFromV1LibraryTestCase(ModuleStoreTestCase): """ - Test Clipboard Paste functionality with library content + Test Clipboard Paste functionality with legacy (v1) library content """ def setUp(self): """ - Set up a v2 Content Library and a library content block + Set up a v1 Content Library and a library content block """ super().setUp() self.client = APIClient() self.client.login(username=self.user.username, password=self.user_password) self.store = modulestore() - library = self.setup_library() + self.library = self.setup_library() # Create a library content block (lc), point it out our library, and sync it. self.course = CourseFactory.create(display_name='Course') self.orig_lc_block = BlockFactory.create( parent=self.course, category="library_content", - source_library_id=str(library.key), + source_library_id=str(self.library.context_key), display_name="LC Block", publish_item=False, ) @@ -426,18 +424,15 @@ class ClipboardLibraryContentPasteTestCase(ModuleStoreTestCase): @classmethod def setup_library(cls): """ - Creates and returns a content library. + Creates and returns a legacy content library with 1 problem """ - library = library_api.create_library( - library_type=library_api.COMPLEX, - org=Organization.objects.create(name="Test Org", short_name="CL-TEST"), - slug="lib", - title="Library", - ) - # Populate it with a problem: - problem_key = library_api.create_library_block(library.key, "problem", "p1").usage_key - library_api.set_library_block_olx(problem_key, """ - + library = LibraryFactory.create(display_name='Library') + lib_block = BlockFactory.create( + parent_location=library.usage_key, + category="problem", + display_name="MCQ", + max_attempts=1, + data=""" @@ -445,9 +440,9 @@ class ClipboardLibraryContentPasteTestCase(ModuleStoreTestCase): Right - - """) - library_api.publish_changes(library.key) + """, + publish_item=False, + ) return library def test_paste_library_content_block(self): diff --git a/cms/envs/common.py b/cms/envs/common.py index 7521cc21fa..63221ee0b0 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -434,18 +434,6 @@ FEATURES = { # .. toggle_tickets: https://openedx.atlassian.net/browse/DEPR-58 'DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO': True, - # .. toggle_name: FEATURES['ENABLE_LIBRARY_AUTHORING_MICROFRONTEND'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable the Library Authoring MFE - # .. toggle_use_cases: temporary - # .. toggle_creation_date: 2020-06-20 - # .. toggle_target_removal_date: 2020-12-31 - # .. toggle_tickets: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/4106944527/Libraries+Relaunch+Proposal+For+Product+Review - # .. toggle_warning: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and see - # REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND for rollout. - 'ENABLE_LIBRARY_AUTHORING_MICROFRONTEND': False, - # .. toggle_name: FEATURES['DISABLE_COURSE_CREATION'] # .. toggle_implementation: DjangoSetting # .. toggle_default: False @@ -601,7 +589,6 @@ IDA_LOGOUT_URI_LIST = [] COURSE_AUTHORING_MICROFRONTEND_URL = None DISCUSSIONS_MICROFRONTEND_URL = None DISCUSSIONS_MFE_FEEDBACK_URL = None -LIBRARY_AUTHORING_MICROFRONTEND_URL = None # .. toggle_name: ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY # .. toggle_implementation: DjangoSetting # .. toggle_default: False @@ -2779,6 +2766,7 @@ WIKI_HELP_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-runni CUSTOM_PAGES_HELP_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html#adding-custom-pages" COURSE_LIVE_HELP_URL = "https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/course_assets/course_live.html" ORA_SETTINGS_HELP_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html#configuring-course-level-open-response-assessment-settings" +# pylint: enable=line-too-long # keys for big blue button live provider COURSE_LIVE_GLOBAL_CREDENTIALS = {} @@ -2810,6 +2798,7 @@ DISCUSSIONS_INCONTEXT_FEEDBACK_URL = '' # Learn More link in upgraded discussion notification alert # pylint: disable=line-too-long DISCUSSIONS_INCONTEXT_LEARNMORE_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/manage_discussions/discussions.html" +# pylint: enable=line-too-long #### django-simple-history## # disable indexing on date field its coming django-simple-history. diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index 1d3a510cdc..1200a61b06 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -174,9 +174,6 @@ FEATURES['ENABLE_ORGANIZATION_STAFF_ACCESS_FOR_CONTENT_LIBRARIES'] = True ################### FRONTEND APPLICATION PUBLISHER URL ################### FEATURES['FRONTEND_APP_PUBLISHER_URL'] = 'http://localhost:18400' -################### FRONTEND APPLICATION LIBRARY AUTHORING ################### -LIBRARY_AUTHORING_MICROFRONTEND_URL = 'http://localhost:3001' - ################### FRONTEND APPLICATION COURSE AUTHORING ################### COURSE_AUTHORING_MICROFRONTEND_URL = 'http://localhost:2001' diff --git a/cms/envs/test.py b/cms/envs/test.py index 38b7c78171..49db506088 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -333,3 +333,13 @@ COURSE_LIVE_GLOBAL_CREDENTIALS["BIG_BLUE_BUTTON"] = { "SECRET": "***", "URL": "***", } + +############## openedx-learning (Learning Core) config ############## +OPENEDX_LEARNING = { + 'MEDIA': { + 'BACKEND': 'django.core.files.storage.InMemoryStorage', + 'OPTIONS': { + 'location': MEDIA_ROOT + "_private" + } + } +} diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index f44fdcfc80..61f524123b 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -76,14 +76,18 @@ from django.urls import reverse ${_("This course run is using an upgraded version of edx discussion forum. In order to display the discussions sidebar, discussions xBlocks will no longer be visible to learners.")}
+ %if settings.DISCUSSIONS_INCONTEXT_LEARNMORE_URL: ${_(" Learn more")} + %endif + %if settings.DISCUSSIONS_INCONTEXT_FEEDBACK_URL: ${_("Share feedback")} + %endif
diff --git a/cms/templates/index.html b/cms/templates/index.html index 766d68da78..e558597307 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -348,9 +348,6 @@ from openedx.core.djangolib.js_utils import ( % endif % if libraries_enabled: - % if redirect_to_library_authoring_mfe: -
  • ${_("Libraries")}
  • - %else:
  • % if split_studio_home: ${_("Libraries")} @@ -358,7 +355,6 @@ from openedx.core.djangolib.js_utils import ( ${_("Libraries")} % endif
  • - % endif % endif % if taxonomies_enabled:
  • ${_("Taxonomies")}
  • diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index f49d1bc23b..d7b0698653 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -149,50 +149,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest self.assertRedirects(response, '/test_basket/add/?sku=TEST', fetch_redirect_response=False) ecomm_test_utils.update_commerce_config(enabled=False) - def test_verified_mode_response_contains_course_run_key(self): - # Create only the verified mode and enroll the user - CourseModeFactory.create( - mode_slug='verified', - course_id=self.course_that_started.id, - min_price=149, - sku="dummy" - ) - CourseEnrollmentFactory( - is_active=True, - course_id=self.course_that_started.id, - user=self.user - ) - - # Value Prop TODO (REV-2378): remove waffle flag from tests once the new Track Selection template is rolled out. - with override_waffle_flag(VALUE_PROP_TRACK_SELECTION_FLAG, active=True): - with patch(GATING_METHOD_NAME, return_value=True): - with patch(CDL_METHOD_NAME, return_value=True): - with patch("common.djangoapps.course_modes.views.EcommerceService.is_enabled", return_value=True): - url = reverse('course_modes_choose', args=[str(self.course_that_started.id)]) - response = self.client.get(url) - self.assertContains(response, "&course_run_key=") - self.assertContains(response, self.course_that_started.id) - - def test_response_without_verified_sku_does_not_contain_course_run_key(self): - CourseModeFactory.create( - mode_slug='verified', - course_id=self.course_that_started.id, - ) - CourseEnrollmentFactory( - is_active=True, - course_id=self.course_that_started.id, - user=self.user - ) - - # Value Prop TODO (REV-2378): remove waffle flag from tests once the new Track Selection template is rolled out. - with override_waffle_flag(VALUE_PROP_TRACK_SELECTION_FLAG, active=True): - with patch(GATING_METHOD_NAME, return_value=True): - with patch(CDL_METHOD_NAME, return_value=True): - with patch("common.djangoapps.course_modes.views.EcommerceService.is_enabled", return_value=True): - url = reverse('course_modes_choose', args=[str(self.course_that_started.id)]) - response = self.client.get(url) - self.assertNotContains(response, "&course_run_key=") - @httpretty.activate @ddt.data( '', diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 821c59dc05..759073a135 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -241,7 +241,7 @@ class ChooseModeView(View): if verified_mode.sku: context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(request.user) - context["ecommerce_payment_page"] = ecommerce_service.get_add_to_basket_url() + context["ecommerce_payment_page"] = ecommerce_service.payment_page_url() context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku diff --git a/cms/templates/content_libraries/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html similarity index 99% rename from cms/templates/content_libraries/xblock_iframe.html rename to common/templates/xblock_v2/xblock_iframe.html index b6e455f785..8b733373bd 100644 --- a/cms/templates/content_libraries/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -156,7 +156,7 @@ - + {{ fragment.body_html | safe }} diff --git a/docs/docs_settings.py b/docs/docs_settings.py index 5bc9b15946..f12848876e 100644 --- a/docs/docs_settings.py +++ b/docs/docs_settings.py @@ -14,7 +14,6 @@ from cms.envs.common import ( # lint-amnesty, pylint: disable=unused-import ADVANCED_PROBLEM_TYPES, COURSE_IMPORT_EXPORT_STORAGE, GIT_EXPORT_DEFAULT_IDENT, - LIBRARY_AUTHORING_MICROFRONTEND_URL, SCRAPE_YOUTUBE_THUMBNAILS_JOB_QUEUE, VIDEO_TRANSCRIPT_MIGRATIONS_JOB_QUEUE, UPDATE_SEARCH_INDEX_JOB_QUEUE, diff --git a/lms/djangoapps/branding/tests/test_page.py b/lms/djangoapps/branding/tests/test_page.py index 9904f79f64..46f10bd692 100644 --- a/lms/djangoapps/branding/tests/test_page.py +++ b/lms/djangoapps/branding/tests/test_page.py @@ -22,6 +22,7 @@ from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.course_block import CATALOG_VISIBILITY_ABOUT, CATALOG_VISIBILITY_NONE FEATURES_WITH_STARTDATE = settings.FEATURES.copy() FEATURES_WITH_STARTDATE['DISABLE_START_DATES'] = False @@ -201,6 +202,20 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase): display_name='Tech Beta Course', emit_signals=True, ) + self.course_with_none_visibility = CourseFactory.create( + org='MITx', + number='1003', + catalog_visibility=CATALOG_VISIBILITY_NONE, + display_name='Course with "none" catalog visibility', + emit_signals=True, + ) + self.course_with_about_visibility = CourseFactory.create( + org='MITx', + number='1003', + catalog_visibility=CATALOG_VISIBILITY_ABOUT, + display_name='Course with "about" catalog visibility', + emit_signals=True, + ) self.factory = RequestFactory() @patch('common.djangoapps.student.views.management.render_to_response', RENDER_MOCK) @@ -300,6 +315,15 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase): assert context['courses'][1].id == self.starting_earlier.id assert context['courses'][2].id == self.course_with_default_start_date.id + @patch('lms.djangoapps.courseware.views.views.render_to_response', RENDER_MOCK) + def test_invisible_courses_are_not_displayed(self): + response = self.client.get(reverse('courses')) + ((_template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence + + rendered_ids = [course.id for course in context["courses"]] + assert self.course_with_none_visibility.id not in rendered_ids + assert self.course_with_about_visibility.id not in rendered_ids + class IndexPageProgramsTests(SiteMixin, ModuleStoreTestCase): """ diff --git a/lms/djangoapps/commerce/tests/test_utils.py b/lms/djangoapps/commerce/tests/test_utils.py index 6c793433df..d5d0cf1f1c 100644 --- a/lms/djangoapps/commerce/tests/test_utils.py +++ b/lms/djangoapps/commerce/tests/test_utils.py @@ -11,7 +11,6 @@ from django.conf import settings from django.test import TestCase from django.test.client import RequestFactory from django.test.utils import override_settings -from edx_toggles.toggles.testutils import override_waffle_flag from opaque_keys.edx.locator import CourseLocator from waffle.testutils import override_switch @@ -21,7 +20,6 @@ from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import TEST_PASSWORD, UserFactory from lms.djangoapps.commerce.models import CommerceConfiguration from lms.djangoapps.commerce.utils import EcommerceService, refund_entitlement, refund_seat -from lms.djangoapps.commerce.waffle import ENABLE_TRANSITION_TO_COORDINATOR_CHECKOUT from openedx.core.djangolib.testing.utils import skip_unless_lms from openedx.core.lib.log_utils import audit_log from xmodule.modulestore.tests.django_utils import \ @@ -186,27 +184,6 @@ class EcommerceServiceTests(TestCase): assert url == expected_url - @override_settings(COMMERCE_COORDINATOR_URL_ROOT='http://coordinator_url') - @override_settings(ECOMMERCE_PUBLIC_URL_ROOT='http://ecommerce_url') - @ddt.data( - {'coordinator_flag_active': True}, - {'coordinator_flag_active': False} - ) - @ddt.unpack - def test_get_add_to_basket_url(self, coordinator_flag_active): - with override_waffle_flag(ENABLE_TRANSITION_TO_COORDINATOR_CHECKOUT, active=coordinator_flag_active): - - ecommerce_service = EcommerceService() - result = ecommerce_service.get_add_to_basket_url() - - if coordinator_flag_active: - expected_url = 'http://coordinator_url/lms/payment_page_redirect/' - else: - expected_url = 'http://ecommerce_url/test_basket/add/' - - self.assertIsNotNone(result) - self.assertEqual(expected_url, result) - @ddt.ddt @skip_unless_lms diff --git a/lms/djangoapps/commerce/utils.py b/lms/djangoapps/commerce/utils.py index 617852b4f6..9abcabd4a9 100644 --- a/lms/djangoapps/commerce/utils.py +++ b/lms/djangoapps/commerce/utils.py @@ -13,7 +13,6 @@ from django.urls import reverse from django.utils.translation import gettext as _ from common.djangoapps.course_modes.models import CourseMode -from common.djangoapps.student.models import CourseEnrollmentAttribute from openedx.core.djangoapps.commerce.utils import ( get_ecommerce_api_base_url, get_ecommerce_api_client, @@ -23,10 +22,6 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from openedx.core.djangoapps.theming import helpers as theming_helpers from .models import CommerceConfiguration -from .waffle import ( # lint-amnesty, pylint: disable=invalid-django-waffle-import - should_redirect_to_commerce_coordinator_checkout, - should_redirect_to_commerce_coordinator_refunds, -) from edx_django_utils.plugins import pluggable_override log = logging.getLogger(__name__) @@ -110,15 +105,6 @@ class EcommerceService: """ return self.get_absolute_ecommerce_url(self.config.basket_checkout_page) - def get_add_to_basket_url(self): - """ Return the URL for the payment page based on the waffle switch. - Example: - http://localhost/enabled_service_api_path - """ - if should_redirect_to_commerce_coordinator_checkout(): - return urljoin(settings.COMMERCE_COORDINATOR_URL_ROOT, settings.COORDINATOR_CHECKOUT_REDIRECT_PATH) - return self.payment_page_url() - @pluggable_override('OVERRIDE_GET_CHECKOUT_PAGE_URL') def get_checkout_page_url(self, *skus, **kwargs): """ Construct the URL to the ecommerce checkout page and include products. @@ -261,10 +247,6 @@ def refund_seat(course_enrollment, change_mode=False): course_key_str = str(course_enrollment.course_id) enrollee = course_enrollment.user - if should_redirect_to_commerce_coordinator_refunds(): - if _refund_in_commerce_coordinator(course_enrollment, change_mode): - return - service_user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME) api_client = get_ecommerce_api_client(service_user) @@ -295,75 +277,6 @@ def refund_seat(course_enrollment, change_mode=False): return refund_ids -def _refund_in_commerce_coordinator(course_enrollment, change_mode): - """ - Helper function to perform refund in Commerce Coordinator. - - Parameters: - course_enrollment (CourseEnrollment): the enrollment to refund. - change_mode (bool): whether the enrollment should be auto-enrolled into - the default course mode after refund. - - Returns: - bool: True if refund was performed. False if refund is not applicable - to Commerce Coordinator. - """ - enrollment_source_system = course_enrollment.get_order_attribute_value("source_system") - course_key_str = str(course_enrollment.course_id) - - # Commerce Coordinator enrollments will have an orders.source_system enrollment attribute. - # Redirect to Coordinator only if the source_system is safelisted as Coordinator's in settings. - - if enrollment_source_system and enrollment_source_system in settings.COMMERCE_COORDINATOR_REFUND_SOURCE_SYSTEMS: - log.info('Redirecting refund to Commerce Coordinator for user [%s], course [%s]...', - course_enrollment.user_id, course_key_str) - - # Re-use Ecommerce API client factory to build an API client for Commerce Coordinator... - service_user = get_user_model().objects.get( - username=settings.COMMERCE_COORDINATOR_SERVICE_WORKER_USERNAME - ) - api_client = get_ecommerce_api_client(service_user) - refunds_url = urljoin( - settings.COMMERCE_COORDINATOR_URL_ROOT, - settings.COMMERCE_COORDINATOR_REFUND_PATH - ) - - # Build request, raising exception if Coordinator returns non-200. - enrollment_attributes = CourseEnrollmentAttribute.get_enrollment_attributes(course_enrollment) - - try: - api_client.post( - refunds_url, - json={ - 'course_id': course_key_str, - 'username': course_enrollment.username, - 'enrollment_attributes': enrollment_attributes - } - ).raise_for_status() - - except Exception as exc: # pylint: disable=broad-except - # Catch any possible exceptions from the Commerce Coordinator service to ensure we fail gracefully - log.exception( - "Unexpected exception while attempting to refund user in Coordinator [%s], " - "course key [%s] message: [%s]", - course_enrollment.username, - course_key_str, - str(exc) - ) - - # Refund was successfully sent to Commerce Coordinator - log.info('Refund successfully sent to Commerce Coordinator for user [%s], course [%s].', - course_enrollment.user_id, course_key_str) - if change_mode: - auto_enroll(course_enrollment) - return True - else: - # Refund was not meant to be sent to Commerce Coordinator - log.info('Continuing refund without Commerce Coordinator redirect for user [%s], course [%s]...', - course_enrollment.user_id, course_key_str) - return False - - def auto_enroll(course_enrollment): """ Helper method to update an enrollment to a default course mode. diff --git a/lms/djangoapps/commerce/waffle.py b/lms/djangoapps/commerce/waffle.py deleted file mode 100644 index a36586a52d..0000000000 --- a/lms/djangoapps/commerce/waffle.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Configuration for features of Commerce App -""" -from edx_toggles.toggles import WaffleFlag - -# Namespace for Commerce waffle flags. -WAFFLE_FLAG_NAMESPACE = "commerce" - -# .. toggle_name: commerce.transition_to_coordinator.checkout -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Allows to redirect checkout to Commerce Coordinator API -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2023-11-22 -# .. toggle_target_removal_date: TBA -# .. toggle_tickets: SONIC-99 -# .. toggle_status: supported -ENABLE_TRANSITION_TO_COORDINATOR_CHECKOUT = WaffleFlag( - f"{WAFFLE_FLAG_NAMESPACE}.transition_to_coordinator.checkout", - __name__, -) - -# .. toggle_name: commerce.transition_to_coordinator.refund -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Allows to redirect refunds to Commerce Coordinator API -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2024-03-26 -# .. toggle_target_removal_date: TBA -# .. toggle_tickets: SONIC-382 -# .. toggle_status: supported -ENABLE_TRANSITION_TO_COORDINATOR_REFUNDS = WaffleFlag( - f"{WAFFLE_FLAG_NAMESPACE}.transition_to_coordinator.refunds", - __name__, -) - - -def should_redirect_to_commerce_coordinator_checkout(): - """ - Redirect learners to Commerce Coordinator checkout. - """ - return ENABLE_TRANSITION_TO_COORDINATOR_CHECKOUT.is_enabled() - - -def should_redirect_to_commerce_coordinator_refunds(): - """ - Redirect learners to Commerce Coordinator refunds. - """ - return ENABLE_TRANSITION_TO_COORDINATOR_REFUNDS.is_enabled() diff --git a/lms/djangoapps/course_blocks/transformers/library_content.py b/lms/djangoapps/course_blocks/transformers/library_content.py index 616cf68f4b..10ef8c2138 100644 --- a/lms/djangoapps/course_blocks/transformers/library_content.py +++ b/lms/djangoapps/course_blocks/transformers/library_content.py @@ -14,7 +14,7 @@ from openedx.core.djangoapps.content.block_structure.transformer import ( BlockStructureTransformer, FilteringTransformerMixin ) -from xmodule.library_content_block import LibraryContentBlock # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.library_content_block import LegacyLibraryContentBlock # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from ..utils import get_student_module_as_dict @@ -47,7 +47,6 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo Collects any information that's necessary to execute this transformer's transform method. """ - block_structure.request_xblock_fields('mode') block_structure.request_xblock_fields('max_count') block_structure.request_xblock_fields('category') store = modulestore() @@ -83,7 +82,6 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo if library_children: all_library_children.update(library_children) selected = [] - mode = block_structure.get_xblock_field(block_key, 'mode') max_count = block_structure.get_xblock_field(block_key, 'max_count') if max_count < 0: max_count = len(library_children) @@ -100,7 +98,7 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo # Update selected previous_count = len(selected) - block_keys = LibraryContentBlock.make_selection(selected, library_children, max_count, mode) + block_keys = LegacyLibraryContentBlock.make_selection(selected, library_children, max_count) selected = block_keys['selected'] # Save back any changes @@ -176,7 +174,7 @@ class ContentLibraryTransformer(FilteringTransformerMixin, BlockStructureTransfo with tracker.get_tracker().context(full_event_name, context): tracker.emit(full_event_name, event_data) - LibraryContentBlock.publish_selected_children_events( + LegacyLibraryContentBlock.publish_selected_children_events( block_keys, format_block_keys, publish_event, diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index 0f8227e320..abd1dc5375 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -23,7 +23,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.lib.celery.task_utils import emulate_http_request from openedx.features.course_duration_limits.access import get_user_course_expiration_date -from openedx.features.course_experience import ENABLE_COURSE_GOALS +from openedx.features.course_experience import ENABLE_COURSE_GOALS, ENABLE_SES_FOR_GOALREMINDER from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url log = logging.getLogger(__name__) @@ -86,13 +86,24 @@ def send_ace_message(goal): 'programs_url': getattr(settings, 'ACE_EMAIL_PROGRAMS_URL', None), }) + options = {'transactional': True} + + is_ses_enabled = ENABLE_SES_FOR_GOALREMINDER.is_enabled(goal.course_key) + + if is_ses_enabled: + options = { + 'transactional': True, + 'from_address': settings.LMS_COMM_DEFAULT_FROM_EMAIL, + 'override_default_channel': 'django_email', + } + msg = Message( name="goalreminder", app_label="course_goals", recipient=Recipient(user.id, user.email), language=language, context=message_context, - options={'transactional': True}, + options=options, ) with emulate_http_request(site, user): diff --git a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py index ad60420e0d..5b98b202d4 100644 --- a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py @@ -5,6 +5,7 @@ from pytz import UTC from unittest import mock # lint-amnesty, pylint: disable=wrong-import-order import ddt +from django.conf import settings from django.core.management import call_command from django.test import TestCase from edx_toggles.toggles.testutils import override_waffle_flag @@ -20,7 +21,7 @@ from lms.djangoapps.course_goals.tests.factories import ( from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory from openedx.core.djangolib.testing.utils import skip_unless_lms -from openedx.features.course_experience import ENABLE_COURSE_GOALS +from openedx.features.course_experience import ENABLE_COURSE_GOALS, ENABLE_SES_FOR_GOALREMINDER # Some constants just for clarity of tests (assuming week starts on a Monday, as March 2021 used below does) MONDAY = 0 @@ -180,3 +181,33 @@ class TestGoalReminderEmailCommand(TestCase): def test_old_course(self, end): self.make_valid_goal(overview__end=end) self.call_command(expect_sent=False) + + @mock.patch('lms.djangoapps.course_goals.management.commands.goal_reminder_email.ace.send') + def test_params_with_ses(self, mock_ace): + """Test that the parameters of the msg passed to ace.send() are set correctly when SES is enabled""" + with override_waffle_flag(ENABLE_SES_FOR_GOALREMINDER, active=None): + goal = self.make_valid_goal() + flag = get_waffle_flag_model().get(ENABLE_SES_FOR_GOALREMINDER.name) + flag.users.add(goal.user) + + with freeze_time('2021-03-02 10:00:00'): + call_command('goal_reminder_email') + + assert mock_ace.call_count == 1 + msg = mock_ace.call_args[0][0] + assert msg.options['override_default_channel'] == 'django_email' + assert msg.options['from_address'] == settings.LMS_COMM_DEFAULT_FROM_EMAIL + + @mock.patch('lms.djangoapps.course_goals.management.commands.goal_reminder_email.ace.send') + def test_params_without_ses(self, mock_ace): + """Test that the parameters of the msg passed to ace.send() are set correctly when SES is not enabled""" + self.make_valid_goal() + + with freeze_time('2021-03-02 10:00:00'): + call_command('goal_reminder_email') + + assert mock_ace.call_count == 1 + msg = mock_ace.call_args[0][0] + assert msg.options['transactional'] is True + assert 'override_default_channel' not in msg.options + assert 'from_address' not in msg.options diff --git a/lms/djangoapps/courseware/block_render.py b/lms/djangoapps/courseware/block_render.py index 1bae903224..de92692ce4 100644 --- a/lms/djangoapps/courseware/block_render.py +++ b/lms/djangoapps/courseware/block_render.py @@ -45,7 +45,7 @@ from lms.djangoapps.teams.services import TeamsService from openedx.core.lib.xblock_services.call_to_action import CallToActionService from xmodule.contentstore.django import contentstore from xmodule.exceptions import NotFoundError, ProcessingError -from xmodule.library_tools import LibraryToolsService +from xmodule.library_tools import LegacyLibraryToolsService from xmodule.modulestore.django import XBlockI18nService, modulestore from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.partitions.partitions_service import PartitionService @@ -626,7 +626,7 @@ def prepare_runtime_for_user( ), 'completion': CompletionService(user=user, context_key=course_id) if user and user.is_authenticated else None, 'i18n': XBlockI18nService, - 'library_tools': LibraryToolsService(store, user_id=user.id if user else None), + 'library_tools': LegacyLibraryToolsService(store, user_id=user.id if user else None), 'partitions': PartitionService(course_id=course_id, cache=DEFAULT_REQUEST_CACHE.data), 'settings': SettingsService(), 'user_tags': UserTagsService(user=user, course_id=course_id), diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 79a52db8a0..1c57b23d9b 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -46,7 +46,11 @@ from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle from token_utils.api import unpack_token_for from web_fragments.fragment import Fragment -from xmodule.course_block import COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE +from xmodule.course_block import ( + COURSE_VISIBILITY_PUBLIC, + COURSE_VISIBILITY_PUBLIC_OUTLINE, + CATALOG_VISIBILITY_CATALOG_AND_ABOUT, +) from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem from xmodule.tabs import CourseTabList @@ -288,7 +292,10 @@ def courses(request): course_discovery_meanings = getattr(settings, 'COURSE_DISCOVERY_MEANINGS', {}) set_default_filter = ENABLE_COURSE_DISCOVERY_DEFAULT_LANGUAGE_FILTER.is_enabled() if not settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'): - courses_list = get_courses(request.user) + courses_list = get_courses( + request.user, + filter_={"catalog_visibility": CATALOG_VISIBILITY_CATALOG_AND_ABOUT}, + ) if configuration_helpers.get_value("ENABLE_COURSE_SORTING_BY_START_DATE", settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"]): diff --git a/lms/djangoapps/discussion/rest_api/discussions_notifications.py b/lms/djangoapps/discussion/rest_api/discussions_notifications.py index 4e372280ce..b0eb7c89dc 100644 --- a/lms/djangoapps/discussion/rest_api/discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/discussions_notifications.py @@ -3,7 +3,7 @@ Discussion notifications sender util. """ import re -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, Tag from django.conf import settings from django.utils.text import Truncator @@ -380,6 +380,30 @@ def remove_html_tags(text): return re.sub(clean, '', text) +def strip_empty_tags(soup): + """ + Strip starting and ending empty tags from the soup object + """ + def strip_tag(element, reverse=False): + """ + Checks if element is empty and removes it + """ + if not element.get_text(strip=True): + element.extract() + return True + if isinstance(element, Tag): + child_list = element.contents[::-1] if reverse else element.contents + for child in child_list: + if not strip_tag(child): + break + return False + + while soup.contents: + if not (strip_tag(soup.contents[0]) or strip_tag(soup.contents[-1], reverse=True)): + break + return soup + + def clean_thread_html_body(html_body): """ Get post body with tags removed and limited to 500 characters @@ -392,7 +416,8 @@ def clean_thread_html_body(html_body): "video", "track", # Video Tags "audio", # Audio Tags "embed", "object", "iframe", # Embedded Content - "script" + "script", + "b", "strong", "i", "em", "u", "s", "strike", "del", "ins", "mark", "sub", "sup", # Text Formatting ] # Remove the specified tags while keeping their content @@ -400,18 +425,29 @@ def clean_thread_html_body(html_body): for match in html_body.find_all(tag): match.unwrap() + if not html_body.find(): + return str(html_body) + # Replace tags that are not allowed in email tags_to_update = [ {"source": "button", "target": "span"}, - {"source": "h1", "target": "h4"}, - {"source": "h2", "target": "h4"}, - {"source": "h3", "target": "h4"}, + *[ + {"source": tag, "target": "p"} + for tag in ["div", "section", "article", "h1", "h2", "h3", "h4", "h5", "h6"] + ], ] for tag_dict in tags_to_update: for source_tag in html_body.find_all(tag_dict['source']): target_tag = html_body.new_tag(tag_dict['target'], **source_tag.attrs) - if source_tag.string: - target_tag.string = source_tag.string - source_tag.replace_with(target_tag) + if source_tag.contents: + for content in list(source_tag.contents): + target_tag.append(content) + source_tag.insert_before(target_tag) + source_tag.extract() + for tag in html_body.find_all(True): + tag.attrs = {} + tag['style'] = 'margin: 0' + + html_body = strip_empty_tags(html_body) return str(html_body) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py index d92e1000fe..9e4a76aa40 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py @@ -104,14 +104,14 @@ class TestCleanThreadHtmlBody(unittest.TestCase):

    This is a link to a page.

    Here is an image: image

    Embedded video:

    -

    Script test:

    +

    Script test:

    Some other content that should remain.

    """ - expected_output = ("

    This is a link to a page.

    " - "

    Here is an image:

    " - "

    Embedded video:

    " - "

    Script test: alert('hello');

    " - "

    Some other content that should remain.

    ") + expected_output = ('

    This is a link to a page.

    ' + '

    Here is an image:

    ' + '

    Embedded video:

    ' + '

    Script test: alert("hello");

    ' + '

    Some other content that should remain.

    ') result = clean_thread_html_body(html_body) @@ -132,19 +132,16 @@ class TestCleanThreadHtmlBody(unittest.TestCase): """ Test that the clean_thread_html_body function truncates the HTML body to 500 characters """ - html_body = """ -

    This is a long text that should be truncated to 500 characters.

    - """ * 20 # Repeat to exceed 500 characters - - result = clean_thread_html_body(html_body) - self.assertGreaterEqual(500, len(result)) + html_body = "This is a long text that should be truncated to 500 characters." * 20 + result = clean_thread_html_body(f"

    {html_body}

    ") + self.assertGreaterEqual(525, len(result)) # 500 characters + 25 characters for the HTML tags def test_no_tags_to_remove(self): """ Test that the clean_thread_html_body function does not remove any tags if there are no unwanted tags """ html_body = "

    This paragraph has no tags to remove.

    " - expected_output = "

    This paragraph has no tags to remove.

    " + expected_output = '

    This paragraph has no tags to remove.

    ' result = clean_thread_html_body(html_body) self.assertEqual(result, expected_output) @@ -169,28 +166,49 @@ class TestCleanThreadHtmlBody(unittest.TestCase): result = clean_thread_html_body(html_body) self.assertEqual(result.strip(), expected_output) + def test_tag_replace(self): + """ + Tests if the clean_thread_html_body function replaces tags + """ + for tag in ["div", "section", "article", "h1", "h2", "h3", "h4", "h5", "h6"]: + html_body = f'<{tag}>Text' + result = clean_thread_html_body(html_body) + self.assertEqual(result, '

    Text

    ') + def test_button_tag_replace(self): """ Tests that the clean_thread_html_body function replaces the button tag with span tag """ - # Tests for button replacement tag with text html_body = '' - expected_output = 'Button' + expected_output = 'Button' result = clean_thread_html_body(html_body) self.assertEqual(result, expected_output) - # Tests button tag replacement without text + html_body = '

    abc

    abc

    ' + expected_output = '

    abc

    '\ + '

    abc

    ' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) + + def test_button_tag_removal(self): + """ + Tests button tag with no text is removed if at start or end + """ html_body = '' - expected_output = '' + expected_output = '' result = clean_thread_html_body(html_body) self.assertEqual(result, expected_output) - def test_heading_tag_replace(self): + def test_attributes_removal_from_tag(self): + # Tests for removal of attributes from tags + html_body = '

    Paragraph

    ' + result = clean_thread_html_body(html_body) + self.assertEqual(result, '

    Paragraph

    ') + + def test_strip_empty_tags(self): """ - Tests that the clean_thread_html_body function replaces the h1, h2 and h3 tags with h4 tag + Tests if the clean_thread_html_body function removes starting and ending empty tags """ - for tag in ['h1', 'h2', 'h3']: - html_body = f'<{tag}>Heading' - expected_output = '

    Heading

    ' - result = clean_thread_html_body(html_body) - self.assertEqual(result, expected_output) + html_body = '

    content

    ' + result = clean_thread_html_body(html_body) + self.assertEqual(result, '

    content

    ') diff --git a/lms/djangoapps/grades/rest_api/v1/views.py b/lms/djangoapps/grades/rest_api/v1/views.py index 5f49f2299a..b6835fd61e 100644 --- a/lms/djangoapps/grades/rest_api/v1/views.py +++ b/lms/djangoapps/grades/rest_api/v1/views.py @@ -378,7 +378,7 @@ class SubmissionHistoryView(GradeViewMixin, PaginatedAPIView): def get(self, request, course_id=None): """ Get submission history details. This submission history is related to only - ProblemBlock and it doesn't support LibraryContentBlock or ContentLibraries + ProblemBlock and it doesn't support LegacyLibraryContentBlock or ContentLibraries as of now. **Usecases**: @@ -463,7 +463,7 @@ class SubmissionHistoryView(GradeViewMixin, PaginatedAPIView): @staticmethod def get_problem_blocks(course): """ Get a list of problem xblock for the course. - This doesn't support LibraryContentBlock or ContentLibraries + This doesn't support LegacyLibraryContentBlock or ContentLibraries as of now """ blocks = [] diff --git a/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py b/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py index 3a08ede0aa..4c45f415cf 100644 --- a/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/approve_id_verifications.py @@ -10,9 +10,11 @@ from pprint import pformat from django.core.management.base import BaseCommand, CommandError +from common.djangoapps.student.models_api import get_name, get_pending_name_change from lms.djangoapps.verify_student.api import send_approval_email from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date +from openedx.features.name_affirmation_api.utils import get_name_affirmation_service log = logging.getLogger(__name__) @@ -149,5 +151,37 @@ class Command(BaseCommand): for verification in existing_id_verifications: verification.approve(service='idv_verifications command') send_approval_email(verification) + self._approve_verified_name_for_software_secure_verification(verification) return list(failed_user_ids) + + def _approve_verified_name_for_software_secure_verification(self, verification): + """ + This method manually creates a verified name given a SoftwareSecurePhotoVerification object. + """ + + name_affirmation_service = get_name_affirmation_service() + + if name_affirmation_service: + from edx_name_affirmation.exceptions import VerifiedNameDoesNotExist # pylint: disable=import-error + + pending_name_change = get_pending_name_change(verification.user) + if pending_name_change: + full_name = pending_name_change.new_name + else: + full_name = get_name(verification.user.id) + + try: + name_affirmation_service.update_verified_name_status( + verification.user, + 'approved', + verification_attempt_id=verification.id + ) + except VerifiedNameDoesNotExist: + name_affirmation_service.create_verified_name( + verification.user, + verification.name, + full_name, + verification_attempt_id=verification.id, + status='approved', + ) diff --git a/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py b/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py index e6e580c1d1..6eccee1947 100644 --- a/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py +++ b/lms/djangoapps/verify_student/management/commands/tests/test_approve_id_verifications.py @@ -6,6 +6,8 @@ import ddt import logging import os import tempfile +from unittest import skipUnless +from unittest.mock import MagicMock, patch import pytest from django.core import mail @@ -15,9 +17,12 @@ from testfixtures import LogCapture from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification +from openedx.features.name_affirmation_api.utils import get_name_affirmation_service LOGGER_NAME = 'lms.djangoapps.verify_student.management.commands.approve_id_verifications' +name_affirmation_service = get_name_affirmation_service() + @ddt.ddt class TestApproveIDVerificationsCommand(TestCase): @@ -158,3 +163,57 @@ class TestApproveIDVerificationsCommand(TestCase): """ with pytest.raises(CommandError): call_command('approve_id_verifications', 'invalid/user_id/file/path') + + @skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name_affirmation_service') + def test_create_verified_names(self, mock_get_service): + mock_service = MagicMock() + mock_get_service.return_value = mock_service + + verification = SoftwareSecurePhotoVerification.objects.create( + user=self.user1_profile.user, + name=self.user1_profile.name, + status='submitted', + ) + + call_command('approve_id_verifications', self.tmp_file_path) + mock_service.update_verified_name_status.assert_called_with( + self.user1_profile.user, + 'approved', + verification_attempt_id=verification.id, + ) + + @skipUnless(name_affirmation_service is not None, 'Requires Name Affirmation') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_pending_name_change') + @patch('lms.djangoapps.verify_student.management.commands.approve_id_verifications.get_name_affirmation_service') + @ddt.data( + '', + MagicMock(new_name='test') + ) + def test_create_update_verified_names(self, pending_name, mock_get_service, mock_get_pending, mock_get_name): + from edx_name_affirmation.exceptions import VerifiedNameDoesNotExist # pylint: disable=import-error + + mock_service = MagicMock() + mock_get_service.return_value = mock_service + mock_service.update_verified_name_status.side_effect = VerifiedNameDoesNotExist() + + mock_get_pending.return_value = pending_name + mock_get_name.return_value = self.user1_profile.name + + verification = SoftwareSecurePhotoVerification.objects.create( + user=self.user1_profile.user, + name=self.user1_profile.name, + status='submitted', + ) + + expected_name = 'test' if pending_name else self.user1_profile.name + + call_command('approve_id_verifications', self.tmp_file_path) + mock_service.create_verified_name.assert_called_with( + verification.user, + verification.name, + expected_name, + verification_attempt_id=verification.id, + status='approved', + ) diff --git a/lms/envs/common.py b/lms/envs/common.py index df8878f269..b59d60c751 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4304,13 +4304,6 @@ ECOMMERCE_ORDERS_API_CACHE_TIMEOUT = 3600 ECOMMERCE_SERVICE_WORKER_USERNAME = 'ecommerce_worker' ECOMMERCE_API_SIGNING_KEY = 'SET-ME-PLEASE' -# E-Commerce Commerce Coordinator Configuration -COMMERCE_COORDINATOR_URL_ROOT = 'http://localhost:8140' -COMMERCE_COORDINATOR_REFUND_PATH = '/lms/refund/' -COMMERCE_COORDINATOR_REFUND_SOURCE_SYSTEMS = ('SET-ME-PLEASE',) -COMMERCE_COORDINATOR_SERVICE_WORKER_USERNAME = 'commerce_coordinator_worker' -COORDINATOR_CHECKOUT_REDIRECT_PATH = '/lms/payment_page_redirect/' - # Exam Service EXAMS_SERVICE_URL = 'http://localhost:18740/api/v1' @@ -5541,3 +5534,6 @@ SURVEY_REPORT_EXTRA_DATA = {} # .. for now it wil impact country listing in auth flow and user profile. # .. eg ['US', 'CA'] DISABLED_COUNTRIES = [] + + +LMS_COMM_DEFAULT_FROM_EMAIL = "no-reply@example.com" diff --git a/lms/templates/course_modes/track_selection.html b/lms/templates/course_modes/track_selection.html index 557bd18844..39a5f54403 100644 --- a/lms/templates/course_modes/track_selection.html +++ b/lms/templates/course_modes/track_selection.html @@ -35,9 +35,9 @@ from openedx.core.djangolib.js_utils import js_escaped_string $('.popover-icon').click(function(e){ e.stopPropagation(); if ($('.popover').css("visibility") == "hidden" || $('.popover').css("visibility") == "" ) { - $('.popover').css({"visibility":"visible", "opacity": 1}); + $('.popover').css({"visibility":"visible", "opacity": 1}); } else { - $('.popover').css({"visibility":"hidden", "opacity": 0}); + $('.popover').css({"visibility":"hidden", "opacity": 0}); } }); }); @@ -52,7 +52,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string } window.addEventListener("resize", onresize); - // responsive: show more + // responsive: show more $(function(){ if($(window).width() <= 768) { $('.collapsible-item').css({"display":"none"}); @@ -64,7 +64,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string e.preventDefault(); $('.collapsible').css({"display":"none"}); $('.collapsible-item').css({"display":"list-item"}); - }); + }); }); @@ -112,7 +112,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string

    ${currency_symbol}${min_price} ${currency}

    ${_("Earn a certificate")}

    - <%block name="track_selection_certificate_bullets"/> + <%block name="track_selection_certificate_bullets"/>