Skip to content

Commit 328b3ee

Browse files
fix: remove the branch/version while building BS (#37866)
This commit updates the logic in the build_block_structure function to ensure that block locations are consistently normalized by removing branch and version information. This change addresses issues when creating a BlockStructure from modulestore using the published_only branch. Without this change, we end up comparing versioned keys to unversioned ones later on, which always yields False.
1 parent 3430dbd commit 328b3ee

2 files changed

Lines changed: 91 additions & 7 deletions

File tree

openedx/core/djangoapps/content/block_structure/factory.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def create_from_modulestore(cls, root_block_usage_key, modulestore):
3131
xmodule.modulestore.exceptions.ItemNotFoundError if a block for
3232
root_block_usage_key is not found in the modulestore.
3333
"""
34-
block_structure = BlockStructureModulestoreData(root_block_usage_key)
34+
root_xblock = modulestore.get_item(root_block_usage_key, depth=None, lazy=False)
35+
block_structure = BlockStructureModulestoreData(root_block_usage_key.for_branch(None))
3536
blocks_visited = set()
3637

3738
def build_block_structure(xblock):
@@ -41,19 +42,26 @@ def build_block_structure(xblock):
4142
"""
4243
# Check if the xblock was already visited (can happen in
4344
# DAGs).
44-
if xblock.location in blocks_visited:
45+
# Normalize location to remove branch/version information
46+
# When create_from_modulestore is wrapped in published_only branch decorator,
47+
# "xblock being changed" location contains branch and version info which causes
48+
# mismatch when removing inaccessible blocks in
49+
# CourseNavigationBlocksView.filter_inaccessible_blocks
50+
# while fetching course navigation.
51+
location = xblock.location.for_branch(None)
52+
if location in blocks_visited:
4553
return
4654

4755
# Add the xBlock.
48-
blocks_visited.add(xblock.location)
49-
block_structure._add_xblock(xblock.location, xblock) # pylint: disable=protected-access
56+
blocks_visited.add(location)
57+
block_structure._add_xblock(location, xblock) # pylint: disable=protected-access
5058

5159
# Add relations with its children and recurse.
5260
for child in xblock.get_children():
53-
block_structure._add_relation(xblock.location, child.location) # pylint: disable=protected-access
61+
child_location = child.location.for_branch(None)
62+
block_structure._add_relation(location, child_location) # pylint: disable=protected-access
5463
build_block_structure(child)
5564

56-
root_xblock = modulestore.get_item(root_block_usage_key, depth=None, lazy=False)
5765
build_block_structure(root_xblock)
5866
return block_structure
5967

openedx/core/djangoapps/content/block_structure/tests/test_factory.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from django.test import TestCase
77

88
from opaque_keys.edx.keys import CourseKey
9+
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
910
from xmodule.modulestore.exceptions import ItemNotFoundError
1011

1112
from ..exceptions import BlockStructureNotFound
1213
from ..factory import BlockStructureFactory
1314
from ..store import BlockStructureStore
14-
from .helpers import ChildrenMapTestMixin, MockCache, MockModulestoreFactory
15+
from .helpers import ChildrenMapTestMixin, MockCache, MockModulestoreFactory, MockXBlock, MockModulestore
1516

1617

1718
class TestBlockStructureFactory(TestCase, ChildrenMapTestMixin):
@@ -77,3 +78,78 @@ def test_new(self):
7778
block_structure._block_data_map, # pylint: disable=protected-access
7879
)
7980
self.assert_block_structure(new_structure, self.children_map)
81+
82+
def test_from_modulestore_normalizes_locations_with_branch_info(self):
83+
"""
84+
Test that locations with branch/version information are normalized
85+
when building block structures.
86+
87+
This test verifies the fix for PR #37866, which ensures that when
88+
creating block structures within the published_only branch context,
89+
locations are normalized by removing branch/version information.
90+
This prevents comparison mismatches when filtering inaccessible blocks.
91+
92+
Without the fix, locations with branch info would be stored as-is,
93+
causing issues when comparing with normalized locations later.
94+
"""
95+
# Create a course key with branch information to simulate
96+
# the published_only branch context
97+
course_key_with_branch = CourseLocator('org', 'course', 'run', branch='published')
98+
root_usage_key = BlockUsageLocator(
99+
course_key=course_key_with_branch,
100+
block_type='html',
101+
block_id='0'
102+
)
103+
104+
# Create a modulestore with xblocks that have locations containing branch info
105+
modulestore = MockModulestore()
106+
blocks = {}
107+
children_map = self.SIMPLE_CHILDREN_MAP
108+
109+
# Create blocks with branch information in their locations
110+
for block_id, children in enumerate(children_map):
111+
# Create location with branch info
112+
block_location = BlockUsageLocator(
113+
course_key=course_key_with_branch,
114+
block_type='html',
115+
block_id=str(block_id)
116+
)
117+
# Create child locations with branch info
118+
child_locations = [
119+
BlockUsageLocator(
120+
course_key=course_key_with_branch,
121+
block_type='html',
122+
block_id=str(child_id)
123+
)
124+
for child_id in children
125+
]
126+
blocks[block_location] = MockXBlock(
127+
location=block_location,
128+
children=child_locations,
129+
modulestore=modulestore
130+
)
131+
modulestore.set_blocks(blocks)
132+
133+
# Build block structure from modulestore
134+
block_structure = BlockStructureFactory.create_from_modulestore(
135+
root_block_usage_key=root_usage_key,
136+
modulestore=modulestore
137+
)
138+
139+
# Verify that all stored block keys are normalized (without branch info)
140+
# This is the key assertion: with the fix, all keys should be normalized
141+
for block_key in block_structure:
142+
# The block_key should equal its normalized version
143+
normalized_key = block_key.for_branch(None)
144+
self.assertEqual(
145+
block_key,
146+
normalized_key,
147+
f"Block key {block_key} should be normalized (without branch info). "
148+
f"Normalized version: {normalized_key}"
149+
)
150+
# Verify it doesn't have branch information in the course_key
151+
if hasattr(block_key.course_key, 'branch'):
152+
self.assertIsNone(
153+
block_key.course_key.branch,
154+
f"Block key {block_key} should not have branch information"
155+
)

0 commit comments

Comments
 (0)