Skip to content

Commit

Permalink
Merge pull request #491 from stevepiercy/master
Browse files Browse the repository at this point in the history
Forward port #486
  • Loading branch information
stevepiercy authored Nov 6, 2020
2 parents 522a348 + 2cdd0df commit 622ed45
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 35 deletions.
36 changes: 26 additions & 10 deletions deform/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,17 +448,21 @@ def get_widget_requirements(self):
requirements in :ref:`get_widget_requirements`.
"""
L = []
requirements = self.widget.requirements

requirements = [req for req in self.widget.requirements] + [
req
for child in self.children
for req in child.get_widget_requirements()
]

if requirements:
for requirement in requirements:
reqt = tuple(requirement)
if reqt not in L:
L.append(reqt)
for child in self.children:
for requirement in child.get_widget_requirements():
reqt = tuple(requirement)
if reqt not in L:
L.append(reqt)
if isinstance(requirement, dict):
L.append(requirement)
else:
reqt = tuple(requirement)
if reqt not in L:
L.append(reqt)
return L

def get_widget_resources(self, requirements=None):
Expand All @@ -485,7 +489,19 @@ def get_widget_resources(self, requirements=None):
"""
if requirements is None:
requirements = self.get_widget_requirements()
return self.resource_registry(requirements)
resources = self.resource_registry(
(req for req in requirements if not isinstance(req, dict))
)
for req in requirements:
if not isinstance(req, dict):
continue
for key in {'js', 'css'}.intersection(req):
value = req[key]
if isinstance(value, str):
resources[key].append(value)
else:
resources[key].extend(value)
return resources

def set_widgets(self, values, separator="."):
"""set widgets of the child fields of this field
Expand Down
15 changes: 13 additions & 2 deletions deform/tests/test_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ def test_get_widget_requirements(self):
result, [("abc", "123"), ("ghi", "789"), ("def", "456")]
)

def test_get_widget_resources(self):
def test_get_widget_resources_with_registry(self):
def resource_registry(requirements):
self.assertEqual(requirements, [("abc", "123")])
self.assertEqual(list(requirements), [("abc", "123")])
return "OK"

schema = DummySchema()
Expand All @@ -358,6 +358,17 @@ def resource_registry(requirements):
result = field.get_widget_resources()
self.assertEqual(result, "OK")

def test_get_widget_resources_without_registry(self):
schema = DummySchema()
field = self._makeOne(schema)
field.widget.requirements = (
{"js": "123.js", "css": ["123.css"]},
{"css": ["1.css", "2.css"]},
)
result = field.get_widget_resources()
self.assertEqual(result['js'], ['123.js'])
self.assertEqual(result['css'], ["123.css", "1.css", "2.css"])

def test_clone(self):
schema = DummySchema()
field = self._makeOne(schema, renderer="abc")
Expand Down
62 changes: 45 additions & 17 deletions deform/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,21 @@ class Widget(object):
be added to the input tag.
requirements
A sequence of two-tuples in the form ``( (requirement_name,
version_id), ...)`` indicating the logical external
requirements needed to make this widget render properly within
The requirements are specified as a sequence of either of the
following.
1. Two-tuples in the form ``(requirement_name, version_id)``.
The **logical** requirement name identifiers are resolved to
concrete files using the ``resource_registry``.
2. Dicts in the form ``{requirement_type:
requirement_location(s)}``. The ``resource_registry`` is
bypassed. This is useful for creating custom widgets with
their own resources.
Requirements specified as a sequence of two-tuples should be in the
form ``( (requirement_name, version_id), ...)`` indicating the logical
external requirements needed to make this widget render properly within
a form. The ``requirement_name`` is a string that *logically*
(not concretely, it is not a filename) identifies *one or
more* Javascript or CSS resources that must be included in the
Expand All @@ -153,7 +165,23 @@ class Widget(object):
'tinymce' for Tiny MCE). The ``version_id`` is a string
indicating the version number (or ``None`` if no particular
version is required). For example, a rich text widget might
declare ``requirements = (('tinymce', '3.3.8'),)``. See also:
declare ``requirements = (('tinymce', '3.3.8'),)``.
Requirements specified as a sequence of dicts should be in the form
``({requirement_type: requirement_location(s)}, ...)``. The
``requirement_type`` key must be either ``js`` or ``css``. The
``requirement_location(s)`` value must be either a string or a list of
strings. Each string must resolve to a concrete resource. For example,
a widget might declare:
.. code-block:: python
requirements = (
{"js": "deform:static/tinymce/tinymce.min.js"},
{"css": "deform:static/tinymce/tinymce.min.css"},
)
See also:
:ref:`specifying_widget_requirements` and
:ref:`widget_requirements`.
Expand Down Expand Up @@ -850,7 +878,7 @@ class RichTextWidget(TextInputWidget):
delayed_load = False
strip = True
template = "richtext"
requirements = (("tinymce", None),)
requirements = ({"js": "deform:static/tinymce/tinymce.min.js"},)

#: Default options passed to TinyMCE. Customise by using :attr:`options`.
default_options = (
Expand Down Expand Up @@ -1171,7 +1199,13 @@ class Select2Widget(SelectWidget):
"""

