Skip to content

Commit

Permalink
[fix] add adapter for fix relationfield deserializer (#86)
Browse files Browse the repository at this point in the history
* [fix] add adapter for fix relationfield deserializer

* isort + flake

* updated CHANGES

* use urlparse for parse url of related object

* isort

* test

* black isort flake8

* black + isort
  • Loading branch information
eikichi18 authored Dec 28, 2023
1 parent 221c8e4 commit 887049a
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 0 deletions.
54 changes: 54 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks

default_language_version:
python: python3.9

default_stages: [commit, push]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/psf/black
rev: 23.7.0
hooks:
- id: black
# args: ["--line-length=88", "--check", "--diff", "--force-exclude=migrations", "src/"]
args: ["--line-length=88", "--force-exclude=migrations", "src/"]
types: [python]
entry: black
- repo: https://github.com/PyCQA/flake8.git
rev: "6.1.0"
hooks:
- id: flake8
name: flake8
entry: flake8
types: [python]
args: ["--max-complexity=30", "--max-line-length=88", "--ignore=DJ01,DJ08,W503,ANN101", "--exclude=docs/*", "src/", "setup.py"]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
args: ["--multi-line=3", "--lbt=1", "--trailing-comma", "--force-grid-wrap=0", "--use-parentheses", "--ensure-newline-before-comments", "--line-length=88"]
- repo: local
hooks:
- id: python-check-pdb
name: check pdb
description: 'PDB check inside code'
entry: '^\s?[^#]+\.set_trace\(\)'
language: pygrep
types: [python]
- repo: https://github.com/collective/zpretty
rev: 3.1.0
hooks:
- id: zpretty
name: zpretty
- repo: https://github.com/regebro/pyroma
rev: '4.2'
hooks:
- id: pyroma
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
5.4.1 (unreleased)
------------------

- Fix deserializer for relationfield, use UID instead of @id
[eikichi18]
- Isort
[folix-01]

Expand Down
3 changes: 3 additions & 0 deletions src/redturtle/volto/restapi/deserializer/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
<!-- convert Link widget value in normalized url -->
<adapter factory=".dxfields.LinkTextLineFieldDeserializer" />

<!-- convert related string obj to obj -->
<adapter factory=".relationfield.RelationChoiceFieldDeserializer" />


<adapter factory=".dxfields.DatetimeFieldDeserializer" />

Expand Down
63 changes: 63 additions & 0 deletions src/redturtle/volto/restapi/deserializer/relationfield.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from urllib.parse import urlparse

from plone.dexterity.interfaces import IDexterityContent
from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
from plone.restapi.interfaces import IFieldDeserializer
from Products.CMFCore.utils import getToolByName
from z3c.relationfield.interfaces import IRelationChoice
from zope.component import adapter
from zope.component import getMultiAdapter
from zope.component import queryUtility
from zope.interface import implementer
from zope.intid.interfaces import IIntIds

from redturtle.volto.interfaces import IRedturtleVoltoLayer


@implementer(IFieldDeserializer)
@adapter(IRelationChoice, IDexterityContent, IRedturtleVoltoLayer)
class RelationChoiceFieldDeserializer(DefaultFieldDeserializer):
def __call__(self, value):
obj = None
resolved_by = None

if isinstance(value, dict):
# We are trying to deserialize the output of a serialization
# which is enhanced, extract it and put it on the loop again
value = value["UID"] if value.get("UID", None) else value["@id"]

if isinstance(value, int):
# Resolve by intid
intids = queryUtility(IIntIds)
obj = intids.queryObject(value)
resolved_by = "intid"
elif isinstance(value, str):
portal = getMultiAdapter(
(self.context, self.request), name="plone_portal_state"
).portal()
portal_url = portal.absolute_url()
if value.startswith(portal_url):
# Resolve by URL
obj = portal.restrictedTraverse(urlparse(value).path, None)
resolved_by = "URL"
elif value.startswith("/"):
# Resolve by path
obj = portal.restrictedTraverse(value.lstrip("/"), None)
resolved_by = "path"
else:
# Resolve by UID
catalog = getToolByName(self.context, "portal_catalog")
brain = catalog(UID=value)
if brain:
obj = brain[0].getObject()
resolved_by = "UID"

if obj is None:
self.request.response.setStatus(400)
msg = f"Could not resolve object for {value}"
if resolved_by:
msg += f" (resolved by {resolved_by})"
raise ValueError(msg)

self.field.validate(obj)
return obj
106 changes: 106 additions & 0 deletions src/redturtle/volto/tests/test_dxfield_deserializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import unittest

import transaction

from plone.app.testing import TEST_USER_ID
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import login
from plone.app.testing import setRoles
from plone.dexterity.utils import iterSchemata
from plone.restapi.interfaces import IFieldDeserializer
from Products.CMFCore.utils import getToolByName
from zope.component import getMultiAdapter

from redturtle.volto.testing import REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING


class TestDXFieldDeserializer(unittest.TestCase):
layer = REDTURTLE_VOLTO_API_FUNCTIONAL_TESTING

def setUp(self):
self.app = self.layer["app"]
self.request = self.layer["request"]
self.portal = self.layer["portal"]

self.wftool = getToolByName(self.portal, "portal_workflow")
self.acl_users = getToolByName(self.portal, "acl_users")

self.acl_users.userFolderAddUser("user1", "secret", ["Manager"], [])
self.acl_users.getUser("user1")

login(self.portal, "user1")

# folders
self.portal.invokeFactory("Folder", id="folder", title="Private Folder")
self.portal.folder.invokeFactory(
"Folder", id="otherfolder", title="Other Folder"
)
self.wftool.doActionFor(self.portal.folder.otherfolder, "publish")

self.portal.invokeFactory("Document", id="doc1", title="Test Document")
self.wftool.doActionFor(self.portal.doc1, "publish")

transaction.commit()

def deserialize(self, fieldname, value):
for schema in iterSchemata(self.portal.doc1):
if fieldname in schema:
field = schema.get(fieldname)
break
deserializer = getMultiAdapter(
(field, self.portal.doc1, self.request), IFieldDeserializer
)
return deserializer(value)

def test_relationlist_deserialization_broke_with_path(self):
# documents
doc2 = self.portal.folder.otherfolder[
self.portal.folder.otherfolder.invokeFactory(
"Document", id="doc2", title="Referenceable Document"
)
]
self.wftool.doActionFor(self.portal.folder.otherfolder.doc2, "publish")
doc3 = self.portal.folder.otherfolder[
self.portal.folder.otherfolder.invokeFactory(
"Document", id="doc3", title="Referenceable Document"
)
]
self.wftool.doActionFor(self.portal.folder.otherfolder.doc3, "publish")

setRoles(self.portal, TEST_USER_ID, ["Member"])
login(self.portal, TEST_USER_NAME)

try:
value = self.deserialize(
"relatedItems",
[str(doc2.absolute_url_path()), str(doc3.absolute_url_path())],
)
except ValueError:
value = None

self.assertIsNone(value)

def test_relationlist_deserialization_correct_with_uid(self):
# documents
doc2 = self.portal.folder.otherfolder[
self.portal.folder.otherfolder.invokeFactory(
"Document", id="doc2", title="Referenceable Document"
)
]
self.wftool.doActionFor(self.portal.folder.otherfolder.doc2, "publish")
doc3 = self.portal.folder.otherfolder[
self.portal.folder.otherfolder.invokeFactory(
"Document", id="doc3", title="Referenceable Document"
)
]
self.wftool.doActionFor(self.portal.folder.otherfolder.doc3, "publish")

setRoles(self.portal, TEST_USER_ID, ["Member"])
login(self.portal, TEST_USER_NAME)

value = self.deserialize(
"relatedItems",
[str(doc2.UID()), str(doc3.UID())],
)

self.assertTrue(value)

0 comments on commit 887049a

Please sign in to comment.