Skip to content
This repository was archived by the owner on Oct 5, 2023. It is now read-only.
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.log
*.pyc
.coverage
cover
xblock_utils.egg-info
tests.integration.*.png
188 changes: 186 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
xblock-utils: Various utilities for XBlocks
-------------------------------------------
===========================================

These are a collection of useful utility functions,
test base classes and documentation shared by many XBlocks.
Expand All @@ -10,4 +10,188 @@ test base classes and documentation shared by many XBlocks.

To test the utilities, run::

./run_tests.py
python run_tests.py

To get a coverage report, use:

python run_tests.py --with-coverage --cover-package=xblockutils --cover-html


StudioEditableXBlockMixin
-------------------------

.. code:: python

from xblockutils.studio_editable import StudioEditableXBlockMixin

This mixin will automatically generate a working ``studio_view`` form
that allows content authors to edit the fields of your XBlock. To use,
simply add the class to your base class list, and add a new class field
called ``editable_fields``, set to a tuple of the names of the fields
you want your user to be able to edit.

.. code:: python

@XBlock.needs("i18n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this sufficiently generic? do all xblocks apply this decorator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs("i18n") is a very common, useful decorator, since it's basically the only way for an XBlock to localize its translatable strings. It's not strictly required, but it's in this example code as a reminder of good practices.

class ExampleBlock(StudioEditableXBlockMixin, XBlock):
...
mode = String(
display_name="Mode",
help="Determines the behaviour of this component. Standard is recommended.",
default='standard',
scope=Scope.content,
values=('standard', 'crazy')
)
editable_fields = ('mode', 'display_name')

That's all you need to do. The mixin will read the optional
``display_name``, ``help``, ``default``, and ``values`` settings from
the fields you mention and build the editor form as well as an AJAX save
handler.

If you want to validate the data, you can override
``validate_field_data(self, validation, data)`` and/or
``clean_studio_edits(self, data)`` - see the source code for details.

Supported field types:

* Boolean:
``field_name = Boolean(display_name="Field Name")``
* Float:
``field_name = Float(display_name="Field Name")``
* Integer:
``field_name = Integer(display_name="Field Name")``
* String:
``field_name = String(display_name="Field Name")``
* String (multiline):
``field_name = String(multiline_editor=True, resettable_editor=False)``
* String (html):
``field_name = String(multiline_editor='html', resettable_editor=False)``

Any of the above will use a dropdown menu if they have a pre-defined
list of possible values.

* List of unordered unique values (i.e. sets) drawn from a small set of
possible values:
``field_name = List(list_style='set', list_values_provider=some_method)``

- The ``List`` declaration must include the property ``list_style='set'`` to
indicate that the ``List`` field is being used with set semantics.
- The ``List`` declaration must also define a ``list_values_provider`` method
which will be called with the block as its only parameter and which must
return a list of possible values.
* Rudimentary support for Dict, ordered List, and any other JSONField-derived field types

- ``list_field = List(display_name="Ordered List", default=[])``
- ``dict_field = Dict(display_name="Normal Dict", default={})``

Supported field options (all field types):

* ``values`` can define a list of possible options, changing the UI element
to a select box. Values can be set to any of the formats `defined in the
XBlock source code <https://github.com/edx/XBlock/blob/master/xblock/fields.py>`__:

- A finite set of elements: ``[1, 2, 3]``
- A finite set of elements where the display names differ from the values
::
[
{"display_name": "Always", "value": "always"},
{"display_name": "Past Due", "value": "past_due"},
]
- A range for floating point numbers with specific increments:
``{"min": 0 , "max": 10, "step": .1}``
- A callable that returns one of the above. (Note: the callable does
*not* get passed the XBlock instance or runtime, so it cannot be a
normal member function)
* ``values_provider`` can define a callable that accepts the XBlock
instance as an argument, and returns a list of possible values in one
of the formats listed above.
* ``resettable_editor`` - defaults to ``True``. Set ``False`` to hide the
"Reset" button used to return a field to its default value by removing
the field's value from the XBlock instance.

Basic screenshot: |Screenshot 1|

StudioContainerXBlockMixin
--------------------------

.. code:: python

from xblockutils.studio_editable import StudioContainerXBlockMixin

This mixin helps to create XBlocks that allow content authors to add,
remove, or reorder child blocks. By removing any existing
``author_view`` and adding this mixin, you'll get editable,
re-orderable, and deletable child support in Studio. To enable authors to
add arbitrary blocks as children, simply override ``author_edit_view``
and set ``can_add=True`` when calling ``render_children`` - see the
source code. To restrict authors so they can add only specific types of
child blocks or a limited number of children requires custom HTML.

An example is the mentoring XBlock: |Screenshot 2|

SeleniumXBlockTest
------------------

.. code:: python

from xblockutils.base_test import SeleniumXBlockTest

This is a base class that you can use for writing Selenium integration
tests that are hosted in the XBlock SDK (Workbench).

Here is an example:

.. code:: python

class TestStudentView(SeleniumXBlockTest):
"""
Test the Student View of MyCoolXBlock
"""
def setUp(self):
super(TestStudentView, self).setUp()
self.set_scenario_xml('<mycoolblock display_name="Test Demo Block" field2="hello" />')
self.element = self.go_to_view("student_view")

def test_shows_field_2(self):
"""
The xblock should display the text value of field2.
"""
self.assertIn("hello", self.element.text)

StudioEditableBaseTest
----------------------

.. code:: python

from xblockutils.studio_editable_test import StudioEditableBaseTest

This is a subclass of ``SeleniumXBlockTest`` that adds a few helper
methods useful for testing the ``studio_view`` of any XBlock using
``StudioEditableXBlockMixin``.

child\_isinstance
-----------------

.. code:: python

from xblockutils.helpers import child_isinstance

If your XBlock needs to find children/descendants of a particular
class/mixin, you should use

.. code:: python

child_isinstance(self, child_usage_id, SomeXBlockClassOrMixin)

rather than calling

.. code:: python

``isinstance(self.runtime.get_block(child_usage_id), SomeXBlockClassOrMixin)``.

On runtimes such as those in edx-platform, ``child_isinstance`` is
orders of magnitude faster.

.. |Screenshot 1| image:: https://cloud.githubusercontent.com/assets/945577/6341782/7d237966-bb83-11e4-9344-faa647056999.png
.. |Screenshot 2| image:: https://cloud.githubusercontent.com/assets/945577/6341803/d0195ec4-bb83-11e4-82f6-8052c9f70690.png
4 changes: 3 additions & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ disable=
protected-access,
star-args,
too-few-public-methods,
too-many-public-methods,
too-many-ancestors,
too-many-arguments,
too-many-branches,
too-many-locals,
too-many-public-methods,
too-many-statements,
unused-argument

[SIMILARITIES]
Expand Down
14 changes: 13 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@
import os.path
from setuptools import setup


def package_data(pkg, root_list):
"""Generic function to find package_data for `pkg` under `root`."""
data = []
for root in root_list:
for dirname, _, files in os.walk(os.path.join(pkg, root)):
for fname in files:
data.append(os.path.relpath(os.path.join(dirname, fname), pkg))

return {pkg: data}

setup(
name='xblock-utils',
version='0.1a0',
Expand All @@ -31,5 +42,6 @@
],
install_requires=[
'XBlock',
]
],
package_data=package_data("xblockutils", ["public", "templates"]),
)
Loading