From fa2ee27b780dbb28afdb6742b0b66cc4595990d7 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Fri, 12 Sep 2025 14:11:35 -0500 Subject: [PATCH 1/9] feat: Add `blockType` to the showXBlockLibraryChangesPreview iframe message --- cms/static/js/views/pages/container.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 831f71747da1..b01d6796efe5 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -586,9 +586,10 @@ function($, _, Backbone, gettext, BasePage, payload: { downstreamBlockId: xblockInfo.get('id'), displayName: xblockInfo.get('display_name'), - isVertical: xblockInfo.isVertical(), + isContainer: false, upstreamBlockId, upstreamBlockVersionSynced, + blockType: xblockInfo.get('category'), } }, document.referrer ); From 52eb38c68543c8aeed8f1c66e04cc016c9c769f1 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 15 Sep 2025 19:32:52 -0500 Subject: [PATCH 2/9] feat: Add title to the xblock_iframe --- cms/static/sass/course-unit-mfe-iframe-bundle.scss | 7 +++++++ common/templates/xblock_v2/xblock_iframe.html | 5 +++++ openedx/core/djangoapps/xblock/rest_api/views.py | 8 ++++++++ 3 files changed, 20 insertions(+) diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index b53210484a04..b58eb0231d3d 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -470,6 +470,13 @@ body, &.xblock-iframe-content { height: 100%; + .h2 { + font-size: 1.5em; + font-weight: bold; + margin-block-start: 0.83em; + margin-block-end: 0.83em; + } + // Reset the max-height to allow the settings list to grow .wrapper-comp-settings .list-input.settings-list { max-height: unset; diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index cd3096aa4626..5e4b3be8a516 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -196,6 +196,11 @@ event listeners below, in certain situations. Resetting it to the default "auto" skirts the problem.-->
+ {% if title %} +
+ {{ title | safe }} +
+ {% endif %} {{ fragment.body_html | safe }} diff --git a/openedx/core/djangoapps/xblock/rest_api/views.py b/openedx/core/djangoapps/xblock/rest_api/views.py index a71fb6cfd5e2..ef3316bccfbe 100644 --- a/openedx/core/djangoapps/xblock/rest_api/views.py +++ b/openedx/core/djangoapps/xblock/rest_api/views.py @@ -18,6 +18,7 @@ from rest_framework.decorators import api_view, permission_classes # lint-amnesty, pylint: disable=unused-import from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, NotFound from rest_framework.response import Response +from rest_framework.fields import BooleanField from rest_framework.views import APIView from xblock.django.request import DjangoWebobRequest, webob_to_django_response from xblock.exceptions import NoSuchUsage @@ -99,6 +100,10 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): Unstable - may change after Sumac """ + show_title = request.GET.get('show_title') + title = '' + if show_title is not None: + show_title = BooleanField().to_internal_value(show_title) # Check if a specific version has been requested. TODO: move this to a URL path param like the other views? try: version = VersionConverter().to_python(request.GET.get("version")) @@ -110,6 +115,8 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): except NoSuchUsage as exc: raise NotFound(f"{usage_key} not found") from exc + if show_title: + title = block.display_name fragment = _render_block_view(block, view_name, request.user) handler_urls = { str(block.usage_key): _get_handler_url(block.usage_key, 'handler_name', request.user, version=version) @@ -147,6 +154,7 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): 'view_name': view_name, 'is_development': settings.DEBUG, 'oa_manifest': new_oa_manifest, + 'title': title, } response = render(request, 'xblock_v2/xblock_iframe.html', context, content_type='text/html') From ed9277aca8ef0e420b44ba1b9dbc88de4a189094 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Wed, 17 Sep 2025 17:04:03 -0500 Subject: [PATCH 3/9] feat: Add is-modified to studio_xblock_wrapper --- cms/static/js/views/pages/container.js | 2 ++ cms/templates/studio_xblock_wrapper.html | 1 + 2 files changed, 3 insertions(+) diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index b01d6796efe5..8429db4d4b34 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -577,6 +577,7 @@ function($, _, Backbone, gettext, BasePage, const headerElement = xblockElement.find('.xblock-header-primary'); const upstreamBlockId = headerElement.data('upstream-ref'); const upstreamBlockVersionSynced = headerElement.data('version-synced'); + const upstreamDownstreamIsModified = headerElement.data('is-modified'); try { if (this.options.isIframeEmbed) { @@ -589,6 +590,7 @@ function($, _, Backbone, gettext, BasePage, isContainer: false, upstreamBlockId, upstreamBlockVersionSynced, + upstreamDownstreamIsModified: upstreamDownstreamIsModified === 'True', blockType: xblockInfo.get('category'), } }, document.referrer diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html index 2c57872e10d3..4fb3c71803cb 100644 --- a/cms/templates/studio_xblock_wrapper.html +++ b/cms/templates/studio_xblock_wrapper.html @@ -93,6 +93,7 @@ % if upstream_info.upstream_ref: data-upstream-ref = ${upstream_info.upstream_ref} data-version-synced = ${upstream_info.version_synced} + data-is-modified = ${upstream_info.is_modified} %endif >
From 2ca2e7dc64102515f36304b6fc83f5fa3d5a0ef3 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Wed, 17 Sep 2025 18:04:17 -0500 Subject: [PATCH 4/9] feat: Add disable_staff_debug_info as query params in in render_xblock --- lms/djangoapps/courseware/views/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 1b199df151d6..5b3ed4bb8d49 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -47,6 +47,7 @@ from rest_framework.decorators import api_view, throttle_classes from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle +from rest_framework.fields import BooleanField from web_fragments.fragment import Fragment from xmodule.course_block import ( COURSE_VISIBILITY_PUBLIC, @@ -1576,6 +1577,9 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True, disable_sta Returns an HttpResponse with HTML content for the xBlock with the given usage_key. The returned HTML is a chromeless rendering of the xBlock (excluding content of the containing courseware). """ + if not disable_staff_debug_info: + disable_staff_debug_info = BooleanField().to_internal_value(request.GET.get('disable_staff_debug_info', False)) + usage_key = UsageKey.from_string(usage_key_string) usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key)) From 4ff50e95c3e83dfaf27d72b9d3d201abe2f388e0 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 18 Sep 2025 14:43:07 -0500 Subject: [PATCH 5/9] refactor: Update styles in xblock_iframe --- cms/static/sass/course-unit-mfe-iframe-bundle.scss | 3 ++- common/templates/xblock_v2/xblock_iframe.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index b58eb0231d3d..6c9b43cf0b25 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -470,7 +470,8 @@ body, &.xblock-iframe-content { height: 100%; - .h2 { + .xblock-title { + margin-bottom: 1.5em !important; font-size: 1.5em; font-weight: bold; margin-block-start: 0.83em; diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index 5e4b3be8a516..7aa32195110d 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -197,7 +197,7 @@
{% if title %} -
+
{{ title | safe }}
{% endif %} From 375129d0c92040035d0b5d9645e7d88d11ae64c4 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 23 Sep 2025 14:02:49 -0500 Subject: [PATCH 6/9] refactor: Rename upstreamDownstreamIsModified by isLocallyModified --- cms/static/js/views/pages/container.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 8429db4d4b34..d50f6b4bbe4a 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -577,7 +577,7 @@ function($, _, Backbone, gettext, BasePage, const headerElement = xblockElement.find('.xblock-header-primary'); const upstreamBlockId = headerElement.data('upstream-ref'); const upstreamBlockVersionSynced = headerElement.data('version-synced'); - const upstreamDownstreamIsModified = headerElement.data('is-modified'); + const isLocallyModified = headerElement.data('is-modified'); try { if (this.options.isIframeEmbed) { @@ -590,7 +590,7 @@ function($, _, Backbone, gettext, BasePage, isContainer: false, upstreamBlockId, upstreamBlockVersionSynced, - upstreamDownstreamIsModified: upstreamDownstreamIsModified === 'True', + isLocallyModified: isLocallyModified === 'True', blockType: xblockInfo.get('category'), } }, document.referrer From f2b7a14eff13973224f1340699bc28a256358338 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 23 Sep 2025 15:56:04 -0500 Subject: [PATCH 7/9] feat: `downstream_is_modified` added to EntityLinkBase --- ...entlink_downstream_is_modified_and_more.py | 23 +++++ cms/djangoapps/contentstore/models.py | 5 + .../tests/test_downstream_sync_integration.py | 98 ++++++++++++++++--- .../v2/views/tests/test_downstreams.py | 32 ++++++ cms/djangoapps/contentstore/utils.py | 2 + 5 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 cms/djangoapps/contentstore/migrations/0013_componentlink_downstream_is_modified_and_more.py diff --git a/cms/djangoapps/contentstore/migrations/0013_componentlink_downstream_is_modified_and_more.py b/cms/djangoapps/contentstore/migrations/0013_componentlink_downstream_is_modified_and_more.py new file mode 100644 index 000000000000..c119387ce7d6 --- /dev/null +++ b/cms/djangoapps/contentstore/migrations/0013_componentlink_downstream_is_modified_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.24 on 2025-09-23 19:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contentstore', '0012_componentlink_top_level_parent_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='componentlink', + name='downstream_is_modified', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='containerlink', + name='downstream_is_modified', + field=models.BooleanField(default=False), + ), + ] diff --git a/cms/djangoapps/contentstore/models.py b/cms/djangoapps/contentstore/models.py index fd98ec44fe09..47a450cde867 100644 --- a/cms/djangoapps/contentstore/models.py +++ b/cms/djangoapps/contentstore/models.py @@ -108,6 +108,7 @@ class EntityLinkBase(models.Model): top_level_parent = models.ForeignKey("ContainerLink", on_delete=models.SET_NULL, null=True, blank=True) version_synced = models.IntegerField() version_declined = models.IntegerField(null=True, blank=True) + downstream_is_modified = models.BooleanField(default=False) created = manual_date_time_field() updated = manual_date_time_field() @@ -257,6 +258,7 @@ def update_or_create( version_synced: int, top_level_parent_usage_key: UsageKey | None = None, version_declined: int | None = None, + downstream_is_modified: bool = False, created: datetime | None = None, ) -> "ComponentLink": """ @@ -281,6 +283,7 @@ def update_or_create( 'version_synced': version_synced, 'version_declined': version_declined, 'top_level_parent': top_level_parent, + 'downstream_is_modified': downstream_is_modified, } if upstream_block: new_values['upstream_block'] = upstream_block @@ -482,6 +485,7 @@ def update_or_create( version_synced: int, top_level_parent_usage_key: UsageKey | None = None, version_declined: int | None = None, + downstream_is_modified: bool = False, created: datetime | None = None, ) -> "ContainerLink": """ @@ -506,6 +510,7 @@ def update_or_create( 'version_synced': version_synced, 'version_declined': version_declined, 'top_level_parent': top_level_parent, + 'downstream_is_modified': downstream_is_modified, } if upstream_container_id: new_values['upstream_container_id'] = upstream_container_id diff --git a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py index 8eba50c829a9..e5cd063e8181 100644 --- a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py +++ b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py @@ -384,6 +384,7 @@ def test_unit_sync(self): 'updated': date_format, 'upstream_key': self.upstream_html1["id"], 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 2, @@ -400,7 +401,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_problem1["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 3, @@ -417,7 +419,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_problem2["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 1, @@ -434,7 +437,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_unit["id"], - 'upstream_type': 'container' + 'upstream_type': 'container', + 'downstream_is_modified': False, } ] data = downstreams.json() @@ -533,6 +537,7 @@ def test_unit_sync(self): 'updated': date_format, 'upstream_key': self.upstream_html1["id"], 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 2, @@ -549,7 +554,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_problem1["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 3, @@ -566,7 +572,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_problem2["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 1, @@ -583,7 +590,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_unit["id"], - 'upstream_type': 'container' + 'upstream_type': 'container', + 'downstream_is_modified': False, } ] data = downstreams.json() @@ -681,6 +689,7 @@ def test_unit_sync(self): 'updated': date_format, 'upstream_key': self.upstream_html1["id"], 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 2, @@ -697,7 +706,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_problem1["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 4, @@ -714,7 +724,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': upstream_problem3["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 1, @@ -731,7 +742,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_unit["id"], - 'upstream_type': 'container' + 'upstream_type': 'container', + 'downstream_is_modified': False, } ] data = downstreams.json() @@ -810,6 +822,7 @@ def test_unit_sync(self): 'updated': date_format, 'upstream_key': self.upstream_html1["id"], 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 2, @@ -826,7 +839,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_problem1["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 4, @@ -843,7 +857,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': upstream_problem3["id"], - 'upstream_type': 'component' + 'upstream_type': 'component', + 'downstream_is_modified': False, }, { 'id': 1, @@ -860,7 +875,8 @@ def test_unit_sync(self): 'created': date_format, 'updated': date_format, 'upstream_key': self.upstream_unit["id"], - 'upstream_type': 'container' + 'upstream_type': 'container', + 'downstream_is_modified': False, } ] data = downstreams.json() @@ -1047,6 +1063,7 @@ def test_modified_html_copy_paste(self): Test that we can sync a html from a library into a course. """ # 1️⃣ First, create the html in the course, using the upstream problem as a template: + date_format = self.now.isoformat().split("+")[0] + 'Z' downstream_html1 = self._create_block_from_upstream( block_category="html", parent_usage_key=str(self.course_subsection.usage_key), @@ -1079,6 +1096,34 @@ def test_modified_html_copy_paste(self): >This is the HTML. """) + # Check that: The downstream links are created as expected for the component + downstreams = self._get_downstream_links( + course_id=str(self.course.id) + ) + expected_downstreams = [ + { + 'id': 1, + 'upstream_context_title': self.library_title, + 'upstream_version': 2, + 'ready_to_sync': False, + 'ready_to_sync_from_children': False, + 'upstream_context_key': self.library_id, + 'downstream_usage_key': downstream_html1["locator"], + 'downstream_context_key': str(self.course.id), + 'top_level_parent_usage_key': None, + 'version_synced': 2, + 'version_declined': None, + 'created': date_format, + 'updated': date_format, + 'upstream_key': self.upstream_html1["id"], + 'upstream_type': 'component', + 'downstream_is_modified': False, + }, + ] + data = downstreams.json() + self.assertEqual(data["count"], 1) + self.assertListEqual(data["results"], expected_downstreams) + # 2️⃣ Now, lets modify the upstream html AND the downstream display_name: self._update_course_block_fields(downstream_html1["locator"], { "display_name": "New Text Content", @@ -1111,9 +1156,36 @@ def test_modified_html_copy_paste(self): 'version_declined': None, 'ready_to_sync': True, # <--- updated 'error_message': None, - 'is_modified': True, + 'is_modified': True, # <--- updated }) + downstreams = self._get_downstream_links( + course_id=str(self.course.id) + ) + expected_downstreams = [ + { + 'id': 1, + 'upstream_context_title': self.library_title, + 'upstream_version': 3, # <--- updated + 'ready_to_sync': True, # <--- updated + 'ready_to_sync_from_children': False, + 'upstream_context_key': self.library_id, + 'downstream_usage_key': downstream_html1["locator"], + 'downstream_context_key': str(self.course.id), + 'top_level_parent_usage_key': None, + 'version_synced': 2, + 'version_declined': None, + 'created': date_format, + 'updated': date_format, + 'upstream_key': self.upstream_html1["id"], + 'upstream_type': 'component', + 'downstream_is_modified': True, # <--- updated + }, + ] + data = downstreams.json() + self.assertEqual(data["count"], 1) + self.assertListEqual(data["results"], expected_downstreams) + # 3️⃣ Now, sync and check the resulting OLX of the downstream self._sync_downstream(downstream_html1["locator"]) diff --git a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstreams.py b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstreams.py index bab601e9c092..b8f2c8f41057 100644 --- a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstreams.py +++ b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstreams.py @@ -675,6 +675,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -692,6 +693,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -709,6 +711,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_unit.usage_key), + 'downstream_is_modified': False, }, { 'created': date_format, @@ -726,6 +729,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_chapter.usage_key), + 'downstream_is_modified': False, }, { 'created': date_format, @@ -743,6 +747,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -760,6 +765,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -777,6 +783,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -794,6 +801,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -811,6 +819,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -828,6 +837,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_chapter.usage_key), + 'downstream_is_modified': False, }, { 'created': date_format, @@ -845,6 +855,7 @@ def test_200_all_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_chapter.usage_key), + 'downstream_is_modified': False, }, ] self.assertListEqual(data["results"], expected) @@ -884,6 +895,7 @@ def test_200_component_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -901,6 +913,7 @@ def test_200_component_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -918,6 +931,7 @@ def test_200_component_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_unit.usage_key), + 'downstream_is_modified': False, }, { 'created': date_format, @@ -935,6 +949,7 @@ def test_200_component_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_chapter.usage_key), + 'downstream_is_modified': False, }, ] self.assertListEqual(data["results"], expected) @@ -969,6 +984,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -986,6 +1002,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1003,6 +1020,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1020,6 +1038,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1037,6 +1056,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1054,6 +1074,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_chapter.usage_key), + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1071,6 +1092,7 @@ def test_200_container_downstreams_for_a_course(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': str(self.top_level_downstream_chapter.usage_key), + 'downstream_is_modified': False, }, ] self.assertListEqual(data["results"], expected) @@ -1170,6 +1192,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_components(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1187,6 +1210,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_components(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1204,6 +1228,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_components(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1221,6 +1246,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_components(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, ] print(data["results"]) @@ -1267,6 +1293,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_containers(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1284,6 +1311,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_containers(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1301,6 +1329,7 @@ def test_200_get_ready_to_sync_top_level_parents_with_containers(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, ] self.assertListEqual(data["results"], expected) @@ -1354,6 +1383,7 @@ def test_200_get_ready_to_sync_duplicated_top_level_parents(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1371,6 +1401,7 @@ def test_200_get_ready_to_sync_duplicated_top_level_parents(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, { 'created': date_format, @@ -1388,6 +1419,7 @@ def test_200_get_ready_to_sync_duplicated_top_level_parents(self): 'version_declined': None, 'version_synced': 1, 'top_level_parent_usage_key': None, + 'downstream_is_modified': False, }, ] self.assertListEqual(data["results"], expected) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index c526111cf21f..6c30c1cf91aa 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -2413,6 +2413,7 @@ def _create_or_update_component_link(created: datetime | None, xblock): top_level_parent_usage_key=top_level_parent_usage_key, version_synced=xblock.upstream_version, version_declined=xblock.upstream_version_declined, + downstream_is_modified=len(getattr(xblock, "downstream_customized", [])) > 0, created=created, ) @@ -2445,6 +2446,7 @@ def _create_or_update_container_link(created: datetime | None, xblock): version_synced=xblock.upstream_version, top_level_parent_usage_key=top_level_parent_usage_key, version_declined=xblock.upstream_version_declined, + downstream_is_modified=len(getattr(xblock, "downstream_customized", [])) > 0, created=created, ) From 9848257ecd051e7dd31fa743fccb10e21fb02f96 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Fri, 26 Sep 2025 14:36:10 -0500 Subject: [PATCH 8/9] refactor: Show Display name in all student views --- common/templates/xblock_v2/xblock_iframe.html | 4 ++-- openedx/core/djangoapps/xblock/rest_api/views.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index 7aa32195110d..d9e1e7b4da5d 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -196,9 +196,9 @@ event listeners below, in certain situations. Resetting it to the default "auto" skirts the problem.-->
- {% if title %} + {% if view_name != 'studio_view' %}
- {{ title | safe }} + {{ display_name | safe }}
{% endif %} diff --git a/openedx/core/djangoapps/xblock/rest_api/views.py b/openedx/core/djangoapps/xblock/rest_api/views.py index ef3316bccfbe..f58be6d16254 100644 --- a/openedx/core/djangoapps/xblock/rest_api/views.py +++ b/openedx/core/djangoapps/xblock/rest_api/views.py @@ -100,10 +100,6 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): Unstable - may change after Sumac """ - show_title = request.GET.get('show_title') - title = '' - if show_title is not None: - show_title = BooleanField().to_internal_value(show_title) # Check if a specific version has been requested. TODO: move this to a URL path param like the other views? try: version = VersionConverter().to_python(request.GET.get("version")) @@ -115,8 +111,6 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): except NoSuchUsage as exc: raise NotFound(f"{usage_key} not found") from exc - if show_title: - title = block.display_name fragment = _render_block_view(block, view_name, request.user) handler_urls = { str(block.usage_key): _get_handler_url(block.usage_key, 'handler_name', request.user, version=version) @@ -154,7 +148,7 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): 'view_name': view_name, 'is_development': settings.DEBUG, 'oa_manifest': new_oa_manifest, - 'title': title, + 'display_name': block.display_name, } response = render(request, 'xblock_v2/xblock_iframe.html', context, content_type='text/html') From fa2ee3c11329d95626b4547ffa1861aeb49ae2f3 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 29 Sep 2025 11:11:57 -0500 Subject: [PATCH 9/9] refactor: Revert and use show_title again --- common/templates/xblock_v2/xblock_iframe.html | 2 +- openedx/core/djangoapps/xblock/rest_api/views.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index d9e1e7b4da5d..9bf0ab2df94f 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -196,7 +196,7 @@ event listeners below, in certain situations. Resetting it to the default "auto" skirts the problem.-->
- {% if view_name != 'studio_view' %} + {% if show_title %}
{{ display_name | safe }}
diff --git a/openedx/core/djangoapps/xblock/rest_api/views.py b/openedx/core/djangoapps/xblock/rest_api/views.py index f58be6d16254..4135fcbf6cdb 100644 --- a/openedx/core/djangoapps/xblock/rest_api/views.py +++ b/openedx/core/djangoapps/xblock/rest_api/views.py @@ -101,6 +101,10 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): Unstable - may change after Sumac """ # Check if a specific version has been requested. TODO: move this to a URL path param like the other views? + show_title = request.GET.get('show_title', False) + if show_title is not None: + show_title = BooleanField().to_internal_value(show_title) + try: version = VersionConverter().to_python(request.GET.get("version")) except ValueError as exc: @@ -149,6 +153,7 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str): 'is_development': settings.DEBUG, 'oa_manifest': new_oa_manifest, 'display_name': block.display_name, + 'show_title': show_title, } response = render(request, 'xblock_v2/xblock_iframe.html', context, content_type='text/html')