diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py
index eafc2b37aa0c..189f2496a427 100644
--- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py
+++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py
@@ -1,6 +1,7 @@
"""
Unit tests for course index outline.
"""
+from django.conf import settings
from django.test import RequestFactory
from django.urls import reverse
from rest_framework import status
@@ -62,7 +63,7 @@ def test_course_index_response(self):
"advance_settings_url": f"/settings/advanced/{self.course.id}"
},
"discussions_incontext_feedback_url": "",
- "discussions_incontext_learnmore_url": "",
+ "discussions_incontext_learnmore_url": settings.DISCUSSIONS_INCONTEXT_LEARNMORE_URL,
"is_custom_relative_dates_active": True,
"initial_state": None,
"initial_user_clipboard": {
@@ -103,7 +104,7 @@ def test_course_index_response_with_show_locators(self):
"advance_settings_url": f"/settings/advanced/{self.course.id}"
},
"discussions_incontext_feedback_url": "",
- "discussions_incontext_learnmore_url": "",
+ "discussions_incontext_learnmore_url": settings.DISCUSSIONS_INCONTEXT_LEARNMORE_URL,
"is_custom_relative_dates_active": False,
"initial_state": {
"expanded_locators": [
diff --git a/cms/djangoapps/contentstore/views/transcripts_ajax.py b/cms/djangoapps/contentstore/views/transcripts_ajax.py
index 892b76caae72..8cb7f455013b 100644
--- a/cms/djangoapps/contentstore/views/transcripts_ajax.py
+++ b/cms/djangoapps/contentstore/views/transcripts_ajax.py
@@ -649,6 +649,9 @@ def _get_item(request, data):
Returns the item.
"""
usage_key = UsageKey.from_string(data.get('locator'))
+ if not usage_key.context_key.is_course:
+ # TODO: implement transcript support for learning core / content libraries.
+ raise TranscriptsRequestValidationException(_('Transcripts are not yet supported in content libraries.'))
# This is placed before has_course_author_access() to validate the location,
# because has_course_author_access() raises r if location is invalid.
item = modulestore().get_item(usage_key)
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 79186248414d..986b7262d92d 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -2813,8 +2813,14 @@
BRAZE_COURSE_ENROLLMENT_CANVAS_ID = ''
+######################## Discussion Forum settings ########################
+
+# Feedback link in upgraded discussion notification alert
DISCUSSIONS_INCONTEXT_FEEDBACK_URL = ''
-DISCUSSIONS_INCONTEXT_LEARNMORE_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"
#### django-simple-history##
# disable indexing on date field its coming django-simple-history.
diff --git a/docs/conf.py b/docs/conf.py
index f37fc32f6160..01280c6cd214 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -258,6 +258,16 @@
epub_exclude_files = ['search.html']
+# -- Read the Docs Specific Configuration
+# Define the canonical URL if you are using a custom domain on Read the Docs
+html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
+
+# Tell Jinja2 templates the build is running on Read the Docs
+if os.environ.get("READTHEDOCS", "") == "True":
+ if "html_context" not in globals():
+ html_context = {}
+ html_context["READTHEDOCS"] = True
+
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py
index 2f43896c197e..4c6227af309f 100644
--- a/openedx/core/djangoapps/content/search/tests/test_api.py
+++ b/openedx/core/djangoapps/content/search/tests/test_api.py
@@ -141,7 +141,7 @@ def setUp(self):
"context_key": "lib:org1:lib",
"org": "org1",
"breadcrumbs": [{"display_name": "Library"}],
- "content": {"problem_types": [], "capa_content": " "},
+ "content": {"problem_types": [], "capa_content": ""},
"type": "library_block",
"access_id": lib_access.id,
"last_published": None,
@@ -157,7 +157,7 @@ def setUp(self):
"context_key": "lib:org1:lib",
"org": "org1",
"breadcrumbs": [{"display_name": "Library"}],
- "content": {"problem_types": [], "capa_content": " "},
+ "content": {"problem_types": [], "capa_content": ""},
"type": "library_block",
"access_id": lib_access.id,
"last_published": None,
diff --git a/openedx/core/djangoapps/content/search/tests/test_handlers.py b/openedx/core/djangoapps/content/search/tests/test_handlers.py
index 8a6627e3902d..bdc4814d1c8f 100644
--- a/openedx/core/djangoapps/content/search/tests/test_handlers.py
+++ b/openedx/core/djangoapps/content/search/tests/test_handlers.py
@@ -148,7 +148,7 @@ def test_create_delete_library_block(self, meilisearch_client):
"context_key": "lib:orgA:lib_a",
"org": "orgA",
"breadcrumbs": [{"display_name": "Library Org A"}],
- "content": {"problem_types": [], "capa_content": " "},
+ "content": {"problem_types": [], "capa_content": ""},
"access_id": lib_access.id,
"last_published": None,
"created": created_date.timestamp(),
diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py
index 54ca0cbc312f..c1c650144b05 100644
--- a/xmodule/capa_block.py
+++ b/xmodule/capa_block.py
@@ -616,11 +616,15 @@ def index_dictionary(self):
"",
capa_content
)
+ # Strip out all other tags, leaving their content. But we want spaces between adjacent tags, so that
+ # Option A
Option B
+ # becomes "Option A Option B" not "Option AOption B" (these will appear in search results)
+ capa_content = re.sub(r"(\w+)><([^>]+)>", r"\1> <\2>", capa_content)
capa_content = re.sub(
r"(\s| |//)+",
" ",
nh3.clean(capa_content, tags=set())
- )
+ ).strip()
capa_body = {
"capa_content": capa_content,
diff --git a/xmodule/partitions/tests/test_partitions.py b/xmodule/partitions/tests/test_partitions.py
index fde6afd3141b..41f26b14db52 100644
--- a/xmodule/partitions/tests/test_partitions.py
+++ b/xmodule/partitions/tests/test_partitions.py
@@ -462,21 +462,6 @@ class TestPartitionService(PartitionServiceBaseClass):
Test getting a user's group out of a partition
"""
- def test_get_user_group_id_for_partition(self):
- # assign the first group to be returned
- user_partition_id = self.user_partition.id
- groups = self.user_partition.groups
- self.user_partition.scheme.current_group = groups[0]
-
- # get a group assigned to the user
- group1_id = self.partition_service.get_user_group_id_for_partition(self.user, user_partition_id)
- assert group1_id == groups[0].id
-
- # switch to the second group and verify that it is returned for the user
- self.user_partition.scheme.current_group = groups[1]
- group2_id = self.partition_service.get_user_group_id_for_partition(self.user, user_partition_id)
- assert group2_id == groups[1].id
-
def test_caching(self):
username = "psvc_cache_user"
user_partition_id = self.user_partition.id
diff --git a/xmodule/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py
index d1c01e109718..c81b137f1b23 100644
--- a/xmodule/tests/test_capa_block.py
+++ b/xmodule/tests/test_capa_block.py
@@ -3290,7 +3290,7 @@ def test_response_types_ignores_non_response_tags(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['multiplechoiceresponse'],
- 'content': {'display_name': name, 'capa_content': ' Label Some comment Apple Banana Chocolate Donut '}}
+ 'content': {'display_name': name, 'capa_content': 'Label Some comment Apple Banana Chocolate Donut'}}
def test_response_types_multiple_tags(self):
xml = textwrap.dedent("""
@@ -3328,7 +3328,7 @@ def test_response_types_multiple_tags(self):
'problem_types': {"optionresponse", "multiplechoiceresponse"},
'content': {
'display_name': name,
- 'capa_content': " Label Some comment Donut Buggy '1','2' "
+ 'capa_content': "Label Some comment Donut Buggy '1','2'"
},
}
)
@@ -3369,7 +3369,7 @@ def test_solutions_not_indexed(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': [],
- 'content': {'display_name': name, 'capa_content': ' '}}
+ 'content': {'display_name': name, 'capa_content': ''}}
def test_indexing_checkboxes(self):
name = "Checkboxes"
@@ -3390,7 +3390,7 @@ def test_indexing_checkboxes(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['choiceresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_dropdown(self):
name = "Dropdown"
@@ -3405,7 +3405,7 @@ def test_indexing_dropdown(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['optionresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_multiple_choice(self):
name = "Multiple Choice"
@@ -3424,7 +3424,7 @@ def test_indexing_multiple_choice(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['multiplechoiceresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_numerical_input(self):
name = "Numerical Input"
@@ -3446,7 +3446,7 @@ def test_indexing_numerical_input(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['numericalresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_text_input(self):
name = "Text Input"
@@ -3465,7 +3465,7 @@ def test_indexing_text_input(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['stringresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_non_latin_problem(self):
sample_text_input_problem_xml = textwrap.dedent("""
@@ -3476,7 +3476,7 @@ def test_indexing_non_latin_problem(self):
""")
name = "Non latin Input"
block = self._create_block(sample_text_input_problem_xml, name=name)
- capa_content = " Δοκιμή με μεταβλητές με Ελληνικούς χαρακτήρες μέσα σε python: $FX1_VAL "
+ capa_content = "Δοκιμή με μεταβλητές με Ελληνικούς χαρακτήρες μέσα σε python: $FX1_VAL"
block_dict = block.index_dictionary()
assert block_dict['content']['capa_content'] == smart_str(capa_content)
@@ -3503,7 +3503,7 @@ def test_indexing_checkboxes_with_hints_and_feedback(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['choiceresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_dropdown_with_hints_and_feedback(self):
name = "Dropdown with Hints and Feedback"
@@ -3523,7 +3523,7 @@ def test_indexing_dropdown_with_hints_and_feedback(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['optionresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_multiple_choice_with_hints_and_feedback(self):
name = "Multiple Choice with Hints and Feedback"
@@ -3543,7 +3543,7 @@ def test_indexing_multiple_choice_with_hints_and_feedback(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['multiplechoiceresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_numerical_input_with_hints_and_feedback(self):
name = "Numerical Input with Hints and Feedback"
@@ -3561,7 +3561,7 @@ def test_indexing_numerical_input_with_hints_and_feedback(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['numericalresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_text_input_with_hints_and_feedback(self):
name = "Text Input with Hints and Feedback"
@@ -3579,7 +3579,7 @@ def test_indexing_text_input_with_hints_and_feedback(self):
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': ['stringresponse'],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}}
def test_indexing_problem_with_html_tags(self):
sample_problem_xml = textwrap.dedent("""
@@ -3598,14 +3598,33 @@ def test_indexing_problem_with_html_tags(self):
""")
name = "Mixed business"
block = self._create_block(sample_problem_xml, name=name)
- capa_content = textwrap.dedent("""
- This has HTML comment in it.
- HTML end.
- """)
+ capa_content = "This has HTML comment in it. HTML end."
assert block.index_dictionary() ==\
{'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
'problem_types': [],
- 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}}
+ 'content': {'display_name': name, 'capa_content': capa_content}}
+
+ def test_indexing_problem_with_no_whitespace_between_tags(self):
+ """
+ The new (MFE) visual editor for capa problems renders the OLX without spaces between the tags.
+ We want to make sure the index description is still readable and has whitespace.
+ """
+ sample_problem_xml = (
+ ""
+ "Question text here.
"
+ "Option A
"
+ "Option B
"
+ ""
+ ""
+ )
+ name = "No spaces"
+ block = self._create_block(sample_problem_xml, name=name)
+ capa_content = "Question text here. Option A Option B"
+ assert block.index_dictionary() == {
+ 'content_type': ProblemBlock.INDEX_CONTENT_TYPE,
+ 'problem_types': ['choiceresponse'],
+ 'content': {'display_name': name, 'capa_content': capa_content},
+ }
def test_invalid_xml_handling(self):
"""
diff --git a/xmodule/video_block/transcripts_utils.py b/xmodule/video_block/transcripts_utils.py
index e41f925295f0..132b8cff1e14 100644
--- a/xmodule/video_block/transcripts_utils.py
+++ b/xmodule/video_block/transcripts_utils.py
@@ -1074,7 +1074,7 @@ def get_transcript_from_learning_core(video_block, language, output_format, tran
"""
# TODO: Update to use Learning Core data models once static assets support
# has been added.
- raise NotImplementedError("Transcripts not supported.")
+ raise NotFoundError("No transcript - transcripts not supported yet by learning core components.")
def get_transcript(video, lang=None, output_format=Transcript.SRT, youtube_id=None):
diff --git a/xmodule/video_block/video_block.py b/xmodule/video_block/video_block.py
index b4fddb63fa7a..782645c373b0 100644
--- a/xmodule/video_block/video_block.py
+++ b/xmodule/video_block/video_block.py
@@ -482,7 +482,7 @@ def get_html(self, view=STUDENT_VIEW, context=None): # lint-amnesty, pylint: di
'hide_downloads': is_public_view or is_embed,
'id': self.location.html_id(),
'block_id': str(self.location),
- 'course_id': str(self.location.course_key),
+ 'course_id': str(self.context_key),
'video_id': str(self.edx_video_id),
'user_id': self.get_user_id(),
'is_embed': is_embed,
@@ -510,8 +510,10 @@ def get_course_video_sharing_override(self):
"""
Return course video sharing options override or None
"""
+ if not self.context_key.is_course:
+ return False # Only courses support this feature at all (not libraries)
try:
- course = get_course_by_id(self.course_id)
+ course = get_course_by_id(self.context_key)
return getattr(course, 'video_sharing_options', None)
# In case the course / modulestore does something weird
@@ -523,11 +525,13 @@ def is_public_sharing_enabled(self):
"""
Is public sharing enabled for this video?
"""
+ if not self.context_key.is_course:
+ return False # Only courses support this feature at all (not libraries)
try:
# Video share feature must be enabled for sharing settings to take effect
- feature_enabled = PUBLIC_VIDEO_SHARE.is_enabled(self.location.course_key)
+ feature_enabled = PUBLIC_VIDEO_SHARE.is_enabled(self.context_key)
except Exception as err: # pylint: disable=broad-except
- log.exception(f"Error retrieving course for course ID: {self.location.course_key}")
+ log.exception(f"Error retrieving course for course ID: {self.context_key}")
return False
if not feature_enabled:
return False
@@ -552,11 +556,13 @@ def is_transcript_feedback_enabled(self):
"""
Is transcript feedback enabled for this video?
"""
+ if not self.context_key.is_course:
+ return False # Only courses support this feature at all (not libraries)
try:
# Video transcript feedback must be enabled in order to show the widget
- feature_enabled = TRANSCRIPT_FEEDBACK.is_enabled(self.location.course_key)
+ feature_enabled = TRANSCRIPT_FEEDBACK.is_enabled(self.context_key)
except Exception as err: # pylint: disable=broad-except
- log.exception(f"Error retrieving course for course ID: {self.location.course_key}")
+ log.exception(f"Error retrieving course for course ID: {self.context_key}")
return False
return feature_enabled