-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Fix versioned URLs for XBlock handlers in v2 runtime [FC-0062] #35676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bradenmacdonald
merged 5 commits into
openedx:master
from
open-craft:braden/fix-handler-versioned-urls
Oct 21, 2024
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
dc8fd74
fix: problem block could not be used with versioned handler URls
bradenmacdonald ea7b049
refactor: simplify REST API handling of usage keys
bradenmacdonald ec63d4a
feat: add more version awareness and update tests
bradenmacdonald 72950a9
fix: make the preview changes modal bigger as requested
bradenmacdonald a850821
refactor: parse version at the urlconf layer too
bradenmacdonald File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that you're making one of these–I think it really helps clean things up. |
||
| """ | ||
| 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I moved the usage key parsing into Django's URL router, we still get a 404 response here but it's not a customized one for this API anymore.