Skip to content
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 news/1998.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for plate block from `@kitconcept/volto-plate` (text indexer, resolveuid transforms, link integrity). @davisagli
13 changes: 13 additions & 0 deletions src/plone/restapi/blocks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections import deque
from plone.restapi.interfaces import IBlockVisitor
from zope.component import adapter
from zope.component import subscribers
Expand Down Expand Up @@ -72,3 +73,15 @@ def __call__(self, block_value):
yield from block_value["data"]["blocks"].values()
if "blocks" in block_value:
yield from block_value["blocks"].values()
if block_value.get("@type") == "__somersault__":
yield from self.visit_plate_value(block_value.get("value", []))

def visit_plate_value(self, value):
queue = deque(value)
while queue:
child = queue.pop()
if isinstance(child, dict):
if "@type" in child:
yield child
elif child.get("children", []):
queue.extend(child["children"] or [])
26 changes: 20 additions & 6 deletions src/plone/restapi/blocks_linkintegrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,7 @@ def __call__(self, block):
return links


@adapter(IBlocks, IBrowserRequest)
@implementer(IBlockFieldLinkIntegrityRetriever)
class SlateBlockLinksRetriever:
order = 100
block_type = "slate"
class BaseSlateOrPlateBlockLinksRetriever:
field = "value"

def __init__(self, context, request):
Expand All @@ -77,9 +73,16 @@ def __call__(self, block):
value = handler(child)
if value:
self.links.append(value)

return self.links


@adapter(IBlocks, IBrowserRequest)
@implementer(IBlockFieldLinkIntegrityRetriever)
class SlateBlockLinksRetriever(BaseSlateOrPlateBlockLinksRetriever):
order = 100
block_type = "slate"
field = "value"

def handle_a(self, child):
data = child.get("data", {})
if data.get("link", {}).get("internal", {}).get("internal_link"):
Expand All @@ -92,6 +95,17 @@ def handle_link(self, child):
return child["data"]["url"]


@adapter(IBlocks, IBrowserRequest)
@implementer(IBlockFieldLinkIntegrityRetriever)
class PlateBlockLinksRetriever(BaseSlateOrPlateBlockLinksRetriever):
order = 100
block_type = "__somersault__"
field = "value"

def handle_a(self, child):
return child.get("url")


@adapter(IBlocks, IBrowserRequest)
@implementer(IBlockFieldLinkIntegrityRetriever)
class GenericBlockLinksRetriever:
Expand Down
4 changes: 4 additions & 0 deletions src/plone/restapi/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,9 @@
factory=".blocks_linkintegrity.SlateBlockLinksRetriever"
provides="plone.restapi.interfaces.IBlockFieldLinkIntegrityRetriever"
/>
<subscriber
factory=".blocks_linkintegrity.PlateBlockLinksRetriever"
provides="plone.restapi.interfaces.IBlockFieldLinkIntegrityRetriever"
/>

</configure>
56 changes: 42 additions & 14 deletions src/plone/restapi/indexers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
from plone.indexer.decorator import indexer
from plone.restapi import HAS_PLONE_6
from plone.restapi.behaviors import IBlocks
from plone.restapi.blocks import visit_blocks
from plone.restapi.blocks import visit_subblocks
from plone.restapi.interfaces import IBlockSearchableText
from typing import List
from zope.component import adapter
from zope.component import queryMultiAdapter
from zope.globalrequest import getRequest
Expand Down Expand Up @@ -65,6 +67,40 @@ def __call__(self, block):
return block.get("plaintext", "")


@implementer(IBlockSearchableText)
@adapter(IBlocks, IBrowserRequest)
class PlateTextIndexer:
"""Searchable Text indexer for plate blocks."""

def __init__(self, context, request):
self.context = context
self.request = request

def __call__(self, block) -> str:
texts = [self.extract_plate_text(block["value"])]
for subblock in visit_subblocks(self.context, block):
texts.append(extract_text(subblock, self.context, self.request))
result = text_strip(texts)
print(result)
return result

def extract_plate_text(self, value) -> str:
if isinstance(value, list):
return " ".join(self.extract_plate_text(item) for item in value)
elif isinstance(value, dict):
if "@type" in value:
# sub-block, will be processed via visit_blocks
return ""
texts = []
for key in ("text", "children"):
if key in value:
texts.append(self.extract_plate_text(value[key]))
return " ".join(texts)
elif isinstance(value, str):
return value.strip()
return ""


