diff --git a/lms/djangoapps/edxnotes/decorators.py b/lms/djangoapps/edxnotes/decorators.py index 39d5c0b076d4..866df4915999 100644 --- a/lms/djangoapps/edxnotes/decorators.py +++ b/lms/djangoapps/edxnotes/decorators.py @@ -22,7 +22,15 @@ def get_html(self, *args, **kwargs): Returns raw html for the component. """ # Import is placed here to avoid model import at project startup. - from .helpers import generate_uid, get_edxnotes_id_token, get_public_endpoint, get_token_url, is_feature_enabled + from .helpers import ( + generate_uid, + get_ccx_master_course_key, + get_ccx_master_usage_key, + get_edxnotes_id_token, + get_public_endpoint, + get_token_url, + is_feature_enabled, + ) if not settings.ENABLE_EDXNOTES: return original_get_html(self, *args, **kwargs) @@ -47,6 +55,14 @@ def get_html(self, *args, **kwargs): if is_studio or not is_feature_enabled(course, user): return original_get_html(self, *args, **kwargs) else: + # Notes are recorded against the master course (and its own block ids), + # not the CCX, so a note taken in any CCX section shows up in every CCX + # section derived from the same master course. `tokenUrl` deliberately + # keeps the real (CCX) course id: that URL is routed/access-checked + # against the course actually being viewed, not the note's data key. + notes_course_id = get_ccx_master_course_key(course.id) + notes_usage_id = get_ccx_master_usage_key(self.scope_ids.usage_id) + return render_to_string("edxnotes_wrapper.html", { "content": original_get_html(self, *args, **kwargs), "uid": generate_uid(), @@ -55,8 +71,8 @@ def get_html(self, *args, **kwargs): ), "params": { # Use camelCase to name keys. - "usageId": self.scope_ids.usage_id, - "courseId": course.id, + "usageId": notes_usage_id, + "courseId": notes_course_id, "token": get_edxnotes_id_token(user), "tokenUrl": get_token_url(course.id), "endpoint": get_public_endpoint(), diff --git a/lms/djangoapps/edxnotes/helpers.py b/lms/djangoapps/edxnotes/helpers.py index ad7a7af15eeb..0726a807fa2e 100644 --- a/lms/djangoapps/edxnotes/helpers.py +++ b/lms/djangoapps/edxnotes/helpers.py @@ -74,6 +74,37 @@ def get_token_url(course_id): }) +def get_ccx_master_course_key(course_key): + """ + If `course_key` identifies a CCX (Custom Course for edX), return the CourseKey + of the CCX's master course. Otherwise return `course_key` unchanged. + + This lets notes/annotations created while viewing a CCX be recorded against the + shared master course rather than that one CCX section, so the same user sees the + same notes on every CCX section (and the master course itself) derived from it. + """ + ccx_id = getattr(course_key, 'ccx', None) + if not ccx_id: + return course_key + + # Imported locally to avoid a hard import-time dependency between the edxnotes + # and ccx apps. + from lms.djangoapps.ccx.utils import get_ccx_from_ccx_locator # pylint: disable=import-outside-toplevel + ccx = get_ccx_from_ccx_locator(course_key) + return ccx.course_id if ccx else course_key + + +def get_ccx_master_usage_key(usage_key): + """ + If `usage_key` identifies a block within a CCX, return the equivalent UsageKey + for the same block within the CCX's master course (CCX field overrides and + scheduling don't change the underlying content, so the block ids line up). + Otherwise return `usage_key` unchanged. + """ + to_block_locator = getattr(usage_key, 'to_block_locator', None) + return to_block_locator() if to_block_locator else usage_key + + def send_request(user, course_id, page, page_size, path="", text=None): """ Sends a request to notes api with appropriate parameters and headers. @@ -332,7 +363,10 @@ def get_notes(request, course, page=DEFAULT_PAGE, page_size=DEFAULT_PAGE_SIZE, t results: list with notes info dictionary. each item in this list will be a dict """ path = 'search' if text else 'annotations' - response = send_request(request.user, course.id, page, page_size, path, text) + # Resolve to the master course so notes taken inside any CCX section are + # recorded against (and read back from) the shared master course. + notes_course_id = get_ccx_master_course_key(course.id) + response = send_request(request.user, notes_course_id, page, page_size, path, text) try: collection = json.loads(response.content.decode('utf-8'))