chore: remove legacy problem studio view (#37795)

* fix: remove legacy problem studio view and resource templates
This commit is contained in:
Irtaza Akram
2026-01-08 13:07:15 +05:00
committed by GitHub
parent 0b628353ef
commit 474dc71f0d
41 changed files with 12 additions and 4715 deletions

View File

@@ -1450,8 +1450,6 @@ class ContentStoreTest(ContentStoreTestCase):
problem_loc = UsageKey.from_string(payload['locator'])
problem = self.store.get_item(problem_loc)
self.assertIsInstance(problem, ProblemBlock, "New problem is not a ProblemBlock")
context = problem.get_context()
self.assertIn('markdown', context, "markdown is missing from context")
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields") # lint-amnesty, pylint: disable=line-too-long
def test_cms_imported_course_walkthrough(self):

View File

@@ -20,23 +20,9 @@ class TemplateTests(ModuleStoreTestCase):
self.assertIsNotNone(found.get('course'))
self.assertIsNotNone(found.get('about'))
self.assertIsNotNone(found.get('html'))
self.assertIsNotNone(found.get('problem'))
self.assertEqual(len(found.get('course')), 0)
self.assertEqual(len(found.get('about')), 1)
self.assertGreaterEqual(len(found.get('html')), 2)
self.assertGreaterEqual(len(found.get('problem')), 10)
dropdown = None
for template in found['problem']:
self.assertIn('metadata', template)
self.assertIn('display_name', template['metadata'])
if template['metadata']['display_name'] == 'Dropdown':
dropdown = template
break
self.assertIsNotNone(dropdown)
self.assertIn('markdown', dropdown['metadata'])
self.assertIn('data', dropdown)
self.assertRegex(dropdown['metadata']['markdown'], r'.*dropdown problems.*')
self.assertRegex(dropdown['data'], r'<problem>\s*<optionresponse>\s*<p>.*dropdown problems.*')
def test_get_some_templates(self):
course = CourseFactory.create()

View File

@@ -123,25 +123,6 @@ def use_video_gallery_flow():
return ENABLE_VIDEO_GALLERY_FLOW_FLAG.is_enabled()
# .. toggle_name: legacy_studio.problem_editor
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Temporarily fall back to the old Problem component (a.k.a. CAPA/problem block) editor.
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2025-03-14
# .. toggle_target_removal_date: 2025-09-14
# .. toggle_tickets: https://github.com/openedx/edx-platform/issues/36275
# .. toggle_warning: In Ulmo, this toggle will be removed. Only the new (React-based) experience will be available.
LEGACY_STUDIO_PROBLEM_EDITOR = CourseWaffleFlag('legacy_studio.problem_editor', __name__)
def use_new_problem_editor(course_key):
"""
Returns a boolean if new problem editor is enabled
"""
return not LEGACY_STUDIO_PROBLEM_EDITOR.is_enabled(course_key)
# .. toggle_name: contentstore.individualize_anonymous_user_id
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False

View File

@@ -33,7 +33,6 @@ from cms.djangoapps.contentstore.helpers import (
from cms.djangoapps.contentstore.toggles import (
libraries_v1_enabled,
libraries_v2_enabled,
use_new_problem_editor,
use_new_unit_page,
)
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import load_services_for_studio
@@ -368,16 +367,14 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint:
)
)
#If using new problem editor, we select problem type inside the editor
# because of this, we only show one problem.
if category == 'problem' and use_new_problem_editor(courselike.context_key):
if category == 'problem':
templates_for_category = [
template for template in templates_for_category if template['boilerplate_name'] == 'blank_common.yaml'
]
# Add any advanced problem types. Note that these are different xblocks being stored as Advanced Problems,
# currently not supported in libraries .
if category == 'problem' and not library and not use_new_problem_editor(courselike.context_key):
if category == 'problem' and not library:
disabled_block_names = [block.name for block in disabled_xblocks()]
advanced_problem_types = [advanced_problem_type for advanced_problem_type in ADVANCED_PROBLEM_TYPES
if advanced_problem_type['component'] not in disabled_block_names]