template = "select2"
requirements = (("deform", None), ("select2", None))
requirements = (
("deform", None),
{
"js": "deform:static/select2/select2.js",
"css": "deform:static/select2/select2.css",
},
)


class RadioChoiceWidget(SelectWidget):
Expand Down Expand Up @@ -1576,7 +1610,10 @@ class SequenceWidget(Widget):
min_len = None
max_len = None
orderable = False
requirements = (("deform", None), ("sortable", None))
requirements = (
("deform", None),
{"js": "deform:static/scripts/jquery-sortable.js"},
)

def prototype(self, field):
# we clone the item field to bump the oid (for easier
Expand Down Expand Up @@ -1736,7 +1773,7 @@ class FileUploadWidget(Widget):
readonly_template = "readonly/file_upload"
accept = None

requirements = (("fileupload", None),)
requirements = ({"js": "deform:static/scripts/file_upload.js"},)

_pstruct_schema = SchemaNode(
Mapping(),
Expand Down Expand Up @@ -2154,8 +2191,6 @@ def __call__(self, requirements):
)
}
},
"tinymce": {None: {"js": "deform:static/tinymce/tinymce.min.js"}},
"sortable": {None: {"js": "deform:static/scripts/jquery-sortable.js"}},
"typeahead": {
None: {
"js": "deform:static/scripts/typeahead.min.js",
Expand All @@ -2182,13 +2217,6 @@ def __call__(self, requirements):
),
}
},
"select2": {
None: {
"js": "deform:static/select2/select2.js",
"css": "deform:static/select2/select2.css",
}
},
"fileupload": {None: {"js": "deform:static/scripts/file_upload.js"}},
}

default_resource_registry = ResourceRegistry()
53 changes: 47 additions & 6 deletions docs/widget.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,29 @@ See also the documentation of the ``resource_registry`` argument in
Specifying Widget Requirements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When creating a new widget, you may specify its requirements by using
the ``requirements`` attribute:
When instantiating a new widget, you may specify its requirements by using the ``requirements`` attribute.
The requirements are specified as a sequence of two-tuples, dicts, or a combination of both two-tuples and dicts.

The two-tuple form uses the resource registry and is used by most of the core Deform widgets.
The two-tuple form takes advantage of Deform's abstraction layer through the resource registry.

The dict form bypasses the resource registry.
The dict form may be easier to implement than the two-tuple form for custom widgets because it does not introduce an implicit dependency on the resource registry.
This is especially true if the required resources are tightly coupled to a custom widget.


.. _two-tuple-widget-requirements:

Using two-tuples for specifying widget requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: python
:linenos:
:linenos:
from deform.widget import Widget
from deform.widget import Widget
class MyWidget(Widget):
requirements = ( ('jquery', '1.4.2'), )
class MyWidget(Widget):
requirements = ( ("jquery", "1.4.2"), )
There are no hard-and-fast rules about the composition of a
requirement name. Your widget's docstring should explain what its
Expand All @@ -283,6 +296,34 @@ constructing the form. The default resource registry
(:attr:`deform.widget.resource_registry`) does not contain resource
mappings for your newly-created requirement.


.. _dict-widget-requirements:

Using dicts for specifying widget requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Requirements specified as a sequence of dicts should be in the form``({requirement_type: requirement_location(s)}, ...)``.
The ``requirement_type`` key must be either ``js`` or ``css``.
The ``requirement_location(s)`` value must be either a string or a list of strings.
Each string must resolve to a concrete resource.

.. code-block:: python
:linenos:
from deform.widget import Widget
class MyWidget(Widget):
requirements = ( {
"js": "my:static/path/to/jquery.js",
"css": [
"my:static/path/to/jquery.css",
"my:static:path/to/bootstrap.css"],
} )
The supplied paths are resolved by ``request.get_path()`` so the required
static resources should be included in the Pyramid config.


.. _writing_a_widget:

Writing Your Own Widget
Expand Down

0 comments on commit 622ed45

Please sign in to comment.