Skip to content

Commit

Permalink
Merge pull request #326 from edx/ekolpakov/force_export
Browse files Browse the repository at this point in the history
Force export runtime option + some tests for XmlSerializationMixin
  • Loading branch information
John Eskew committed Dec 2, 2015
2 parents 446190f + cc3b1c3 commit 17956e7
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 8 deletions.
6 changes: 5 additions & 1 deletion xblock/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ class will want to refer to.
xml_node: if set, the field will be serialized as a
separate node instead of an xml attribute (default: False).
force_export: if set, the field value will be exported to XML even if normal
export conditions are not met (i.e. the field has no explicit value set)
kwargs: optional runtime-specific options/metadata. Will be stored as
runtime_options.
Expand All @@ -308,7 +311,7 @@ class will want to refer to.
# We're OK redefining built-in `help`
def __init__(self, help=None, default=UNSET, scope=Scope.content, # pylint:disable=redefined-builtin
display_name=None, values=None, enforce_type=False,
xml_node=False, **kwargs):
xml_node=False, force_export=False, **kwargs):
self.warned = False
self.help = help
self._enable_enforce_type = enforce_type
Expand All @@ -322,6 +325,7 @@ def __init__(self, help=None, default=UNSET, scope=Scope.content, # pylint:disa
self._values = values
self.runtime_options = kwargs
self.xml_node = xml_node
self.force_export = force_export

@property
def default(self):
Expand Down
13 changes: 8 additions & 5 deletions xblock/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import logging
from lxml import etree
import copy
from collections import OrderedDict

try:
import simplesjson as json # pylint: disable=F0401
Expand All @@ -22,10 +23,12 @@
from xblock.internal import class_lazy, NamedAttributesMetaclass


XML_NAMESPACES = {
"option": "http://code.edx.org/xblock/option",
"block": "http://code.edx.org/xblock/block",
}
# OrderedDict is used so that namespace attributes are put in predictable order
# This allows for simple string equality assertions in tests and have no other effects
XML_NAMESPACES = OrderedDict([
("option", "http://code.edx.org/xblock/option"),
("block", "http://code.edx.org/xblock/block"),
])


class HandlersMixin(object):
Expand Down Expand Up @@ -477,7 +480,7 @@ def add_xml_to_node(self, node):
for field_name, field in self.fields.iteritems():
if field_name in ('children', 'parent', 'content'):
continue
if field.is_set_on(self):
if field.is_set_on(self) or field.force_export:
self._add_field(node, field_name, field)

# Add children for each of our children.
Expand Down
78 changes: 76 additions & 2 deletions xblock/test/test_mixins.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""
Tests of the XBlock-family functionality mixins
"""

from lxml import etree
import mock
from unittest import TestCase

from xblock.fields import List, Scope, Integer
from xblock.core import XBlock
from xblock.fields import List, Scope, Integer, String, ScopeIds, UNIQUE_ID
from xblock.field_data import DictFieldData
from xblock.mixins import ScopedStorageMixin, HierarchyMixin, IndexInfoMixin, ViewsMixin
from xblock.runtime import Runtime


class AttrAssertionMixin(TestCase):
Expand Down Expand Up @@ -209,3 +213,73 @@ def has_support(self, view, functionality):
test_xblock.has_support(getattr(test_xblock, view_name, None), functionality),
expected_result
)


class TestXmlSerializationMixin(TestCase):
""" Tests for XmlSerialization Mixin """

etree_node_tag = 'test_xblock'

# pylint:disable=invalid-name
class TestXBlock(XBlock):
""" XBlock for XML export test """
field = String()
simple_default = String(default="default")
simple_default_with_force_export = String(default="default", force_export=True)
unique_id_default = String(default=UNIQUE_ID)
unique_id_default_with_force_export = String(default=UNIQUE_ID, force_export=True)

def _make_block(self):
""" Creates a test block """
runtime_mock = mock.Mock(spec=Runtime)
scope_ids = ScopeIds("user_id", self.etree_node_tag, "def_id", "usage_id")
return self.TestXBlock(runtime_mock, field_data=DictFieldData({}), scope_ids=scope_ids)

def _assert_node_attributes(self, node, attributes):
""" Asserts node attributes """
node_attributes = node.keys()
node_attributes.remove('xblock-family')

self.assertEqual(node.get('xblock-family'), self.TestXBlock.entry_point)

self.assertEqual(set(node_attributes), set(attributes.keys()))

for key, value in attributes.iteritems():
if value != UNIQUE_ID:
self.assertEqual(node.get(key), value)
else:
self.assertIsNotNone(node.get(key))

def test_add_xml_to_node(self):
""" Tests add_xml_to_node with various field defaults and runtime parameters """
block = self._make_block()
node = etree.Element(self.etree_node_tag)

# precondition check
for field_name in block.fields.keys():
self.assertFalse(block.fields[field_name].is_set_on(block))

block.add_xml_to_node(node)

self._assert_node_attributes(
node, {'simple_default_with_force_export': 'default', 'unique_id_default_with_force_export': UNIQUE_ID}
)

block.field = 'Value1'
block.simple_default = 'Value2'
block.simple_default_with_force_export = 'Value3'
block.unique_id_default = 'Value4'
block.unique_id_default_with_force_export = 'Value5'

block.add_xml_to_node(node)

self._assert_node_attributes(
node,
{
'field': 'Value1',
'simple_default': 'Value2',
'simple_default_with_force_export': 'Value3',
'unique_id_default': 'Value4',
'unique_id_default_with_force_export': 'Value5',
}
)

0 comments on commit 17956e7

Please sign in to comment.