Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add param option to exclude basic metadata from GET content #1660

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs/source/usage/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ For folderish types, their children are automatically included in the response a
To disable the inclusion, add the `GET` parameter `include_items=false` to the URL.

By default, only basic metadata is included.
To exclude basic metadata, add the `GET` parameter `include_basic_metadata=false` to the URL.
To exclude expandable elements, add the `GET` parameter `include_expandable_elements=false` to the URL.
To include additional metadata, you can specify the names of the properties with the `metadata_fields` parameter.
See also {ref}`retrieving-additional-metadata`.

Expand Down
1 change: 1 addition & 0 deletions news/1661.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add param option to exclude basic metadata from GET content - @razvanMiu
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@razvanMiu can you elaborate on your use case to exclude basic metadata from the request? Performance? Security?

162 changes: 123 additions & 39 deletions src/plone/restapi/serializer/dxcontent.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,70 +45,156 @@ class SerializeToJson:
def __init__(self, context, request):
self.context = context
self.request = request
self.metadata_fields = self.getParam("metadata_fields", list)
self.include_basic_metadata = self.getParam(
"include_basic_metadata", bool, True
)
self.include_expandable_elements = self.getParam(
"include_expandable_elements", bool, True
)

self.permission_cache = {}

def can_include_metadata(self, metadata):
if self.include_basic_metadata:
return True
if metadata in self.metadata_fields:
return True
return False

def getVersion(self, version):
if version == "current":
return self.context
else:
repo_tool = getToolByName(self.context, "portal_repository")
return repo_tool.retrieve(self.context, int(version)).object

def __call__(self, version=None, include_items=True):
version = "current" if version is None else version
def getParam(self, param, value_type, default_value=None):
value = self.request.form.get(param, default_value)
if value_type == list and not value:
return []
if value_type == list and type(value) != list:
return [value]
if value_type == bool:
return boolean_value(value)
return value

obj = self.getVersion(version)
def getId(self, obj):
return obj.id

def getTypeTitle(self, obj):
return get_portal_type_title(obj.portal_type)

def getUID(self, obj):
return obj.UID()

def getParent(self, obj):
parent = aq_parent(aq_inner(obj))
parent_summary = getMultiAdapter(
(parent, self.request), ISerializeToJsonSummary
)()
return parent_summary

def getCreated(self, obj):
return json_compatible(obj.created())

def getModified(self, obj):
return json_compatible(obj.modified())

def getLayout(self, **kwargs):
return self.context.getLayout()

def getLock(self, obj):
return lock_info(obj)

def getReviewState(self, obj):
return self._get_workflow_state(obj)

def getAllowDiscussion(self, **kwargs):
return getMultiAdapter(
(self.context, self.request), name="conversation_view"
).enabled()

def getTargetUrl(self, **kwargs):
target_url = getMultiAdapter(
(self.context, self.request), IObjectPrimaryFieldTarget
)()
return target_url

def __call__(self, version=None, include_items=True):
version = "current" if version is None else version

obj = self.getVersion(version)

result = {
# '@context': 'http://www.w3.org/ns/hydra/context.jsonld',
"@id": obj.absolute_url(),
"id": obj.id,
"@type": obj.portal_type,
"type_title": get_portal_type_title(obj.portal_type),
"parent": parent_summary,
"created": json_compatible(obj.created()),
"modified": json_compatible(obj.modified()),
"review_state": self._get_workflow_state(obj),
"UID": obj.UID(),
"version": version,
"layout": self.context.getLayout(),
"is_folderish": False,
}
metadatas = {
# '@context': 'http://www.w3.org/ns/hydra/context.jsonld',
"id": self.getId,
"type_title": self.getTypeTitle,
"UID": self.getUID,
"parent": self.getParent,
"created": self.getCreated,
"modified": self.getModified,
"layout": self.getLayout,
# Insert locking information
"lock": self.getLock,
"review_state": self.getReviewState,
"version": version,
}

# Filter basic metadata
for key, value in metadatas.items():
if not self.can_include_metadata(key):
continue
if callable(value):
value = value(obj=obj)
result[key] = value

# Insert next/prev information
try:
nextprevious = NextPrevious(obj)
result.update(
{"previous_item": nextprevious.previous, "next_item": nextprevious.next}
)
except ValueError:
# If we're serializing an old version that was renamed or moved,
# then its id might not be found inside the current object's container.
result.update({"previous_item": {}, "next_item": {}})
if self.can_include_metadata("previous_item") or self.can_include_metadata(
"next_item"
):
try:
nextprevious = NextPrevious(obj)
result.update(
{
"previous_item": nextprevious.previous,
"next_item": nextprevious.next,
}
)
except ValueError:
# If we're serializing an old version that was renamed or moved,
# then its id might not be found inside the current object's container.
result.update({"previous_item": {}, "next_item": {}})

# Insert working copy information
if WorkingCopyInfo is not None:
baseline, working_copy = WorkingCopyInfo(
self.context
).get_working_copy_info()
result.update({"working_copy": working_copy, "working_copy_of": baseline})

# Insert locking information
result.update({"lock": lock_info(obj)})
if self.can_include_metadata("working_copy"):
if WorkingCopyInfo is not None:
baseline, working_copy = WorkingCopyInfo(
self.context
).get_working_copy_info()
result.update(
{
"working_copy": working_copy,
"working_copy_of": baseline,
}
)

# Insert expandable elements
result.update(expandable_elements(self.context, self.request))
if self.include_expandable_elements:
result.update(expandable_elements(self.context, self.request))

# Insert field values
for schema in iterSchemata(self.context):
read_permissions = mergedTaggedValueDict(schema, READ_PERMISSIONS_KEY)

for name, field in getFields(schema).items():
if not self.can_include_metadata(name):
continue
if not self.check_permission(read_permissions.get(name), obj):
continue

Expand All @@ -119,15 +205,13 @@ def __call__(self, version=None, include_items=True):
value = serializer()
result[json_compatible(name)] = value

target_url = getMultiAdapter(
(self.context, self.request), IObjectPrimaryFieldTarget
)()
if target_url:
result["targetUrl"] = target_url
if self.can_include_metadata("targetUrl"):
targetUrl = self.getTargetUrl()
if targetUrl:
result["targetUrl"] = targetUrl

result["allow_discussion"] = getMultiAdapter(
(self.context, self.request), name="conversation_view"
).enabled()
if self.can_include_metadata("allow_discussion"):
result["allow_discussion"] = self.getAllowDiscussion()

return result

Expand Down