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

Optional registry #486

Merged
merged 16 commits into from
Nov 6, 2020
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
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 @@ -843,7 +871,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 @@ -1164,7 +1192,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 @@ -1562,7 +1596,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 @@ -1722,7 +1759,7 @@ class FileUploadWidget(Widget):
readonly_template = "readonly/file_upload"
accept = None

requirements = (("fileupload", None),)
requirements = ({"js": "deform:static/scripts/file_upload.js"},)
stevepiercy marked this conversation as resolved.
Show resolved Hide resolved

_pstruct_schema = SchemaNode(
Mapping(),
Expand Down Expand Up @@ -2140,8 +2177,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 @@ -2168,13 +2203,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