From a32acaf3428969b2326c840b8a1aa8db383cb7df Mon Sep 17 00:00:00 2001 From: Tom Gross Date: Thu, 8 Jun 2017 14:53:06 +0200 Subject: [PATCH] Add new and enhanced link widget. --- CHANGES.rst | 3 ++ plone/app/z3cform/configure.zcml | 12 ++++++ plone/app/z3cform/converters.py | 38 +++++++++++++++++ plone/app/z3cform/interfaces.py | 4 ++ plone/app/z3cform/templates/link_input.pt | 51 +++++++++++++++++++++++ plone/app/z3cform/utils.py | 32 ++++++++++++++ plone/app/z3cform/widget.py | 46 ++++++++++++++++++++ 7 files changed, 186 insertions(+) create mode 100644 plone/app/z3cform/templates/link_input.pt diff --git a/CHANGES.rst b/CHANGES.rst index 8b5ecc0a..a1e0223f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,9 @@ New features: Bug fixes: +- Add new and enhanced link widget. + [tomgross, thet] + - Fix broken ``get_tinymce_options`` when called with non-contentish contexts like form or field contexts. [thet] diff --git a/plone/app/z3cform/configure.zcml b/plone/app/z3cform/configure.zcml index 1849e0b1..eed91e28 100644 --- a/plone/app/z3cform/configure.zcml +++ b/plone/app/z3cform/configure.zcml @@ -118,6 +118,13 @@ template="templates/object_input.pt" /> + + + @@ -184,4 +192,8 @@ for="plone.app.textfield.interfaces.IRichText plone.app.z3cform.interfaces.IPloneFormLayer"/> + + diff --git a/plone/app/z3cform/converters.py b/plone/app/z3cform/converters.py index d2dbebdc..8693fe23 100644 --- a/plone/app/z3cform/converters.py +++ b/plone/app/z3cform/converters.py @@ -4,9 +4,11 @@ from plone.app.z3cform.interfaces import IAjaxSelectWidget from plone.app.z3cform.interfaces import IDatetimeWidget from plone.app.z3cform.interfaces import IDateWidget +from plone.app.z3cform.interfaces import ILinkWidget from plone.app.z3cform.interfaces import IQueryStringWidget from plone.app.z3cform.interfaces import IRelatedItemsWidget from plone.app.z3cform.interfaces import ISelectWidget +from plone.app.z3cform.utils import replace_link_variables_by_paths from plone.uuid.interfaces import IUUID from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import safe_callable @@ -303,3 +305,39 @@ def toFieldValue(self, value): if not value: return self.field.missing_value return value + + +@adapter(IField, ILinkWidget) +class LinkWidgetDataConverter(BaseDataConverter): + """Data converter for the enhanced link widget.""" + + def toWidgetValue(self, value): + value = super(LinkWidgetDataConverter, self).toWidgetValue(value) + result = {'internal': u'', + 'external': u'', + 'email': u'', + 'email_subject': u''} + uuid = None + if value.startswith('mailto:'): + value = value[7:] # strip mailto from beginning + if '?subject=' in value: + email, email_subject = value.split('?subject=') + result['email'] = email + result['email_subject'] = email_subject + else: + result['email'] = value + else: + if '/resolveuid/' in value: + result['internal'] = value.rsplit('/', 1)[-1] + else: + portal = getSite() + path = replace_link_variables_by_paths(portal, value) + path = path[len(portal.absolute_url())+1:].encode('ascii', 'ignore') # noqa + obj = portal.unrestrictedTraverse(path=path, default=None) + if obj is not None: + uuid = IUUID(obj, None) + if uuid is not None: + result['internal'] = uuid + else: + result['external'] = value + return result diff --git a/plone/app/z3cform/interfaces.py b/plone/app/z3cform/interfaces.py index eadf50e9..0243ca78 100644 --- a/plone/app/z3cform/interfaces.py +++ b/plone/app/z3cform/interfaces.py @@ -46,3 +46,7 @@ class IRelatedItemsWidget(ITextWidget): class IRichTextWidget(patextfield_IRichTextWidget): """Marker interface for the TinyMCEWidget.""" + + +class ILinkWidget(ITextWidget): + """Marker interface for the enhanced link widget.""" diff --git a/plone/app/z3cform/templates/link_input.pt b/plone/app/z3cform/templates/link_input.pt new file mode 100644 index 00000000..3f194134 --- /dev/null +++ b/plone/app/z3cform/templates/link_input.pt @@ -0,0 +1,51 @@ +
+
+ +
+ + + Internal +
+
+ + +
+
+
+ + + External +
+ + +
+
+ + + +
+ +
+
diff --git a/plone/app/z3cform/utils.py b/plone/app/z3cform/utils.py index 154d38e7..f7ee27a2 100644 --- a/plone/app/z3cform/utils.py +++ b/plone/app/z3cform/utils.py @@ -73,3 +73,35 @@ def call_callables(value, *args, **kwargs): for k, v in value.items() } return ret + + +def replace_link_variables_by_paths(context, url): + """Take an `url` and replace the variables "${navigation_root_url}" and + "${portal_url}" by the corresponding paths. `context` is the acquisition + context. + """ + + def _replace_variable_by_path(url, variable, obj): + path = '/'.join(obj.getPhysicalPath()) + return url.replace(variable, path) + + if not url: + return url + + portal_state = context.restrictedTraverse('@@plone_portal_state') + + if '${navigation_root_url}' in url: + url = _replace_variable_by_path( + url, + '${navigation_root_url}', + portal_state.navigation_root() + ) + + if '${portal_url}' in url: + url = _replace_variable_by_path( + url, + '${portal_url}', + portal_state.portal() + ) + + return url diff --git a/plone/app/z3cform/widget.py b/plone/app/z3cform/widget.py index 39d4047d..762f4df4 100644 --- a/plone/app/z3cform/widget.py +++ b/plone/app/z3cform/widget.py @@ -21,6 +21,7 @@ from plone.app.z3cform.interfaces import IAjaxSelectWidget from plone.app.z3cform.interfaces import IDatetimeWidget from plone.app.z3cform.interfaces import IDateWidget +from plone.app.z3cform.interfaces import ILinkWidget from plone.app.z3cform.interfaces import IQueryStringWidget from plone.app.z3cform.interfaces import IRelatedItemsWidget from plone.app.z3cform.interfaces import IRichTextWidget @@ -30,6 +31,7 @@ from plone.registry.interfaces import IRegistry from Products.CMFCore.utils import getToolByName from Products.CMFPlone.interfaces import IEditingSchema +from Products.CMFPlone.utils import safe_unicode from UserDict import UserDict from z3c.form.browser.select import SelectWidget as z3cform_SelectWidget from z3c.form.browser.text import TextWidget as z3cform_TextWidget @@ -42,6 +44,7 @@ from z3c.form.widget import Widget from zope.component import ComponentLookupError from zope.component import getUtility +from zope.component.hooks import getSite from zope.i18n import translate from zope.interface import implementer from zope.interface import implementer_only @@ -686,6 +689,44 @@ def render(self): return super(RichTextWidget, self).render() +@implementer_only(ILinkWidget) +class LinkWidget(z3cform_TextWidget): + """Implementation of enhanced link widget. + + .. note:: + Unlike the others here, this is not a plone.app.widgets based widget + and it uses it's own template. + """ + + def pattern_data(self): + pattern_data = { + 'vocabularyUrl': '{0}/@@getVocabulary?name=plone.app.vocabularies.Catalog'.format( # noqa + getSite().absolute_url(0) + ), + 'maximumSelectionSize': 1 + } + return json.dumps(pattern_data) + + def extract(self, default=NO_VALUE): + form = self.request.form + internal = form.get(self.name + '.internal') + external = form.get(self.name + '.external') + email = form.get(self.name + '.email') + if internal: + url = '${portal_url}/resolveuid/' + internal + elif email: + subject = form.get(self.name + '.subject') + if not subject: + url = 'mailto:' + email + else: + url = 'mailto:{}?subject={}'.format(email, subject) + else: + url = external # the default is `http://` so we land here + if url: + self.request[self.name] = safe_unicode(url) + return super(LinkWidget, self).extract(default=default) + + @implementer(IFieldWidget) def DateFieldWidget(field, request): widget = FieldWidget(field, DateWidget(request)) @@ -731,3 +772,8 @@ def QueryStringFieldWidget(field, request, extra=None): if extra is not None: request = extra return FieldWidget(field, QueryStringWidget(request)) + + +@implementer(IFieldWidget) +def LinkFieldWidget(field, request): + return FieldWidget(field, LinkWidget(request))