Skip to content
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

feat: [FC-0044] Course unit - Edit iframe modal window #35777

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion cms/djangoapps/contentstore/views/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
from django.db import transaction
from django.http import Http404, HttpResponse
from django.utils.translation import gettext as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.http import require_http_methods
from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment

from cms.djangoapps.contentstore.utils import load_services_for_studio
from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW
from common.djangoapps.edxmako.shortcuts import render_to_string
from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string
from common.djangoapps.student.auth import (
has_studio_read_access,
has_studio_write_access,
Expand Down Expand Up @@ -44,6 +45,8 @@
is_unit,
)
from .preview import get_preview_fragment
from .component import _get_item_in_course
from ..utils import get_container_handler_context

from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import (
handle_xblock,
Expand Down Expand Up @@ -302,6 +305,39 @@ def xblock_view_handler(request, usage_key_string, view_name):
return HttpResponse(status=406)


@xframe_options_exempt
@require_http_methods(["GET"])
@login_required
def xblock_edit_view(request, usage_key_string):
"""
Return rendered xblock edit view.

Allows editing of an XBlock specified by the usage key.
"""
usage_key = usage_key_with_run(usage_key_string)
if not has_studio_read_access(request.user, usage_key.course_key):
raise PermissionDenied()

store = modulestore()

with store.bulk_operations(usage_key.course_key):
course, xblock, _, __ = _get_item_in_course(request, usage_key)
container_handler_context = get_container_handler_context(request, usage_key, course, xblock)

fragment = get_preview_fragment(request, xblock, {})

hashed_resources = {
hash_resource(resource): resource._asdict() for resource in fragment.resources
}

container_handler_context.update({
"action_name": "edit",
"resources": list(hashed_resources.items()),
})

return render_to_response('container_editor.html', container_handler_context)


@require_http_methods("GET")
@login_required
@expect_json
Expand Down
59 changes: 59 additions & 0 deletions cms/djangoapps/contentstore/views/tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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
from webob import Response
from xblock.core import XBlockAside
Expand Down Expand Up @@ -4538,3 +4539,61 @@ def test_update_clobbers(self):
user_id=user.id,
)
self.check_updated(source_block, destination_block.location)


class TestXblockEditView(CourseTestCase):
"""
Test xblock_edit_view.
"""

def setUp(self):
super().setUp()
self.chapter = self._create_block(self.course, "chapter", "Week 1")
self.sequential = self._create_block(self.chapter, "sequential", "Lesson 1")
self.vertical = self._create_block(self.sequential, "vertical", "Unit")
self.html = self._create_block(self.vertical, "html", "HTML")
self.child_container = self._create_block(
self.vertical, "split_test", "Split Test"
)
self.child_vertical = self._create_block(
self.child_container, "vertical", "Child Vertical"
)
self.video = self._create_block(self.child_vertical, "video", "My Video")
self.store = modulestore()

self.store.publish(self.vertical.location, self.user.id)

def _create_block(self, parent, category, display_name, **kwargs):
"""
creates a block in the module store, without publishing it.
"""
return BlockFactory.create(
parent=parent,
category=category,
display_name=display_name,
publish_item=False,
user_id=self.user.id,
**kwargs,
)

def test_xblock_edit_view(self):
url = reverse_usage_url("xblock_edit_handler", self.video.location)
resp = self.client.get_html(url)
self.assertEqual(resp.status_code, 200)

html_content = resp.content.decode(resp.charset)
self.assertIn("var decodedActionName = 'edit';", html_content)

def test_xblock_edit_view_contains_resources(self):
url = reverse_usage_url("xblock_edit_handler", self.video.location)
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)

html_content = resp.content.decode(resp.charset)
soup = BeautifulSoup(html_content, "html.parser")

resource_links = [link["href"] for link in soup.find_all("link", {"rel": "stylesheet"})]
script_sources = [script["src"] for script in soup.find_all("script") if script.get("src")]

self.assertGreater(len(resource_links), 0, f"No CSS resources found in HTML. Found: {resource_links}")
self.assertGreater(len(script_sources), 0, f"No JS resources found in HTML. Found: {script_sources}")
11 changes: 11 additions & 0 deletions cms/static/cms/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ define([
title: gettext("Studio's having trouble saving your work"),
message: message
});
if (window.self !== window.top) {
try {
window.parent.postMessage({
type: 'studioAjaxError',
message: 'Sends a message when an AJAX error occurs',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}
}
console.log('Studio AJAX Error', { // eslint-disable-line no-console
url: event.currentTarget.URL,
response: jqXHR.responseText,
Expand Down
22 changes: 11 additions & 11 deletions cms/static/js/views/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,6 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView)
newParent = undefined;
},
update: function(event, ui) {
try {
window.parent.postMessage(
{
type: 'refreshPositions',
message: 'Refresh positions of all xblocks',
payload: {}
}, document.referrer
);
} catch (e) {
console.error(e);
}
// When dragging from one ol to another, this method
// will be called twice (once for each list). ui.sender will
// be null if the change is related to the list the element
Expand Down Expand Up @@ -137,6 +126,17 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView)
if (successCallback) {
successCallback();
}
try {
window.parent.postMessage(
{
type: 'refreshPositions',
message: 'Refresh positions of all xblocks',
payload: {}
}, document.referrer
);
} catch (e) {
console.error(e);
}
// Update publish and last modified information from the server.
xblockInfo.fetch();
}
Expand Down
21 changes: 21 additions & 0 deletions cms/static/js/views/modals/base_modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
},

