Commit 51905f0c by Jonathan Piacenti

Addressed notes for RCB duplication, removed management buttons from RCB container

parent a6c00635
...@@ -593,18 +593,17 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_ ...@@ -593,18 +593,17 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_
runtime=source_item.runtime, runtime=source_item.runtime,
) )
handle_children = True children_handled = False
handle_parenting = True
if hasattr(dest_module, 'studio_post_duplicate'): if hasattr(dest_module, 'studio_post_duplicate'):
# Allow an XBlock to do anything fancy it may need to when duplicated from another block. # Allow an XBlock to do anything fancy it may need to when duplicated from another block.
# These blocks may handle their own children or parenting if needed. Let them return booleans to # These blocks may handle their own children or parenting if needed. Let them return booleans to
# let us know if we need to handle these or not. # let us know if we need to handle these or not.
handle_children, handle_parenting = dest_module.studio_post_duplicate(store, parent_usage_key, source_item) children_handled = dest_module.studio_post_duplicate(store, source_item)
# Children are not automatically copied over (and not all xblocks have a 'children' attribute). # Children are not automatically copied over (and not all xblocks have a 'children' attribute).
# Because DAGs are not fully supported, we need to actually duplicate each child as well. # Because DAGs are not fully supported, we need to actually duplicate each child as well.
if source_item.has_children and handle_children: if source_item.has_children and not children_handled:
dest_module.children = dest_module.children or [] dest_module.children = dest_module.children or []
for child in source_item.children: for child in source_item.children:
dupe = _duplicate_item(dest_module.location, child, user=user) dupe = _duplicate_item(dest_module.location, child, user=user)
...@@ -613,7 +612,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_ ...@@ -613,7 +612,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_
store.update_item(dest_module, user.id) store.update_item(dest_module, user.id)
# pylint: disable=protected-access # pylint: disable=protected-access
if ('detached' not in source_item.runtime.load_block_type(category)._class_tags) and handle_parenting: if ('detached' not in source_item.runtime.load_block_type(category)._class_tags):
parent = store.get_item(parent_usage_key) parent = store.get_item(parent_usage_key)
# If source was already a child of the parent, add duplicate immediately afterward. # If source was already a child of the parent, add duplicate immediately afterward.
# Otherwise, add child to end. # Otherwise, add child to end.
......
...@@ -240,7 +240,6 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): ...@@ -240,7 +240,6 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
# Only add the Studio wrapper when on the container page. The "Pages" page will remain as is for now. # Only add the Studio wrapper when on the container page. The "Pages" page will remain as is for now.
if not context.get('is_pages_view', None) and view in PREVIEW_VIEWS: if not context.get('is_pages_view', None) and view in PREVIEW_VIEWS:
root_xblock = context.get('root_xblock') root_xblock = context.get('root_xblock')
can_edit_visibility = not isinstance(xblock.location, LibraryUsageLocator)
is_root = root_xblock and xblock.location == root_xblock.location is_root = root_xblock and xblock.location == root_xblock.location
is_reorderable = _is_xblock_reorderable(xblock, context) is_reorderable = _is_xblock_reorderable(xblock, context)
template_context = { template_context = {
...@@ -251,7 +250,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): ...@@ -251,7 +250,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_root': is_root, 'is_root': is_root,
'is_reorderable': is_reorderable, 'is_reorderable': is_reorderable,
'can_edit': context.get('can_edit', True), 'can_edit': context.get('can_edit', True),
'can_edit_visibility': can_edit_visibility, 'can_edit_visibility': context.get('can_edit_visibility', True),
'can_add': context.get('can_add', True),
} }
html = render_to_string('studio_xblock_wrapper.html', template_context) html = render_to_string('studio_xblock_wrapper.html', template_context)
frag = wrap_fragment(frag, html) frag = wrap_fragment(frag, html)
......
...@@ -80,19 +80,24 @@ messages = json.dumps(xblock.validate().to_json()) ...@@ -80,19 +80,24 @@ messages = json.dumps(xblock.validate().to_json())
</a> </a>
</li> </li>
% endif % endif
<li class="action-item action-duplicate"> % if can_add:
<a href="#" data-tooltip="${_("Duplicate")}" class="duplicate-button action-button"> <li class="action-item action-duplicate">
<i class="icon fa fa-copy"></i> <a href="#" data-tooltip="${_("Duplicate")}" class="duplicate-button action-button">
<span class="sr">${_("Duplicate")}</span> <i class="icon fa fa-copy"></i>
<span class="sr">${_("Duplicate")}</span>
</a>
</li>
% endif
% endif
% if can_add:
<!-- If we can add, we can delete. -->
<li class="action-item action-delete">
<a href="#" data-tooltip="${_("Delete")}" class="delete-button action-button">
<i class="icon fa fa-trash-o"></i>
<span class="sr">${_("Delete")}</span>
</a> </a>
</li> </li>
% endif % endif
<li class="action-item action-delete">
<a href="#" data-tooltip="${_("Delete")}" class="delete-button action-button">
<i class="icon fa fa-trash-o"></i>
<span class="sr">${_("Delete")}</span>
</a>
</li>
% if is_reorderable: % if is_reorderable:
<li class="action-item action-drag"> <li class="action-item action-drag">
<span data-tooltip="${_('Drag to reorder')}" class="drag-handle action"></span> <span data-tooltip="${_('Drag to reorder')}" class="drag-handle action"></span>
......
...@@ -7,6 +7,7 @@ from lxml import etree ...@@ -7,6 +7,7 @@ from lxml import etree
from copy import copy from copy import copy
from capa.responsetypes import registry from capa.responsetypes import registry
from gettext import ngettext from gettext import ngettext
from lazy import lazy
from .mako_module import MakoModuleDescriptor from .mako_module import MakoModuleDescriptor
from opaque_keys.edx.locator import LibraryLocator from opaque_keys.edx.locator import LibraryLocator
...@@ -269,6 +270,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): ...@@ -269,6 +270,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
'max_count': self.max_count, 'max_count': self.max_count,
'display_name': self.display_name or self.url_name, 'display_name': self.display_name or self.url_name,
})) }))
context['can_edit_visibility'] = False
self.render_children(context, fragment, can_reorder=False, can_add=False) self.render_children(context, fragment, can_reorder=False, can_add=False)
# else: When shown on a unit page, don't show any sort of preview - # else: When shown on a unit page, don't show any sort of preview -
# just the status of this block in the validation area. # just the status of this block in the validation area.
...@@ -306,16 +308,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ...@@ -306,16 +308,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
non_editable_fields.extend([LibraryContentFields.mode, LibraryContentFields.source_library_version]) non_editable_fields.extend([LibraryContentFields.mode, LibraryContentFields.source_library_version])
return non_editable_fields return non_editable_fields
def get_tools(self): @lazy
def tools(self):
""" """
Grab the library tools service or raise an error. Grab the library tools service or raise an error.
""" """
lib_tools = self.runtime.service(self, 'library_tools') return self.runtime.service(self, 'library_tools')
if not lib_tools:
# This error is diagnostic. The user won't see it, but it may be helpful
# during debugging.
return Response(_(u"Course does not support Library tools."), status=400)
return lib_tools
def get_user_id(self): def get_user_id(self):
""" """
...@@ -343,14 +341,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ...@@ -343,14 +341,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
the version number of the libraries used, so we easily determine if the version number of the libraries used, so we easily determine if
this block is up to date or not. this block is up to date or not.
""" """
lib_tools = self.get_tools()
user_perms = self.runtime.service(self, 'studio_user_permissions') user_perms = self.runtime.service(self, 'studio_user_permissions')
user_id = self.get_user_id() user_id = self.get_user_id()
lib_tools.update_children(self, user_id, user_perms) self.tools.update_children(self, user_id, user_perms)
return Response() return Response()
# pylint: disable=unused-argument def studio_post_duplicate(self, store, source_block):
def studio_post_duplicate(self, store, parent_usage_key, source_block):
""" """
Used by the studio after basic duplication of a source block. We handle the children Used by the studio after basic duplication of a source block. We handle the children
ourselves, because we have to properly reference the library upstream and set the overrides. ourselves, because we have to properly reference the library upstream and set the overrides.
...@@ -360,32 +356,30 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ...@@ -360,32 +356,30 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
# The first task will be to refresh our copy of the library to generate the children. # The first task will be to refresh our copy of the library to generate the children.
# We must do this at the currently set version of the library block. Otherwise we may not have # We must do this at the currently set version of the library block. Otherwise we may not have
# exactly the same children-- someone may be duplicating an out of date block, after all. # exactly the same children-- someone may be duplicating an out of date block, after all.
lib_tools = self.get_tools()
user_id = self.get_user_id() user_id = self.get_user_id()
user_perms = self.runtime.service(self, 'studio_user_permissions') user_perms = self.runtime.service(self, 'studio_user_permissions')
# pylint: disable=no-member # pylint: disable=no-member
lib_tools.update_children(self, user_id, user_perms, version=self.source_library_version) self.tools.update_children(self, user_id, user_perms, version=self.source_library_version)
# Copy over any overridden settings the course author may have applied to the blocks. # Copy over any overridden settings the course author may have applied to the blocks.
def copy_overrides(source, dest): def copy_overrides(source, dest):
""" """
Copy any overrides the user has made on blocks in this library. Copy any overrides the user has made on blocks in this library.
""" """
for field_name in source.fields.keys(): for field in source.fields.itervalues():
field = dest.fields[field_name]
if field.scope == Scope.settings and field.is_set_on(source): if field.scope == Scope.settings and field.is_set_on(source):
setattr(dest, field_name, getattr(source, field_name)) setattr(dest, field.name, field.read_from(source))
if source.has_children: if source.has_children:
source_children = [store.get_item(source_key) for source_key in source.children] source_children = [self.runtime.get_block(source_key) for source_key in source.children]
dest_children = [store.get_item(dest_key) for dest_key in dest.children] dest_children = [self.runtime.get_block(dest_key) for dest_key in dest.children]
for source_child, dest_child in zip(source_children, dest_children): for source_child, dest_child in zip(source_children, dest_children):
copy_overrides(source_child, dest_child) copy_overrides(source_child, dest_child)
store.update_item(dest, user_id) store.update_item(dest, user_id)
copy_overrides(source_block, self) copy_overrides(source_block, self)
# Don't handle children. Handle parenting. # Children have been handled.
return False, True return True
def _validate_library_version(self, validation, lib_tools, version, library_key): def _validate_library_version(self, validation, lib_tools, version, library_key):
""" """
......
...@@ -82,6 +82,7 @@ class LibraryRoot(XBlock): ...@@ -82,6 +82,7 @@ class LibraryRoot(XBlock):
# Children must have a separate context from the library itself. Make a copy. # Children must have a separate context from the library itself. Make a copy.
child_context = context.copy() child_context = context.copy()
child_context['show_preview'] = self.show_children_previews child_context['show_preview'] = self.show_children_previews
child_context['can_edit_visibility'] = False
child = self.runtime.get_block(child_key) child = self.runtime.get_block(child_key)
child_view_name = StudioEditableModule.get_preview_view_name(child) child_view_name = StudioEditableModule.get_preview_view_name(child)
......
...@@ -28,9 +28,8 @@ class LibraryToolsService(object): ...@@ -28,9 +28,8 @@ class LibraryToolsService(object):
if not isinstance(library_key, LibraryLocator): if not isinstance(library_key, LibraryLocator):
library_key = LibraryLocator.from_string(library_key) library_key = LibraryLocator.from_string(library_key)
library_key = LibraryLocator( if version:
org=library_key.org, library=library_key.library, branch=library_key.branch, version_guid=version library_key.for_version(version)
)
try: try:
return self.store.get_library(library_key, remove_version=False, remove_branch=False) return self.store.get_library(library_key, remove_version=False, remove_branch=False)
......
...@@ -22,6 +22,7 @@ class StudioEditableBlock(object): ...@@ -22,6 +22,7 @@ class StudioEditableBlock(object):
for child in self.get_children(): # pylint: disable=no-member for child in self.get_children(): # pylint: disable=no-member
if can_reorder: if can_reorder:
context['reorderable_items'].add(child.location) context['reorderable_items'].add(child.location)
context['can_add'] = can_add
rendered_child = child.render(StudioEditableModule.get_preview_view_name(child), context) rendered_child = child.render(StudioEditableModule.get_preview_view_name(child), context)
fragment.add_frag_resources(rendered_child) fragment.add_frag_resources(rendered_child)
......
...@@ -407,6 +407,20 @@ class XBlockWrapper(PageObject): ...@@ -407,6 +407,20 @@ class XBlockWrapper(PageObject):
return self.q(css=self._bounded_selector('.wrapper-xblock.has-group-visibility-set')).is_present() return self.q(css=self._bounded_selector('.wrapper-xblock.has-group-visibility-set')).is_present()
@property @property
def has_duplicate_button(self):
"""
Returns true if this xblock has a 'duplicate' button
"""
return self.q(css=self._bounded_selector('a.duplicate-button'))
@property
def has_delete_button(self):
"""
Returns true if this xblock has a 'delete' button
"""
return self.q(css=self._bounded_selector('a.delete-button'))
@property
def has_edit_visibility_button(self): def has_edit_visibility_button(self):
""" """
Returns true if this xblock has an 'edit visibility' button Returns true if this xblock has an 'edit visibility' button
......
...@@ -290,3 +290,21 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest): ...@@ -290,3 +290,21 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest):
block.reset_field_val("Display Name") block.reset_field_val("Display Name")
block.save_settings() block.save_settings()
self.assertEqual(block.name, name_default) self.assertEqual(block.name, name_default)
def test_cannot_manage(self):
"""
Scenario: Given I have a library, a course and library content xblock in a course
When I go to studio unit page for library content block
And when I click the "View" link
Then I can see a preview of the blocks drawn from the library.
And I do not see a duplicate button
And I do not see a delete button
"""
block_wrapper_unit_page = self._get_library_xblock_wrapper(self.unit_page.xblocks[0].children[0])
container_page = block_wrapper_unit_page.go_to_container()
for block in container_page.xblocks:
self.assertFalse(block.has_duplicate_button)
self.assertFalse(block.has_delete_button)
self.assertFalse(block.has_edit_visibility_button)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment