Skip to content

Commit

Permalink
Merge pull request #50 from eea/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
avoinea authored Jan 30, 2025
2 parents 66d87f8 + dd90f46 commit 9305c50
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 11 deletions.
7 changes: 7 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[flake8]
# Recommend matching the black line length (default 88),
# rather than using the flake8 default of 79:
max-line-length = 88
extend-ignore =
# See https://github.com/PyCQA/pycodestyle/issues/373
E203, W503
9 changes: 9 additions & 0 deletions docs/HISTORY.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
=========

6.2 - (2025-01-08)
---------------------------
* Fix: Add serializer for slate and html blocks refs
[dobri1408 - refs #282435]
* Feature: add block transformer for contextNavigation
[nileshgulia1 - refs #282065]
* Feature: add block transformer for versions
[nileshgulia1 - refs #282065]

6.1 - (2024-12-10)
---------------------------
* Fix: large query on context navigation when on layout or add new item.
Expand Down
226 changes: 220 additions & 6 deletions eea/volto/policy/restapi/blocks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,143 @@
""" block-related utils """

"""
Serializers and Deserializers for the blocks of the EEA
"""
import copy
from urllib.parse import urlparse
from bs4 import BeautifulSoup
from plone import api
from plone.restapi.behaviors import IBlocks
from plone.restapi.interfaces import IBlockFieldSerializationTransformer
from plone.restapi.serializer.blocks import (
SlateBlockSerializerBase,
uid_to_url,
)
from plone.restapi.deserializer.utils import path2uid
from zope.component import adapter
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserRequest
from eea.volto.policy.restapi.services.contextnavigation.get import (
EEANavigationPortletRenderer,
eea_extract_data,
IEEANavigationPortlet,
)
try:
from eea.api.versions.browser.relations import EEAVersionsView
except ImportError:
EEAVersionsView = None


def getLink(path):
"""
Get link
"""

URL = urlparse(path)

if URL.netloc.startswith("localhost") and URL.scheme:
return path.replace(URL.scheme + "://" + URL.netloc, "")
return path


class HTMLBlockDeserializerBase:
"""
HTML block Deserializer for the hrefs and src
"""
order = 100
block_type = "html"

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

def __call__(self, block):
raw_html = block.get("html", "")

if not raw_html:
return block

# Parse the HTML using BeautifulSoup
soup = BeautifulSoup(raw_html, "html.parser")

# Resolve all <a> and <img> tags to UIDs
for tag in soup.find_all(["a", "img"]):
if tag.name == "a" and tag.has_attr("href"):

tag["href"] = path2uid(context=self.context, link=tag["href"])

elif tag.name == "img" and tag.has_attr("src"):
tag["src"] = path2uid(context=self.context, link=tag["src"])

# Serialize the modified HTML back into the block
block["html"] = str(soup)
return block

def _convert_to_uid(self, url, is_image=False):
"""
Convert relative or absolute URLs into resolve UID links.
"""
uid = path2uid(self.context, url)
if uid:
return f"/resolveuid/{uid}"
return url


class HTMLBlockSerializerBase:
"""
HTML block Serializer for the hrefs and src
"""
order = 9999
block_type = "html"

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

def __call__(self, block):
block_serializer = copy.deepcopy(block)
raw_html = block_serializer.get("html", "")

if not raw_html:
return block

# Parse the HTML using BeautifulSoup
soup = BeautifulSoup(raw_html, "html.parser")

# Resolve all <a> and <img> tags
for tag in soup.find_all(["a", "img"]):
if tag.name == "a" and tag.has_attr("href"):
tag["href"] = self._resolve_uid(tag["href"])
elif tag.name == "img" and tag.has_attr("src"):
tag["src"] = self._resolve_uid(tag["src"], is_image=True)

# Serialize the modified HTML back into the block
block_serializer["html"] = str(soup)
return block_serializer

def _resolve_uid(self, url, is_image=False):
"""
Convert resolve UID URLs into relative links.
If the URL points to an image, append /@@download/image.
"""
if "/resolveuid/" in url:
resolved_url = uid_to_url(url)
if is_image and resolved_url:
return f"{resolved_url}/@@download/image"
return resolved_url or url
return url


class SlateBlockSerializer(SlateBlockSerializerBase):
"""SlateBlockSerializerBase."""

block_type = "slate"

def handle_img(self, child):
"Serializer for the imgs"
if child.get("url"):
if "resolveuid" in child["url"]:
url = uid_to_url(child["url"])
url = "%s/@@download/image" % url
child["url"] = url


@implementer(IBlockFieldSerializationTransformer)
Expand All @@ -25,8 +157,90 @@ def __call__(self, value):
if not restrictedBlock:
return value
if restrictedBlock and api.user.has_permission(
'EEA: Manage restricted blocks', obj=self.context):
"EEA: Manage restricted blocks", obj=self.context
):
return value
return {
"@type": "empty"
}
return {"@type": "empty"}


@implementer(IBlockFieldSerializationTransformer)
@adapter(IBlocks, IBrowserRequest)
class ContextNavigationBlockSerializationTransformer:
"""ContextNavigation Block serialization"""

order = 9999
block_type = "contextNavigation"

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

def __call__(self, value):
if value.get("variation", None) == "report_navigation":

if (
"root_node" in value and
isinstance(value["root_node"], list) and
len(value["root_node"]) > 0
):
root_nav_item = value["root_node"][0]
url = urlparse(root_nav_item.get("@id", ""))
value["root_path"] = url.path if url.scheme else ""

data = eea_extract_data(IEEANavigationPortlet, value, prefix=None)

renderer = EEANavigationPortletRenderer(
self.context, self.request, data
)
res = renderer.render()
is_data_available = res.get(
"available", True
) # or get res[items]?
value["results"] = is_data_available

return value


@implementer(IBlockFieldSerializationTransformer)
@adapter(IBlocks, IBrowserRequest)
class AllVersionBlockSerializationTransformer:
"""All versions Block serialization"""

order = 9999
block_type = "eea_versions"

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

def __call__(self, value):
if value.get("@type", None) == "eea_versions":
all_versions = EEAVersionsView(self.context, self.request)
results = (
all_versions.newer_versions() or all_versions.older_versions()
)

value["results"] = len(results) > 0
return value


@implementer(IBlockFieldSerializationTransformer)
@adapter(IBlocks, IBrowserRequest)
class LatestVersionBlockSerializationTransformer:
"""Latest versions Block serialization"""

order = 9999
block_type = "eea_latest_version"

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

def __call__(self, value):
if value.get("@type", None) == "eea_latest_version":
all_versions = EEAVersionsView(self.context, self.request)
results = all_versions.newer_versions()

value["results"] = len(results) > 0

return value
37 changes: 36 additions & 1 deletion eea/volto/policy/restapi/configure.zcml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<configure xmlns="http://namespaces.zope.org/zope" xmlns:plone="http://namespaces.plone.org/plone">
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:zcml="http://namespaces.zope.org/zcml">

<include package="plone.restapi" />
<include package=".deserializer" />
<include package=".navigation" />
Expand All @@ -9,5 +12,37 @@
<subscriber
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
factory=".blocks.RestrictedBlockSerializationTransformer" />
<subscriber
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
factory=".blocks.SlateBlockSerializer"
for="plone.restapi.behaviors.IBlocks zope.publisher.interfaces.browser.IBrowserRequest"/>
<!-- Deserializations -->
<subscriber
provides="plone.restapi.interfaces.IBlockFieldDeserializationTransformer"
factory=".blocks.HTMLBlockDeserializerBase"
for="plone.restapi.behaviors.IBlocks zope.publisher.interfaces.browser.IBrowserRequest"/>
<subscriber
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
factory=".blocks.HTMLBlockSerializerBase"
for="plone.restapi.behaviors.IBlocks zope.publisher.interfaces.browser.IBrowserRequest"/>


<subscriber
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
factory=".blocks.ContextNavigationBlockSerializationTransformer" />

<subscriber
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
factory=".blocks.AllVersionBlockSerializationTransformer"
zcml:condition="installed eea.api.versions"
/>

<subscriber
provides="plone.restapi.interfaces.IBlockFieldSerializationTransformer"
factory=".blocks.LatestVersionBlockSerializationTransformer"
zcml:condition="installed eea.api.versions"
/>



</configure>
9 changes: 6 additions & 3 deletions eea/volto/policy/restapi/services/contextnavigation/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,15 @@ def eea_extract_data(_obj_schema, raw_data, prefix):
data = original_get.Data({})
for name in obj_schema_names:
field = obj_schema[name]
raw_value = raw_data.get(prefix + name, field.default)
if prefix:
raw_value = raw_data.get(prefix + name, field.default)
else: # pragma: no cover
raw_value = raw_data.get(name, field.default)

if isinstance(field, schema.Tuple):
value = raw_value.split(',') if ',' in raw_value else raw_value
value = raw_value.split(",") if "," in raw_value else raw_value
else:
value = IFromUnicode(field).fromUnicode(raw_value)
value = IFromUnicode(field).fromUnicode(str(raw_value))

data[name] = value

Expand Down
2 changes: 1 addition & 1 deletion eea/volto/policy/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.1
6.2

0 comments on commit 9305c50

Please sign in to comment.