hide: function() {
try {
window.parent.postMessage(
{
type: 'hideXBlockEditorModal',
message: 'Sends a message when the modal window is hided',
payload: {}
}, document.referrer
);
} catch (e) {
console.error(e);
}

// Completely remove the modal from the DOM
this.undelegateEvents();
this.$el.html('');
Expand All @@ -119,6 +131,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
event.preventDefault();
event.stopPropagation(); // Make sure parent modals don't see the click
}
try {
window.parent.postMessage({
type: 'closeXBlockEditorModal',
message: 'Sends a message when the modal window is closed',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}
this.hide();
},

Expand Down
25 changes: 25 additions & 0 deletions cms/static/js/views/modals/edit_xblock.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
},

getXBlockUpstreamLink: function() {
if (!this.xblockElement || !this.xblockElement.length) {
console.error('xblockElement is empty or not defined');
return;
}

const usageKey = this.xblockElement.data('locator');
$.ajax({
url: '/api/contentstore/v2/downstreams/' + usageKey,
Expand Down Expand Up @@ -219,6 +224,16 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
},

onSave: function() {
try {
window.parent.postMessage({
type: 'saveEditedXBlockData',
message: 'Sends a message when the xblock data is saved',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}

var refresh = this.editOptions.refresh;
this.hide();
if (refresh) {
Expand All @@ -230,6 +245,16 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
// Notify child views to stop listening events
Backbone.trigger('xblock:editorModalHidden');

try {
window.parent.postMessage({
type: 'closeXBlockEditorModal',
message: 'Sends a message when the modal window is closed',
payload: {}
}, document.referrer);
} catch (e) {
console.error(e);
}

BaseModal.prototype.hide.call(this);

// Notify the runtime that the modal has been hidden
Expand Down
44 changes: 28 additions & 16 deletions cms/static/js/views/pages/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ function($, _, Backbone, gettext, BasePage,
case 'refreshXBlock':
this.render();
break;
case 'completeXBlockEditing':
this.refreshXBlock(xblockElement, false);
break;
case 'completeManageXBlockAccess':
this.refreshXBlock(xblockElement, false);
break;
Expand Down Expand Up @@ -507,6 +510,18 @@ function($, _, Backbone, gettext, BasePage,
window.location.href = destinationUrl;
return;
}

if (this.options.isIframeEmbed) {
return window.parent.postMessage(
{
type: 'editXBlock',
message: 'Sends a message when the legacy modal window is shown',
payload: {
id: this.findXBlockElement(event.target).data('locator')
}
}, document.referrer
);
}
}

var xblockElement = this.findXBlockElement(event.target),
Expand Down Expand Up @@ -1050,23 +1065,20 @@ function($, _, Backbone, gettext, BasePage,
},

viewXBlockContent: function(event) {
try {
if (this.options.isIframeEmbed) {
event.preventDefault();
var usageId = event.currentTarget.href.split('/').pop() || '';
window.parent.postMessage(
{
type: 'handleViewXBlockContent',
payload: {
usageId: usageId,
},
}, document.referrer
);
return true;
try {
if (this.options.isIframeEmbed) {
event.preventDefault();
var usageId = event.currentTarget.href.split('/').pop() || '';
window.parent.postMessage({
type: 'handleViewXBlockContent',
message: 'View the content of the XBlock',
payload: { usageId },
}, document.referrer);
return true;
}
} catch (e) {
console.error(e);
}
} catch (e) {
console.error(e);
}
},

toggleSaveButton: function() {
Expand Down
4 changes: 3 additions & 1 deletion cms/static/js/views/pages/container_subviews.js
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,9 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
tagValueElement.className = 'tagging-label-value';

tagContentElement.appendChild(tagValueElement);
parentElement.appendChild(tagContentElement);
if (parentElement) {
parentElement.appendChild(tagContentElement);
}

if (tag.children.length > 0) {
var tagIconElement = document.createElement('span'),
Expand Down
Loading
Loading