View File

@@ -13,7 +13,6 @@ from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_flag
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
from openedx_events.content_authoring.data import DuplicatedXBlockData
from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED
@@ -23,7 +22,6 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.asides import AsideUsageKeyV2
from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from pyquery import PyQuery
from pytz import UTC
from bs4 import BeautifulSoup
from web_fragments.fragment import Fragment
@@ -57,7 +55,6 @@ from xmodule.partitions.partitions import (
from xmodule.partitions.tests.test_partitions import MockPartitionService
from xmodule.x_module import STUDENT_VIEW, STUDIO_VIEW
from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.utils import (
reverse_course_url,
@@ -229,7 +226,6 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=child_vertical_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.assertEqual(resp.status_code, 200)
@@ -258,7 +254,6 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=wrapper_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.assertEqual(resp.status_code, 200)
@@ -286,7 +281,6 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=child_vertical_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.assertEqual(resp.status_code, 200)
usage_key = self.response_usage_key(resp)
@@ -311,18 +305,13 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=split_test_usage_key,
category="html",
boilerplate="announcement.yaml",
)
self.assertEqual(resp.status_code, 200)
resp = self.create_xblock(
parent_usage_key=split_test_usage_key,
category="html",
boilerplate="latex_html.yaml",
)
self.assertEqual(resp.status_code, 200)
html, __ = self._get_container_preview(split_test_usage_key)
self.assertIn("Announcement", html)
self.assertIn("LaTeX", html)
def test_split_test_edited(self):
"""
@@ -608,33 +597,12 @@ class TestCreateItem(ItemTest):
course = self.get_item_from_modulestore(self.usage_key)
self.assertIn(chap_usage_key, course.children)
# use default display name
resp = self.create_xblock(parent_usage_key=chap_usage_key, category="vertical")
vert_usage_key = self.response_usage_key(resp)
# create problem w/ boilerplate
template_id = "multiplechoice.yaml"
resp = self.create_xblock(
parent_usage_key=vert_usage_key, category="problem", boilerplate=template_id
)
prob_usage_key = self.response_usage_key(resp)
problem = self.get_item_from_modulestore(prob_usage_key)
# check against the template
course = CourseFactory.create()
problem_block = BlockFactory.create(category="problem", parent_location=course.location)
template = problem_block.get_template(template_id)
self.assertEqual(problem.data, template["data"])
self.assertEqual(problem.display_name, template["metadata"]["display_name"])
self.assertEqual(problem.markdown, template["metadata"]["markdown"])
def test_create_block_negative(self):
"""
Negative tests for create_item
"""
# non-existent boilerplate: creates a default
resp = self.create_xblock(
category="problem", boilerplate="nosuchboilerplate.yaml"
)
resp = self.create_xblock(category="problem")
self.assertEqual(resp.status_code, 200)
def test_create_with_future_date(self):
@@ -836,7 +804,6 @@ class TestDuplicateItem(ItemTest, DuplicateHelper, OpenEdxEventsTestMixin):
resp = self.create_xblock(
parent_usage_key=self.vert_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.problem_usage_key = self.response_usage_key(resp)
@@ -936,19 +903,6 @@ class TestDuplicateItem(ItemTest, DuplicateHelper, OpenEdxEventsTestMixin):
self.assertEqual(duplicated_item.display_name, expected_name)
return usage_key
# Display name comes from template.
dupe_usage_key = verify_name(
self.problem_usage_key,
self.vert_usage_key,
"Duplicate of 'Multiple Choice'",
)
# Test dupe of dupe.
verify_name(
dupe_usage_key,
self.vert_usage_key,
"Duplicate of 'Duplicate of 'Multiple Choice''",
)
# Uses default display_name of 'Text' from HTML component.
verify_name(self.html_usage_key, self.vert_usage_key, "Duplicate of 'Text'")
@@ -1847,7 +1801,6 @@ class TestDuplicateItemWithAsides(ItemTest, DuplicateHelper):
resp = self.create_xblock(
parent_usage_key=self.seq_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.problem_usage_key = self.response_usage_key(resp)
@@ -1930,11 +1883,9 @@ class TestEditItemSetup(ItemTest):
self.seq2_update_url = reverse_usage_url("xblock_handler", self.seq2_usage_key)
# create problem w/ boilerplate
template_id = "multiplechoice.yaml"
resp = self.create_xblock(
parent_usage_key=self.seq_usage_key,
category="problem",
boilerplate=template_id,
)
self.problem_usage_key = self.response_usage_key(resp)
self.problem_update_url = reverse_usage_url(
@@ -1965,19 +1916,6 @@ class TestEditItem(TestEditItemSetup):
problem = self.get_item_from_modulestore(self.problem_usage_key)
self.assertEqual(problem.rerandomize, 'never')
def test_null_field(self):
"""
Sending null in for a field 'deletes' it
"""
problem = self.get_item_from_modulestore(self.problem_usage_key)
self.assertIsNotNone(problem.markdown)
self.client.ajax_post(
self.problem_update_url,
data={'nullout': ['markdown']}
)
problem = self.get_item_from_modulestore(self.problem_usage_key)
self.assertIsNone(problem.markdown)
def test_date_fields(self):
"""
Test setting due & start dates on sequential
@@ -2427,28 +2365,6 @@ class TestEditItem(TestEditItemSetup):
) # See xmodule/fields.py
class TestEditItemSplitMongo(TestEditItemSetup):
"""
Tests for EditItem running on top of the SplitMongoModuleStore.
"""
def test_editing_view_wrappers(self):
"""
Verify that the editing view only generates a single wrapper, no matter how many times it's loaded
Exposes: PLAT-417
"""
view_url = reverse_usage_url(
"xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW}
)
for __ in range(3):
resp = self.client.get(view_url, HTTP_ACCEPT="application/json")
self.assertEqual(resp.status_code, 200)
content = json.loads(resp.content.decode("utf-8"))
self.assertEqual(len(PyQuery(content["html"])(f".xblock-{STUDIO_VIEW}")), 1)
class TestEditSplitModule(ItemTest):
"""
Tests around editing instances of the split_test block.
@@ -2864,7 +2780,6 @@ class TestComponentHandler(TestCase):
assert mocked_get_aside_from_xblock.called is is_get_aside_called
@override_waffle_flag(toggles.LEGACY_STUDIO_PROBLEM_EDITOR, True)
class TestComponentTemplates(CourseTestCase):
"""
Unit tests for the generation of the component templates for a course.
@@ -3012,12 +2927,6 @@ class TestComponentTemplates(CourseTestCase):
self.course.allow_unsupported_xblocks = True
self.templates = get_component_templates(self.course)
self._verify_basic_component("video", "Video", "us")
problem_templates = self.get_templates_of_type("problem")
problem_no_boilerplate = self.get_template(
problem_templates, "Blank Problem"
)
self.assertIsNotNone(problem_no_boilerplate)
self.assertEqual("us", problem_no_boilerplate["support_level"])
# Now fully disable video through XBlockConfiguration
XBlockConfiguration.objects.create(name="video", enabled=False)
@@ -3061,20 +2970,6 @@ class TestComponentTemplates(CourseTestCase):
self.templates = get_component_templates(self.course)
self.assertTrue((not any(item.get("category") == "done" for item in self.get_templates_of_type("advanced"))))
def test_advanced_problems(self):
"""
Test the handling of advanced problem templates.
"""
problem_templates = self.get_templates_of_type("problem")
circuit_template = self.get_template(
problem_templates, "Circuit Schematic Builder"
)
self.assertIsNotNone(circuit_template)
self.assertEqual(circuit_template.get("category"), "problem")
self.assertEqual(
circuit_template.get("boilerplate_name"), "circuitschematic.yaml"
)
def test_deprecated_no_advance_component_button(self):
"""
Test that there will be no `Advanced` button on unit page if xblocks have disabled

View File

@@ -4,41 +4,9 @@ define(['js/models/component_template'],
var mockTemplateJSON = {
templates: [
{
category: 'problem',
boilerplate_name: 'formularesponse.yaml',
display_name: 'Math Expression Input'
}, {
category: 'problem',
boilerplate_name: null,
display_name: 'Blank Advanced Problem'
}, {
category: 'problem',
boilerplate_name: 'checkboxes.yaml',
display_name: 'Checkboxes'
}, {
category: 'problem',
boilerplate_name: 'multiple_choice.yaml',
display_name: 'Multiple Choice'
}, {
category: 'problem',
boilerplate_name: 'drag_and_drop.yaml',
display_name: 'Drag and Drop'
}, {
category: 'problem',
boilerplate_name: 'problem_with_hint.yaml',
display_name: 'Problem with Adaptive Hint'
}, {
category: 'problem',
boilerplate_name: 'imageresponse.yaml',
display_name: 'Image Mapped Input'
}, {
category: 'openassessment',
boilerplate_name: null,
display_name: 'Peer Assessment'
}, {
category: 'problem',
boilerplate_name: 'an_easy_problem.yaml',
display_name: 'An Easy Problem'
}, {
category: 'word_cloud',
boilerplate_name: null,
@@ -69,7 +37,7 @@ define(['js/models/component_template'],
} else {
// If the first template is blank, make sure that it has the correct category
if (!template.boilerplate_name) {
expect(template.category).toBe('problem');
expect(template.category).toBe('openassessment');
}
lastTemplate = template;
}

View File

@@ -507,12 +507,11 @@ function($, _, Backbone, gettext, BasePage,
const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions');
var useNewVideoEditor = primaryHeader.attr('use-new-editor-video'),
useNewProblemEditor = primaryHeader.attr('use-new-editor-problem'),
blockType = primaryHeader.attr('data-block-type');
if((blockType === 'html')
|| (useNewVideoEditor === 'True' && blockType === 'video')
|| (useNewProblemEditor === 'True' && blockType === 'problem')
|| (blockType === 'problem')
) {
var destinationUrl = primaryHeader.attr('authoring_MFE_base_url')
+ '/' + blockType
@@ -1172,8 +1171,7 @@ function($, _, Backbone, gettext, BasePage,
onNewXBlock: function(xblockElement, scrollOffset, is_duplicate, data) {
var useNewVideoEditor = this.$('.xblock-header-primary').attr('use-new-editor-video'),
useVideoGalleryFlow = this.$('.xblock-header-primary').attr("use-video-gallery-flow"),
useNewProblemEditor = this.$('.xblock-header-primary').attr('use-new-editor-problem');
useVideoGalleryFlow = this.$('.xblock-header-primary').attr("use-video-gallery-flow");
// find the block type in the locator if availible
if(data.hasOwnProperty('locator')) {
@@ -1183,7 +1181,7 @@ function($, _, Backbone, gettext, BasePage,
// open mfe editors for new blocks only and not for content imported from libraries
if(!data.hasOwnProperty('upstreamRef') && (blockType.includes('html')
|| (useNewVideoEditor === 'True' && blockType.includes('video'))
|| (useNewProblemEditor === 'True' && blockType.includes('problem')))
|| (blockType.includes('problem')))
){
if (this.options.isIframeEmbed && (this.isSplitTestContentPage || this.isVerticalContentPage)) {
return this.postMessageToParent({

View File

@@ -13,7 +13,7 @@ from django.urls import reverse
from django.utils.translation import gettext as _
from cms.djangoapps.contentstore.helpers import xblock_studio_url, xblock_type_display_name
from cms.djangoapps.contentstore.toggles import use_new_problem_editor, use_new_video_editor, use_video_gallery_flow
from cms.djangoapps.contentstore.toggles import use_new_video_editor, use_video_gallery_flow
from cms.djangoapps.contentstore.utils import get_editor_page_base_url
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
@@ -112,7 +112,6 @@ from openedx.core.djangolib.markup import HTML, Text
<%
use_new_editor_video = use_new_video_editor(xblock_locator.course_key)
use_new_editor_problem = use_new_problem_editor(xblock_locator.course_key)
use_new_video_gallery_flow = use_video_gallery_flow()
%>
@@ -168,7 +167,6 @@ use_new_video_gallery_flow = use_video_gallery_flow()
<nav class="nav-actions" aria-label="${_('Page Actions')}"
use-new-editor-video = ${use_new_editor_video}
use-new-editor-problem = ${use_new_editor_problem}
use-video-gallery-flow = ${use_new_video_gallery_flow}
authoring_MFE_base_url = ${get_editor_page_base_url(xblock_locator.course_key)}
data-block-type = ${xblock.scope_ids.block_type}

View File

@@ -8,13 +8,12 @@ from lms.lib.utils import is_unit
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
from cms.djangoapps.contentstore.toggles import use_new_problem_editor, use_new_video_editor, use_video_gallery_flow
from cms.djangoapps.contentstore.toggles import use_new_video_editor, use_video_gallery_flow
from cms.lib.xblock.upstream_sync import UpstreamLink
from openedx.core.djangoapps.content_tagging.toggles import is_tagging_feature_disabled
%>
<%
use_new_editor_video = use_new_video_editor(xblock.context_key)
use_new_editor_problem = use_new_problem_editor(xblock.context_key)
use_new_video_gallery_flow = use_video_gallery_flow()
use_tagging = not is_tagging_feature_disabled()
xblock_url = xblock_studio_url(xblock)
@@ -84,7 +83,6 @@ can_unlink = upstream_info.upstream_ref and not upstream_info.has_top_level_pare
% endif
"
use-new-editor-video = ${use_new_editor_video}
use-new-editor-problem = ${use_new_editor_problem}
use-video-gallery-flow = ${use_new_video_gallery_flow}
authoring_MFE_base_url = ${get_editor_page_base_url(xblock.location.course_key)}
data-block-type = ${xblock.scope_ids.block_type}

View File

@@ -1,174 +0,0 @@
<%! from django.utils.translation import gettext as _ %>
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/>
<% isLaTexProblem='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] and enable_latex_compiler %>
% if isLaTexProblem:
<div class="wrapper-comp-editor latex-problem" id="editor-tab">
% else:
<div class="wrapper-comp-editor" id="editor-tab">
% endif
<section class="problem-editor editor">
<div class="row">
%if enable_markdown:
<div class="editor-bar">
<ul class="format-buttons">
<li>
<button type="button" class="header-button" data-tooltip="${_("Heading")}">
<span class="problem-editor-icon heading3">
<img class="icon" src="${static.url('images/cms-editor_heading.png')}" alt="${_("Insert a heading")}">
</span>
${_("Heading")}
</button>
</li>
<li>
<button type="button" class="multiple-choice-button" data-tooltip="${_("Multiple Choice")}">
<span class="problem-editor-icon multiple-choice">
<img class="icon" src="${static.url('images/cms-editor_radio.png')}" alt="${_("Add a multiple choice question")}">
</span>
${_("Multiple Choice")}
</button>
</li>
<li>
<button type="button" class="checks-button" data-tooltip="${_("Checkboxes")}">
<span class="problem-editor-icon checks">
<img class="icon" src="${static.url('images/cms-editor_checkbox.png')}" alt="${_("Add a question with checkboxes")}">
</span>
${_("Checkboxes")}
</button>
</li>
<li>
<button type="button" class="string-button" data-tooltip="${_("Text Input")}">
<span class="problem-editor-icon string">
<img class="icon" src="${static.url('images/cms-editor_text.png')}" alt="${_("Insert a text response")}">
</span>
${_("Text Input")}
</button>
</li>
<li>
<button type="button" class="number-button" data-tooltip="${_("Numerical Input")}">
<span class="problem-editor-icon number">
<img class="icon" src="${static.url('images/cms-editor_number.png')}" alt="${_("Insert a numerical response")}">
</span>
${_("Numerical Input")}
</button>
</li>
<li>
<button type="button" class="dropdown-button" data-tooltip="${_("Dropdown")}">
<span class="problem-editor-icon dropdown">
<img class="icon" src="${static.url('images/cms-editor_dropdown.png')}" alt="${_("Insert a dropdown response")}">
</span>
${_("Dropdown")}
</button>
</li>
<li>
<button type="button" class="explanation-button" data-tooltip="${_("Explanation")}">
<span class="problem-editor-icon explanation">
<img class="icon" src="${static.url('images/cms-editor_explanation.png')}" alt="${_("Add an explanation for this question")}">
</span>
${_("Explanation")}
</button>
</li>
</ul>
<ul class="editor-tabs">
<li><button type="button" class="xml-tab advanced-toggle" data-tab="xml">${_("Advanced Editor")}</button></li>
</ul>
</div>
<textarea class="markdown-box">${markdown}</textarea>
<article class="simple-editor-cheatsheet shown">
<div class="cheatsheet-wrapper">
<div class="row">
<h5>${_("Markdown Help")}</h5>
</div>
<div class="row">
<div class="col sample heading-1">
<img class="icon" src="${static.url('images/cms-editor_heading.png')}" alt="${_("Insert a heading")}">
<h6>${_("Heading")}</h6>
</div>
<div class="col">
<pre><code>H3
=====
</code></pre>
</div>
</div>
<div class="row">
<div class="col sample multiple-choice">
<img class="icon" src="${static.url('images/cms-editor_radio.png')}" alt="${_("Add a multiple choice question")}">
<h6>${_("Multiple Choice")}</h6>
</div>
<div class="col">
<pre><code>( ) red
( ) green
(x) blue</code></pre>
</div>
</div>
<div class="row">
<div class="col sample check-multiple">
<img class="icon" src="${static.url('images/cms-editor_checkbox.png')}" alt="${_("Add a question with checkboxes")}">
<h6>${_("Checkboxes")}</h6>
</div>
<div class="col">
<pre><code>[x] earth
[ ] wind
[x] water</code></pre>
</div>
</div>
<div class="row">
<div class="col sample string-response">
<img class="icon" src="${static.url('images/cms-editor_text.png')}" alt="${_("Insert a text response")}">
<h6>${_("Text Input")}</h6>
</div>
<div class="col">
<pre><code>= dog
or= cat
or= mouse</code></pre>
</div>
</div>
<div class="row">
<div class="col sample numerical-response">
<img class="icon" src="${static.url('images/cms-editor_number.png')}" alt="${_("Insert a numerical response")}">
<h6>${_("Numerical Input")}</h6>
</div>
<div class="col">
<pre><code>= 3.14 +- 2%</code></pre>
<pre><code>= [3.14, 3.15)</code></pre>
</div>
</div>
<div class="row">
<div class="col sample option-reponse">
<img class="icon" src="${static.url('images/cms-editor_dropdown.png')}" alt="${_("Insert a dropdown response")}">
<h6>${_("Dropdown")}</h6>
</div>
<div class="col">
<pre><code>[[wrong, (right)]]</code></pre>
</div>
</div>
<div class="row">
<h6>${_("Label")}</h6>
<div class="col">
<pre><code>&gt;&gt;What is the capital of Argentina?&lt;&lt;</code></pre>
</div>
</div>
<div class="row">
<div class="col sample explanation">
<img class="icon" src="${static.url('images/cms-editor_explanation.png')}" alt="${_("Add an explanation for this question")}">
<h6>${_("Explanation")}</h6>
</div>
<div class="col">
<pre><code>[explanation] A short explanation of the answer. [explanation]</code></pre>
</div>
</div>
</div>
</article>
</div>
%endif
</div>
<textarea class="xml-box" rows="8" cols="40">${data}</textarea>
</section>
<script type="text/template" id="simple-editor-cheatsheet">
</script>
</div>
<%include file="metadata-edit.html" />