Skip to content

Commit 41c902e

Browse files
authored
Merge pull request #24 from codesyntax/related-image-everything
feat: dynamically set the value of the related Image, checking the TinyMCE control panel
2 parents 279d9d4 + 0497cb2 commit 41c902e

8 files changed

Lines changed: 248 additions & 7 deletions

File tree

news/24.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dynamically set the value of the related Image, checking the TinyMCE control panel @erral

src/cs_dynamicpages/adapters/__init__.py

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<configure
2+
xmlns="http://namespaces.zope.org/zope"
3+
xmlns:i18n="http://namespaces.zope.org/i18n"
4+
i18n_domain="cs_dynamicpages"
5+
>
6+
7+
<adapter
8+
factory=".patterns.RelatedImageContentbrowserPatternOptions"
9+
name="pattern_options"
10+
/>
11+
12+
</configure>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""
2+
See for some context: https://community.plone.org/t/customizing-contentbrowser-pattern-options/22812
3+
4+
this adapter provides dynamic pattern_options to allow selecting any Image-ish
5+
content-type that previously is enabled in the TinyMCE control-panel as an
6+
"image object"
7+
8+
"""
9+
10+
from ..behaviors.related_image import IImageRelationList
11+
from plone import api
12+
13+
14+
try:
15+
# This is for Plone 6.1
16+
from plone.app.z3cform.interfaces import IContentBrowserWidget
17+
except ImportError:
18+
# This is for previous versions of Plone
19+
from plone.app.z3cform.interfaces import (
20+
IRelatedItemsWidget as IContentBrowserWidget,
21+
)
22+
23+
from z3c.form.interfaces import IValue
24+
from zope.component import adapter
25+
from zope.interface import implementer
26+
from zope.interface import Interface
27+
28+
29+
@implementer(IValue)
30+
@adapter(
31+
Interface, # IContentListingMarker in the original
32+
Interface, # IRequest in the original
33+
Interface, # IForm in the original
34+
IImageRelationList,
35+
IContentBrowserWidget,
36+
)
37+
class RelatedImageContentbrowserPatternOptions:
38+
"""Adapter class for custon pattern options"""
39+
40+
def __init__(self, context, request, form, field, widget):
41+
self.context = context
42+
self.request = request
43+
self.form = form
44+
self.field = field
45+
self.widget = widget
46+
47+
def get(self):
48+
return {
49+
"recentlyUsed": True,
50+
"selectableTypes": api.portal.get_registry_record("plone.image_objects"),
51+
"upload": True,
52+
}

src/cs_dynamicpages/behaviors/related_image.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# from plone.app.z3cform.widgets.contentbrowser import ContentBrowserFieldWidget
1+
"""
2+
See https://community.plone.org/t/customizing-contentbrowser-pattern-options/22812
3+
for some context
4+
5+
"""
6+
27
from cs_dynamicpages import _
38
from plone.autoform import directives as form
49
from plone.autoform.interfaces import IFormFieldProvider
@@ -25,6 +30,15 @@
2530
)
2631

2732

33+
class IImageRelationList(Interface):
34+
pass
35+
36+
37+
@implementer(IImageRelationList)
38+
class ImageRelationList(RelationList):
39+
pass
40+
41+
2842
class IRelatedImageMarker(Interface):
2943
pass
3044

@@ -33,7 +47,7 @@ class IRelatedImageMarker(Interface):
3347
class IRelatedImage(model.Schema):
3448
""" """
3549

36-
related_image = RelationList(
50+
related_image = ImageRelationList(
3751
title=_("Related image"),
3852
description=_("Select the related image that will be shown in this row"),
3953
default=[],
@@ -46,11 +60,6 @@ class IRelatedImage(model.Schema):
4660
"related_image",
4761
RelatedImageFieldWidget,
4862
vocabulary="plone.app.vocabularies.Catalog",
49-
pattern_options={
50-
"recentlyUsed": True,
51-
"selectableTypes": ["Image"],
52-
"upload": True,
53-
},
5463
)
5564
image_position = schema.Choice(
5665
title=_("Image position"),

src/cs_dynamicpages/configure.zcml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838

3939
<include package=".views" />
40+
<include package=".adapters" />
4041

4142

4243

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
from cs_dynamicpages.adapters.patterns import RelatedImageContentbrowserPatternOptions
2+
from cs_dynamicpages.behaviors.related_image import IImageRelationList
3+
from cs_dynamicpages.testing import CS_DYNAMICPAGES_INTEGRATION_TESTING
4+
from plone import api
5+
from plone.app.testing import setRoles
6+
from plone.app.testing import TEST_USER_ID
7+
8+
9+
try:
10+
from plone.app.z3cform.interfaces import IContentBrowserWidget
11+
except ImportError:
12+
from plone.app.z3cform.interfaces import (
13+
IRelatedItemsWidget as IContentBrowserWidget,
14+
)
15+
16+
from z3c.form.interfaces import IValue
17+
from zope.component import getMultiAdapter
18+
from zope.interface import implementer
19+
from zope.interface import Interface
20+
21+
import unittest
22+
23+
24+
@implementer(Interface)
25+
class DummyContext:
26+
pass
27+
28+
29+
@implementer(Interface)
30+
class DummyRequest:
31+
pass
32+
33+
34+
@implementer(Interface)
35+
class DummyForm:
36+
pass
37+
38+
39+
@implementer(IImageRelationList)
40+
class DummyField:
41+
pass
42+
43+
44+
@implementer(IContentBrowserWidget)
45+
class DummyWidget:
46+
pass
47+
48+
49+
class RelatedImageContentbrowserPatternOptionsTest(unittest.TestCase):
50+
layer = CS_DYNAMICPAGES_INTEGRATION_TESTING
51+
52+
def setUp(self):
53+
"""Custom shared utility setup for tests."""
54+
self.portal = self.layer["portal"]
55+
setRoles(self.portal, TEST_USER_ID, ["Manager"])
56+
57+
def test_adapter_registration(self):
58+
context = DummyContext()
59+
request = DummyRequest()
60+
form = DummyForm()
61+
field = DummyField()
62+
widget = DummyWidget()
63+
64+
adapter = getMultiAdapter(
65+
(context, request, form, field, widget), IValue, name="pattern_options"
66+
)
67+
self.assertIsInstance(adapter, RelatedImageContentbrowserPatternOptions)
68+
69+
def test_adapter_get(self):
70+
context = DummyContext()
71+
request = DummyRequest()
72+
form = DummyForm()
73+
field = DummyField()
74+
widget = DummyWidget()
75+
76+
adapter = getMultiAdapter(
77+
(context, request, form, field, widget), IValue, name="pattern_options"
78+
)
79+
80+
api.portal.set_registry_record("plone.image_objects", ["Image"])
81+
self.assertEqual(
82+
adapter.get(),
83+
{
84+
"recentlyUsed": True,
85+
"selectableTypes": ["Image"],
86+
"upload": True,
87+
},
88+
)
89+
90+
api.portal.set_registry_record("plone.image_objects", ["Image", "CustomImage"])
91+
self.assertEqual(
92+
adapter.get(),
93+
{
94+
"recentlyUsed": True,
95+
"selectableTypes": ["Image", "CustomImage"],
96+
"upload": True,
97+
},
98+
)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from cs_dynamicpages.testing import CS_DYNAMICPAGES_FUNCTIONAL_TESTING
2+
from plone import api
3+
from plone.app.testing import setRoles
4+
from plone.app.testing import SITE_OWNER_NAME
5+
from plone.app.testing import SITE_OWNER_PASSWORD
6+
from plone.app.testing import TEST_USER_ID
7+
from plone.testing.zope import Browser
8+
9+
import transaction
10+
import unittest
11+
12+
13+
class RelatedImageFunctionalTest(unittest.TestCase):
14+
layer = CS_DYNAMICPAGES_FUNCTIONAL_TESTING
15+
16+
def setUp(self):
17+
self.portal = self.layer["portal"]
18+
self.request = self.layer["request"]
19+
setRoles(self.portal, TEST_USER_ID, ["Manager"])
20+
21+
# Create a document and apply the behavior to be able to test the form
22+
self.portal.invokeFactory("Document", "doc1", title="Doc 1")
23+
self.doc1 = self.portal["doc1"]
24+
25+
# Apply the behavior to Document type
26+
fti = api.portal.get_tool("portal_types").getTypeInfo("Document")
27+
behaviors = list(fti.behaviors)
28+
behaviors.append("cs_dynamicpages.related_image")
29+
fti.behaviors = tuple(behaviors)
30+
31+
transaction.commit()
32+
33+
self.browser = Browser(self.layer["app"])
34+
self.browser.handleErrors = False
35+
self.browser.addHeader(
36+
"Authorization", f"Basic {SITE_OWNER_NAME}:{SITE_OWNER_PASSWORD}"
37+
)
38+
39+
def test_related_image_pattern_options_in_browser(self):
40+
# We set the registry record
41+
api.portal.set_registry_record("plone.image_objects", ["Image", "CustomImage"])
42+
transaction.commit()
43+
44+
# Open the edit form of the document
45+
self.browser.open(self.doc1.absolute_url() + "/edit")
46+
47+
# The pattern options should be rendered in the HTML for the related_image field
48+
try:
49+
self.assertIn("data-pat-contentbrowser", self.browser.contents)
50+
except AssertionError:
51+
self.assertIn("data-pat-relateditems", self.browser.contents)
52+
53+
self.assertIn(
54+
"&quot;selectableTypes&quot;: [&quot;Image&quot;, &quot;CustomImage&quot;]",
55+
self.browser.contents,
56+
)
57+
58+
# We change the registry record
59+
api.portal.set_registry_record("plone.image_objects", ["Image"])
60+
transaction.commit()
61+
62+
# Open the edit form again
63+
self.browser.open(self.doc1.absolute_url() + "/edit")
64+
65+
# The pattern options should be updated
66+
self.assertIn(
67+
"&quot;selectableTypes&quot;: [&quot;Image&quot;]", self.browser.contents
68+
)

0 commit comments

Comments
 (0)