Skip to content

Commit

Permalink
Merge pull request #308 from euphorie/quaive-app-for-merge-part2
Browse files Browse the repository at this point in the history
Quaive app for merge part 2: services
  • Loading branch information
ale-rt authored Jan 31, 2025
2 parents 1352f81 + b7d6ea2 commit 26557ca
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Changelog
11.0.0 (unreleased)
-------------------

- Add ``@navtree`` REST API endpoint.
[maurits]

- Add ``@tool-versions`` REST API endpoint.
[maurits]

- Prevent the choice widget to throw the error:
``TypeError: object of type 'CatalogSource' has no len()``
[ale-rt]
Expand Down
1 change: 1 addition & 0 deletions src/osha/oira/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<include package=".nuplone" />
<include package=".upgrade" />
<include package=".statistics" />
<include package=".services" />

<!-- Vocabularies -->
<utility
Expand Down
Empty file.
24 changes: 24 additions & 0 deletions src/osha/oira/services/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:plone="http://namespaces.plone.org/plone"
>

<plone:service
method="GET"
factory=".surveys.ToolVersionsGet"
for="euphorie.content.surveygroup.ISurveyGroup"
permission="zope2.View"
layer="osha.oira.interfaces.IOSHAContentSkinLayer"
name="@tool-versions"
/>

<plone:service
method="GET"
factory=".navigation.NavigationService"
permission="zope2.View"
for="*"
layer="osha.oira.interfaces.IOSHAContentSkinLayer"
name="@navtree"
/>

</configure>
67 changes: 67 additions & 0 deletions src/osha/oira/services/navigation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from plone import api
from plone.restapi.services import Service


class NavigationService(Service):
"""This JSON service reuses the navigation tile to serve through the
REST API the navigation tree of this context.
The nodes of the tree should be prepared to have a successful
JSON serialization.
This method removes the keys:
- brain (which is not serializable)
- parent (which will create a circular reference)
and renames some keys to match the plone.restapi standards:
- portal_type -> @type
- url -> @id
It also fixes the portal_type which is returned normalized
with a dash instead of a dot.
"""

def fix_node(self, node):
"""Prepare a node for serialization"""
banned_keys = [
"brain", # not serializable
"parent", # creates a circular reference
]
for key in banned_keys:
if key in node:
del node[key]

# Rename keys to match plone.restapi standards
mapping = {
"portal_type": "@type",
"url": "@id",
"children": "items",
}
for old_key, new_key in mapping.items():
if old_key in node:
node[new_key] = node.pop(old_key)

# Fix the portal_type
if "@type" in node:
node["@type"] = node["@type"].replace("-", ".")

# Recurse into children
if "items" in node:
for child in node["items"]:
self.fix_node(child)

return node

def reply(self):
"""We use the navtree tile to get the navigation tree,
but we have to fiddle with the nodes to have a proper serialization.
"""
navtree_tile = api.content.get_view("navtree", self.context, self.request)
navtree_tile.update()
tree = [self.fix_node(node) for node in navtree_tile.tree]
return {
"@id": self.request.getURL(),
"items": tree,
}
85 changes: 85 additions & 0 deletions src/osha/oira/services/surveys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from Acquisition import aq_base
from Acquisition import aq_inner
from plone import api
from plone.base.utils import base_hasattr
from plone.restapi.serializer.converters import json_compatible
from plone.restapi.services import Service


class ToolVersionsGet(Service):
"""Get info from the oira tool (survey group) and its versions (surveys)."""

@property
def default_image_url(self):
portal_url = api.portal.get().absolute_url()
return f"{portal_url}/++resource++osha.oira.content/clipboard.svg"

def get_survey_info(self, survey):
# Is this survey the tool version that is published on the client?
published_on_client = self.published_tool_version_id == survey.id
# Note that if 'published_on_client' is true, the review_state
# should be 'published', otherwise 'draft', but this is not always
# in sync. So instead of 'api.content.get_state(obj=survey)',
# let's report what the state is meant to be.
review_state = "published" if published_on_client else "draft"
# The 'published' attribute should be the date of publication of this
# survey, but we could inherit this attribute from the surveygroup,
# where it would contain the id of the client-published tool version.
# So do not inherit this.
published_date = getattr(aq_base(survey), "published", None)
return {
"@id": survey.absolute_url(),
"id": survey.id,
"title": survey.Title(),
"created": json_compatible(survey.created()),
"modified": json_compatible(survey.modified()),
"published": json_compatible(published_date),
"review_state": review_state,
}

def reply(self):
surveygroup = aq_inner(self.context)
# The 'published' attribute of the surveygroup has the id of the tool
# version that is currently published on the OiRA client side.
self.published_tool_version_id = getattr(surveygroup, "published", None)

# First gather info about the survey group.
result = {
"@id": f"{surveygroup.absolute_url()}/@tool-versions",
"id": surveygroup.id,
"title": surveygroup.Title(),
"published_tool_version_id": self.published_tool_version_id,
"@type": surveygroup.portal_type,
# We will try to get the next ones from one of the surveys.
"image_url": "",
"summary": "",
"introduction": "",
}

# Now add info for each tool version.
items = []
surveys = surveygroup.contentValues()
for survey in surveys:
items.append(self.get_survey_info(survey))
result["versions"] = items

# Get some more info from the published survey, or from the first one.
survey = (
surveygroup.get(self.published_tool_version_id)
if self.published_tool_version_id
else None
)
if survey is None and surveys:
survey = surveys[0]
if survey is not None:
if base_hasattr(survey, "image") and survey.image:
# The survey has a not inherited image attribute and it is not empty.
result["image_url"] = f"{survey.absolute_url()}/@@images/image"
if base_hasattr(survey, "introduction"):
result["introduction"] = survey.introduction
# The description field always exists.
result["summary"] = survey.Description()
if not result["image_url"]:
result["image_url"] = self.default_image_url

return result

0 comments on commit 26557ca

Please sign in to comment.