def extract_text(block, obj, request):
"""Extract text information from a block.

Expand Down Expand Up @@ -93,23 +129,16 @@ def extract_text(block, obj, request):
# Use server side adapters to extract the text data
adapter = queryMultiAdapter((obj, request), IBlockSearchableText, name=block_type)
result = adapter(block) if adapter is not None else ""
if not result:
for subblock in visit_subblocks(obj, block):
tmp_result = extract_text(subblock, obj, request)
result = f"{result}\n{tmp_result}"
return result


def get_blocks_text(obj):
def get_blocks_text(obj) -> List[str]:
"""Extract text to be used by the SearchableText index in the Catalog."""
request = getRequest()
blocks = obj.blocks
blocks_layout = obj.blocks_layout
blocks_text = []
for block_id in blocks_layout.get("items", []):
block = blocks.get(block_id, {})
blocks_text.append(extract_text(block, obj, request))
return blocks_text
texts = []
for block in visit_blocks(obj, obj.blocks):
texts.append(extract_text(block, obj, request))
return texts


def text_strip(text_list):
Expand Down Expand Up @@ -139,5 +168,4 @@ def SearchableText_blocks(obj):
blocks_text = get_blocks_text(obj)
# Extract text using the base plone.app.contenttypes indexer
std_text = SearchableText(obj)
blocks_text.append(std_text)
return text_strip(blocks_text)
return text_strip([std_text] + blocks_text)
4 changes: 4 additions & 0 deletions src/plone/restapi/indexers.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@
factory=".indexers.TableBlockSearchableText"
name="table"
/>
<adapter
factory=".indexers.PlateTextIndexer"
name="__somersault__"
/>

</configure>
18 changes: 18 additions & 0 deletions src/plone/restapi/tests/test_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,21 @@ def test_visit_blocks(self):
visited.append(block["@id"])
# depth-first traversal
self.assertEqual(visited, ["block2", "block1"])

def test_visit_blocks_in_plate_value(self):
visited = []
blocks = {
"__somersault__": {
"@type": "__somersault__",
"value": [
{
"type": "p",
"children": [{"@type": "image"}],
}
],
}
}
for block in visit_blocks(self.doc, blocks):
visited.append(block["@type"])
# depth-first traversal
self.assertEqual(visited, ["image", "__somersault__"])
30 changes: 30 additions & 0 deletions src/plone/restapi/tests/test_blocks_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,3 +738,33 @@ def test_deserializer_resolve_path_also_if_it_is_an_alias(self):
res = self.deserialize(blocks=blocks)
link = res.blocks["abc"]["href"]
self.assertEqual(link, f"../resolveuid/{self.portal['renamed-doc'].UID()}")

def test_plate_internal_link_deserializer(self):
target_url = self.image.absolute_url()
blocks = {
"__somersault__": {
"@type": "__somersault__",
"value": [
{
"blockWidth": "default",
"children": [
{"text": ""},
{
"blockWidth": "default",
"children": [{"text": "This is a link"}],
"id": "LR0yRMwNPU",
"type": "a",
"url": target_url,
},
{"text": ""},
],
"id": "IR4xX0-rK0",
"type": "p",
},
],
},
}
res = self.deserialize(blocks=blocks)
value = res.blocks["__somersault__"]["value"]
link = value[0]["children"][1]["url"]
self.assertTrue(link.startswith("../resolveuid/"))
33 changes: 33 additions & 0 deletions src/plone/restapi/tests/test_blocks_linkintegrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,39 @@ def test_links_retriever_skip_empty_links(self):

self.assertEqual(len(value), 0)

def test_links_retriever_return_internal_links_type_a_in_plate_block(self):
uid = IUUID(self.doc2)
resolve_uid_link = f"../resolveuid/{uid}"
blocks = {
"__somersault__": {
"@type": "__somersault__",
"value": [
{
"blockWidth": "default",
"children": [
{"text": ""},
{
"blockWidth": "default",
"children": [{"text": "This is a link"}],
"id": "LR0yRMwNPU",
"type": "a",
"url": resolve_uid_link,
},
{"text": ""},
],
"id": "IR4xX0-rK0",
"type": "p",
},
],
},
}

self.portal.doc1.blocks = blocks
value = self.retrieve_links(blocks)

self.assertEqual(len(value), 1)
self.assertIn(resolve_uid_link, value)


class TestLinkintegrityForBlocks(TestCase):

Expand Down
35 changes: 35 additions & 0 deletions src/plone/restapi/tests/test_blocks_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,38 @@ def test_teaser_block_serializer_legacy(self):
self.assertEqual(block["description"], "Custom description")
href = block["href"][0]
self.assertEqual(href["@id"], doc.absolute_url())

def test_plate_internal_link_serializer(self):
target_item = self.image
resolveuid_link = f"../resolveuid/{target_item.UID()}"
blocks = {
"__somersault__": {
"@type": "__somersault__",
"value": [
{
"blockWidth": "default",
"children": [
{"text": ""},
{
"blockWidth": "default",
"children": [{"text": "This is a link"}],
"id": "LR0yRMwNPU",
"type": "a",
"url": resolveuid_link,
},
{"text": ""},
],
"id": "IR4xX0-rK0",
"type": "p",
},
],
},
}

res = self.serialize(
context=self.portal["doc1"],
blocks=blocks,
)
value = res["__somersault__"]["value"]
link = value[0]["children"][1]["url"]
self.assertTrue(link == target_item.absolute_url())
Loading