-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: expand support for versioned URLs in v2 XBlock runtime (#35676)
* fix: problem block could not be used with versioned handler URls * refactor: simplify REST API handling of usage keys * feat: add more version awareness and update tests * fix: make the preview changes modal bigger as requested * refactor: parse version at the urlconf layer too
- Loading branch information
1 parent
9e14566
commit e2d6765
Showing
11 changed files
with
261 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
openedx/core/djangoapps/content_libraries/tests/test_versioned_apis.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
""" | ||
Tests that several XBlock APIs support versioning | ||
""" | ||
from django.test.utils import override_settings | ||
from openedx_events.tests.utils import OpenEdxEventsTestMixin | ||
from xblock.core import XBlock | ||
|
||
from openedx.core.djangoapps.content_libraries.tests.base import ( | ||
ContentLibrariesRestApiTest | ||
) | ||
from openedx.core.djangolib.testing.utils import skip_unless_cms | ||
from .fields_test_block import FieldsTestBlock | ||
|
||
|
||
@skip_unless_cms | ||
@override_settings(CORS_ORIGIN_WHITELIST=[]) # For some reason, this setting isn't defined in our test environment? | ||
class VersionedXBlockApisTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestMixin): | ||
""" | ||
Tests for three APIs implemented by djangoapps.xblock, and used by content | ||
libraries. These tests focus on versioning. | ||
Note the metadata endpoint is different than the similar "metadata" endpoint | ||
within the content libraries API, which returns a lot more information. This | ||
endpoint pretty much only returns the display name of a block, but it does | ||
allow retrieving past versions. | ||
""" | ||
|
||
@XBlock.register_temp_plugin(FieldsTestBlock, FieldsTestBlock.BLOCK_TYPE) | ||
def test_versioned_metadata(self): | ||
""" | ||
Test that metadata endpoint can get different versions of the block's metadata | ||
""" | ||
# Create a library: | ||
lib = self._create_library(slug="test-eb-1", title="Test Library", description="") | ||
lib_id = lib["id"] | ||
# Create an XBlock. This will be the empty version 1: | ||
create_response = self._add_block_to_library(lib_id, FieldsTestBlock.BLOCK_TYPE, "block1") | ||
block_id = create_response["id"] | ||
# Create version 2 of the block by setting its OLX: | ||
olx_response = self._set_library_block_olx(block_id, """ | ||
<fields-test | ||
display_name="Field Test Block (Old, v2)" | ||
setting_field="Old setting value 2." | ||
content_field="Old content value 2." | ||
/> | ||
""") | ||
assert olx_response["version_num"] == 2 | ||
# Create version 3 of the block by setting its OLX again: | ||
olx_response = self._set_library_block_olx(block_id, """ | ||
<fields-test | ||
display_name="Field Test Block (Published, v3)" | ||
setting_field="Published setting value 3." | ||
content_field="Published content value 3." | ||
/> | ||
""") | ||
assert olx_response["version_num"] == 3 | ||
# Publish the library: | ||
self._commit_library_changes(lib_id) | ||
|
||
# Create the draft (version 4) of the block: | ||
olx_response = self._set_library_block_olx(block_id, """ | ||
<fields-test | ||
display_name="Field Test Block (Draft, v4)" | ||
setting_field="Draft setting value 4." | ||
content_field="Draft content value 4." | ||
/> | ||
""") | ||
|
||
def check_results(version, display_name, settings_field, content_field): | ||
meta = self._get_basic_xblock_metadata(block_id, version=version) | ||
assert meta["block_id"] == block_id | ||
assert meta["block_type"] == FieldsTestBlock.BLOCK_TYPE | ||
assert meta["display_name"] == display_name | ||
fields = self._get_library_block_fields(block_id, version=version) | ||
assert fields["display_name"] == display_name | ||
assert fields["metadata"]["setting_field"] == settings_field | ||
rendered = self._render_block_view(block_id, "student_view", version=version) | ||
assert rendered["block_id"] == block_id | ||
assert f"SF: {settings_field}" in rendered["content"] | ||
assert f"CF: {content_field}" in rendered["content"] | ||
|
||
# Now get the metadata. If we don't specify a version, it should be the latest draft (in Studio): | ||
check_results( | ||
version=None, | ||
display_name="Field Test Block (Draft, v4)", | ||
settings_field="Draft setting value 4.", | ||
content_field="Draft content value 4.", | ||
) | ||
|
||
# Get the published version: | ||
check_results( | ||
version="published", | ||
display_name="Field Test Block (Published, v3)", | ||
settings_field="Published setting value 3.", | ||
content_field="Published content value 3.", | ||
) | ||
|
||
# Get a specific version: | ||
check_results( | ||
version="2", | ||
display_name="Field Test Block (Old, v2)", | ||
settings_field="Old setting value 2.", | ||
content_field="Old content value 2.", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
""" | ||
URL pattern converters | ||
https://docs.djangoproject.com/en/5.1/topics/http/urls/#registering-custom-path-converters | ||
""" | ||
from opaque_keys import InvalidKeyError | ||
from opaque_keys.edx.keys import UsageKeyV2 | ||
|
||
from ..api import LatestVersion | ||
|
||
|
||
class UsageKeyV2Converter: | ||
""" | ||
Converter that matches V2 usage keys like: | ||
lb:Org:LIB:drag-and-drop-v2:91c2b1d5 | ||
""" | ||
regex = r'[\w-]+(:[\w\-.]+)+' | ||
|
||
def to_python(self, value: str) -> UsageKeyV2: | ||
try: | ||
return UsageKeyV2.from_string(value) | ||
except InvalidKeyError as exc: | ||
raise ValueError from exc | ||
|
||
def to_url(self, value: UsageKeyV2) -> str: | ||
return str(value) | ||
|
||
|
||
class VersionConverter: | ||
""" | ||
Converter that matches a version string like "draft", "published", or a | ||
number, and converts it to either an 'int' or a LatestVersion enum value. | ||
""" | ||
regex = r'(draft|published|\d+)' | ||
|
||
def to_python(self, value: str | None) -> LatestVersion | int: | ||
""" Convert from string to LatestVersion or integer version spec """ | ||
if value is None: | ||
return LatestVersion.AUTO # AUTO = published if we're in the LMS, draft if we're in Studio. | ||
if value == "draft": | ||
return LatestVersion.DRAFT | ||
if value == "published": | ||
return LatestVersion.PUBLISHED | ||
return int(value) # May raise ValueError, which Django will convert to 404 | ||
|
||
def to_url(self, value: LatestVersion | int | None) -> str: | ||
""" | ||
Convert from LatestVersion or integer version spec to URL path string. | ||
Note that if you provide any value at all, django won't be able to | ||
match the paths that don't have a version in the URL, so if you want | ||
LatestVersion.AUTO, don't pass any value for 'version' to reverse(...). | ||
""" | ||
if value is None or value == LatestVersion.AUTO: | ||
raise ValueError # This default value does not need to be in the URL | ||
if isinstance(value, int): | ||
return str(value) | ||
return value.value # LatestVersion.DRAFT or PUBLISHED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.