Skip to content

Commit

Permalink
add recordsproxy to controlpanel serializer so it handles schemas wit…
Browse files Browse the repository at this point in the history
…h new fields by returning field default
  • Loading branch information
djay committed Nov 24, 2023
1 parent 30102e8 commit b07aad7
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 2 deletions.
47 changes: 46 additions & 1 deletion src/plone/restapi/serializer/controlpanels/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from plone.dexterity.interfaces import IDexterityContent
from plone.registry.interfaces import IRegistry
from plone.registry.interfaces import IRecordsProxy
from plone.registry.recordsproxy import RecordsProxy
from plone.restapi.controlpanels import IControlpanel
from plone.restapi.interfaces import IFieldSerializer
from plone.restapi.interfaces import ISerializeToJson
Expand All @@ -12,12 +14,15 @@
from zope.interface import alsoProvides
from zope.interface import implementer
from zope.interface import noLongerProvides
from zope.schema.interfaces import IField

import zope.schema


SERVICE_ID = "@controlpanels"

_marker = object()


@implementer(ISerializeToJsonSummary)
@adapter(IControlpanel)
Expand Down Expand Up @@ -82,7 +87,14 @@ def __call__(self):
self.controlpanel, self.controlpanel.context, self.controlpanel.request
)

proxy = self.registry.forInterface(self.schema, prefix=self.schema_prefix)
# We use a special proxy and check=False just in case the schema has a new field before an upgrade happens
# Note this doesn't yet handle if a schema field changes type, or the options change
proxy = self.registry.forInterface(
self.schema,
prefix=self.schema_prefix,
check=False,
factory=DefaultRecordsProxy,
)

# Temporarily provide IDexterityContent, so we can use DX field
# serializers
Expand Down Expand Up @@ -113,3 +125,36 @@ def __call__(self):
"schema": json_schema,
"data": json_data,
}


@implementer(IRecordsProxy)
class DefaultRecordsProxy(RecordsProxy):
"""Modified RecordsProxy which returns defaults if values missing"""

def __getattr__(self, name):
if not self.__dict__ or name in self.__dict__.keys():
return super(RecordsProxy, self).__getattr__(name)
if name not in self.__schema__:
raise AttributeError(name)
value = self.__registry__.get(self.__prefix__ + name, _marker)
if value is _marker:
# Instead of returning missing_value we return the default
# so if this is an upgrade the registry will eventually be
# set with the default on next save.
field = self.__schema__[name]
if IField.providedBy(field):
field = field.bind(self)
value = field.default
else:
value = self.__schema__[name].default

return value

def __setattr__(self, name, value):
if name in self.__schema__:
full_name = self.__prefix__ + name
# if full_name not in self.__registry__:
# raise AttributeError(name)
self.__registry__[full_name] = value
else:
self.__dict__[name] = value
41 changes: 40 additions & 1 deletion src/plone/restapi/tests/test_services_controlpanels.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
from plone.app.testing import TEST_USER_ID
from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
from plone.restapi.testing import RelativeSession
from zope import schema
from zope.component import getUtility
from plone.registry.interfaces import IRegistry
import transaction


import unittest


class TestControlpanelsEndpoint(unittest.TestCase):

layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING

def setUp(self):
Expand Down Expand Up @@ -119,3 +123,38 @@ def test_get_usergroup_control_panel(self):
# This control panel does not exist in Plone 5
response = self.api_session.get("/@controlpanels/usergroup")
self.assertEqual(200, response.status_code)

def test_get_schema_with_new_field(self):
# simulate startup with a change registry schema to ensure it doesn't break

registry = getUtility(IRegistry)
registry.records._values[
"plone.ext_editor"
] = True # ensure it's not the default
transaction.commit()

response = self.api_session.get("/@controlpanels/editing")
old_data = response.json()["data"]
self.assertEquals(old_data["ext_editor"], True)

# It's too hard to add another field so lets delete the registry data to
# simulate what it's like starting when the schema has a field and no
# data in the registry for it
# del registry.records['plone.available_editors']
del registry.records["plone.ext_editor"]
transaction.commit()

response = self.api_session.get("/@controlpanels/editing")
old_data = response.json()["data"]
self.assertEquals(old_data["ext_editor"], False)

# ensure there is no problem trying to set missing registry entries
new_values = {
"ext_editor": not old_data["ext_editor"],
"lock_on_ttw_edit": not old_data["lock_on_ttw_edit"],
}
response = self.api_session.patch("/@controlpanels/editing", json=new_values)

# check if the values changed
response = self.api_session.get("/@controlpanels/editing")
self.assertNotEqual(response.json(), old_data)

0 comments on commit b07aad7

Please sign in to comment.