Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions cms/djangoapps/contentstore/core/course_optimizer_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,17 +491,24 @@ def _generate_course_updates_content(course, updates_links, course_key=None):
if not update_items:
return course_updates

# Group links by update ID and categorize them
links_by_update = {}
for item in updates_links:
if len(item) >= 2:
update_id, link = item[0], item[1]
link_state = item[2] if len(item) >= 3 else LinkState.BROKEN
links_by_update.setdefault(update_id, _create_empty_links_data())
_categorize_link_by_state(link, link_state, links_by_update[update_id], course_key)

for update in update_items:
if update.get("status") != "deleted":
update_content = update.get("content", "")
update_link_data = _process_content_links(update_content, updates_links, course_key)

update_id = update.get("id")
course_updates.append(
{
"id": str(update.get("id")),
"id": str(update_id),
"displayName": update.get("date", "Unknown Date"),
"url": f"/course/{str(course.id)}/course_info",
**update_link_data,
**links_by_update.get(update_id, _create_empty_links_data()),
}
)

Expand All @@ -522,14 +529,21 @@ def _generate_handouts_content(course, handouts_links, course_key=None):
):
return course_handouts

links_data = _process_content_links(handouts_block.data, handouts_links, course_key)
# Group links by block_id and categorize them
links_by_handout = {}
for item in handouts_links:
if len(item) >= 2:
block_id, link = item[0], item[1]
link_state = item[2] if len(item) >= 3 else LinkState.BROKEN
links_by_handout.setdefault(block_id, _create_empty_links_data())
_categorize_link_by_state(link, link_state, links_by_handout[block_id], course_key)

course_handouts = [
{
"id": str(usage_key),
"displayName": "handouts",
"url": f"/course/{str(course.id)}/course_info",
**links_data,
**links_by_handout.get(str(usage_key), _create_empty_links_data()),
}
]
return course_handouts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)
from cms.djangoapps.contentstore.tasks import LinkState, extract_content_URLs_from_course
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.utils import contains_previous_course_reference
from cms.djangoapps.contentstore.utils import contains_course_reference
from xmodule.tabs import StaticTab


Expand Down Expand Up @@ -329,7 +329,7 @@ def test_prev_run_link_detection(self):

for url, expected_match in test_cases:
with self.subTest(url=url, expected=expected_match):
result = contains_previous_course_reference(url, previous_course_key)
result = contains_course_reference(url, previous_course_key)
self.assertEqual(
result,
expected_match,
Expand Down
58 changes: 33 additions & 25 deletions cms/djangoapps/contentstore/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from cms.djangoapps.contentstore.toggles import enable_course_optimizer_check_prev_run_links
from cms.djangoapps.contentstore.utils import (
IMPORTABLE_FILE_TYPES,
contains_previous_course_reference,
contains_course_reference,
create_course_info_usage_key,
create_or_update_xblock_upstream_link,
delete_course,
Expand Down Expand Up @@ -1190,7 +1190,7 @@ def _check_broken_links(task_instance, user_id, course_key_string, language):
# Separate previous run links from regular links BEFORE validation
urls_to_validate = []
for block_id, url in url_list:
if contains_previous_course_reference(url, previous_run_course_key):
if contains_course_reference(url, previous_run_course_key):
previous_run_links.append([block_id, url, LinkState.PREVIOUS_RUN])
else:
urls_to_validate.append([block_id, url])
Expand Down Expand Up @@ -1917,28 +1917,45 @@ def _course_link_update_required(url, course_key, prev_run_course_key):
Args:
url: The URL to check
course_key: The current course key
prev_run_course_key: The previous course run key

Returns:
bool: True if the link needs updating
"""

if not url or not course_key:
return False

course_id_match = contains_previous_course_reference(url, prev_run_course_key)
if not course_id_match:
if not all((url, course_key, prev_run_course_key)):
return False

# Check if it's the same org and course but different run
if (
prev_run_course_key.org == course_key.org
and prev_run_course_key.course == course_key.course
and prev_run_course_key.run != course_key.run
):
course_id_match = contains_course_reference(url, prev_run_course_key)
if course_id_match:
return True

return False


def _replace_exact_course_reference(url, old_course_key, new_course_key):
"""
Replaces exact course key references in a URL, avoiding partial matches.

Args:
url: The URL to update
old_course_key: The course key to replace
new_course_key: The course key to replace with

Returns:
str: Updated URL with exact course key replacements
"""
if not old_course_key or not new_course_key or not url:
return url

old_course_pattern = re.escape(str(old_course_key))

# Ensure the course key is followed by '/' or end of string
pattern = old_course_pattern + r'(?=/|$)'

return re.sub(pattern, str(new_course_key), url, flags=re.IGNORECASE)


def _determine_link_type(block_id):
"""
Determines the type of link based on block_id and URL.
Expand Down Expand Up @@ -1987,22 +2004,13 @@ def _update_link_to_latest_rerun(link_data, course_key, prev_run_course_key, use
if not original_url:
return original_url

prev_run_course_org = prev_run_course_key.org if prev_run_course_key else None
prev_run_course_course = (
prev_run_course_key.course if prev_run_course_key else None
)

if prev_run_course_key == course_key:
return original_url

# Validate url based on previous-run org
if (
prev_run_course_org != course_key.org
or prev_run_course_course != course_key.course
):
return original_url
new_url = _replace_exact_course_reference(original_url, prev_run_course_key, course_key)

new_url = original_url.replace(str(prev_run_course_key), str(course_key))
if new_url == original_url:
return original_url

# condition because we're showing handouts as updates
if link_type == "course_updates" and "handouts" in str(block_id):
Expand Down
20 changes: 13 additions & 7 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2477,18 +2477,24 @@ def get_previous_run_course_key(course_key):
return rerun_state.source_course_key


def contains_previous_course_reference(url, previous_course_key):
def contains_course_reference(url, course_key):
"""
Checks if a URL contains references to the previous course.
Checks if a URL contains an exact reference to the specified course key.
Uses specific delimiter matching to ensure exact matching and avoid partial matches.

Arguments:
Args:
url: The URL to check
previous_course_key: The previous course key to look for
course_key: The course key to look for

Returns:
bool: True if URL contains reference to previous course
bool: True if URL contains exact reference to the course
"""
if not previous_course_key:
if not course_key or not url:
return False

return str(previous_course_key).lower() in url.lower()
course_key_pattern = re.escape(str(course_key))

# Ensure the course key is followed by '/' or end of string
pattern = course_key_pattern + r'(?=/|$)'

return bool(re.search(pattern, url, re.IGNORECASE))
Loading