Skip to content

Commit

Permalink
Merge e68ce78 into bc0f5b6
Browse files Browse the repository at this point in the history
  • Loading branch information
gbastien committed Oct 27, 2021
2 parents bc0f5b6 + e68ce78 commit 0e4fbb0
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 24 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Changelog
- Override 'update' and 'workflow transition' to use the uid
[vpiret]

- Added `return_fullobject_after_creation_default`, `True` by default,
to the `imio.restapi` settings panel. This will do the full serialized object to
be returned after an object is created (this is already the current behavior).
When set to False, the summary serialization will be returned.
[gbastien]


1.0a14 (2021-07-16)
-------------------
Expand Down
5 changes: 5 additions & 0 deletions buildout.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ zopesvn = svn://svn.zope.org/repos/main/

[sources]
collective.documentgenerator = git ${remotes:collective}/collective.documentgenerator.git pushurl=${remotes:collective_push}/collective.documentgenerator.git
imio.helpers = git ${remotes:imio}/imio.helpers.git pushurl=${remotes:imio_push}/imio.helpers.git
Products.CPUtils = git ${remotes:imio}/Products.CPUtils.git pushurl=${remotes:imio_push}/Products.CPUtils.git


Expand Down Expand Up @@ -191,3 +192,7 @@ eea.jquery = 9.3
# collective.fingerpointing
zc.lockfile = 1.2.1
file-read-backwards = 1.2.2

# Required by:
# imio.helpers, plone.api>1.9.1
plone.api = 1.10.4
10 changes: 9 additions & 1 deletion src/imio/restapi/locales/en/LC_MESSAGES/imio.restapi.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2020-06-22 11:23+0000\n"
"POT-Creation-Date: 2021-10-26 14:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -39,6 +39,10 @@ msgstr ""
msgid "List of application with who this application will communicate"
msgstr ""

#: ../settings/interfaces.py:61
msgid "Return full object after creation by default?"
msgstr ""

#: ../settings/interfaces.py:23
msgid "The application identifier"
msgstr ""
Expand Down Expand Up @@ -71,6 +75,10 @@ msgstr ""
msgid "Uninstalls the imio.restapi add-on."
msgstr ""

#: ../settings/interfaces.py:62
msgid "When an element is created, check the box if the full object must be returned by default (default is true)"
msgstr ""

#: ../configure.zcml:28
msgid "imio.restapi"
msgstr ""
Expand Down
10 changes: 9 additions & 1 deletion src/imio/restapi/locales/fr/LC_MESSAGES/imio.restapi.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2020-06-22 11:23+0000\n"
"POT-Creation-Date: 2021-10-26 14:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -39,6 +39,10 @@ msgstr "Elément(s) lié(s)"
msgid "List of application with who this application will communicate"
msgstr "Liste des applications avec lesquelles cette application communiquera"

#: ../settings/interfaces.py:61
msgid "Return full object after creation by default?"
msgstr "Lors de la création d'un élément, retourner une sérialisation complète de l'élément créé?"

#: ../settings/interfaces.py:23
msgid "The application identifier"
msgstr "L'identifiant de l'application"
Expand Down Expand Up @@ -71,6 +75,10 @@ msgstr "Cette information est configurée dans le fichier de configuration de l'
msgid "Uninstalls the imio.restapi add-on."
msgstr ""

#: ../settings/interfaces.py:62
msgid "When an element is created, check the box if the full object must be returned by default (default is true)"
msgstr "Cette information est configurée dans le fichier de configuration de l'instance<br>Lorqu'un élément est créé via les WS REST, si la case est cochée (défaut), une sérialisation complète de l'objet créé sera renvoyée au créateur. Si décoché, la version \"sommaire\" est retournée."

#: ../configure.zcml:28
msgid "imio.restapi"
msgstr ""
Expand Down
10 changes: 9 additions & 1 deletion src/imio/restapi/locales/imio.restapi.pot
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2020-06-22 11:23+0000\n"
"POT-Creation-Date: 2021-10-26 14:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
Expand Down Expand Up @@ -42,6 +42,10 @@ msgstr ""
msgid "List of application with who this application will communicate"
msgstr ""

#: ../settings/interfaces.py:61
msgid "Return full object after creation by default?"
msgstr ""

#: ../settings/interfaces.py:23
msgid "The application identifier"
msgstr ""
Expand Down Expand Up @@ -74,6 +78,10 @@ msgstr ""
msgid "Uninstalls the imio.restapi add-on."
msgstr ""

#: ../settings/interfaces.py:62
msgid "When an element is created, check the box if the full object must be returned by default (default is true)"
msgstr ""

#: ../configure.zcml:28
msgid "imio.restapi"
msgstr ""
Expand Down
2 changes: 1 addition & 1 deletion src/imio/restapi/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<version>1000</version>
<version>1001</version>
<dependencies>
<dependency>profile-plone.restapi:default</dependency>
</dependencies>
Expand Down
120 changes: 117 additions & 3 deletions src/imio/restapi/services/add.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
# -*- coding: utf-8 -*-

from Acquisition import aq_base
from Acquisition.interfaces import IAcquirer
from imio.restapi.utils import get_return_fullobject_after_creation_default
from plone import api
from plone.restapi.deserializer import json_body
from plone.restapi.services.content import add
from plone.restapi.exceptions import DeserializationError
from plone.restapi.interfaces import IDeserializeFromJson
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.interfaces import ISerializeToJsonSummary
from plone.restapi.services.content.add import FolderPost
from plone.restapi.services.content.add import PAM_INSTALLED
from plone.restapi.services.content.add import PLONE5
from plone.restapi.services.content.utils import add
from plone.restapi.services.content.utils import create
from Products.CMFPlone.utils import safe_hasattr
from zExceptions import BadRequest
from zExceptions import Unauthorized
from zope.component import queryMultiAdapter
from zope.event import notify
from zope.interface import alsoProvides
from zope.lifecycleevent import ObjectCreatedEvent
from ZPublisher.HTTPRequest import HTTPRequest

import json
import plone.protect.interfaces


FILE_DATA_INCOMPLETE_ERROR = (
Expand All @@ -27,7 +45,7 @@ def create_request(base_request, body):
return request


class FolderPost(add.FolderPost):
class FolderPost(FolderPost):
def __init__(self, context, request):
super(FolderPost, self).__init__(context, request)
self.warnings = []
Expand Down Expand Up @@ -95,12 +113,108 @@ def reply(self):
self.request.set("BODY", json.dumps(self.cleaned_data))
return self._reply()

def do_reply(self):
"""This is the reply method from plone.restapi, overrided
to manage returning the fullobject after creation or the summary."""

# imio.restapi, nothing changed until next comment "imio.restapi ..." comment
data = json_body(self.request)

type_ = data.get("@type", None)
id_ = data.get("id", None)
title = data.get("title", None)
translation_of = data.get("translation_of", None)
language = data.get("language", None)

if not type_:
raise BadRequest("Property '@type' is required")

# Disable CSRF protection
if "IDisableCSRFProtection" in dir(plone.protect.interfaces):
alsoProvides(self.request, plone.protect.interfaces.IDisableCSRFProtection)

try:
obj = create(self.context, type_, id_=id_, title=title)
except Unauthorized as exc:
self.request.response.setStatus(403)
return dict(error=dict(type="Forbidden", message=str(exc)))
except BadRequest as exc:
self.request.response.setStatus(400)
return dict(error=dict(type="Bad Request", message=str(exc)))

# Acquisition wrap temporarily to satisfy things like vocabularies
# depending on tools
temporarily_wrapped = False
if IAcquirer.providedBy(obj) and not safe_hasattr(obj, "aq_base"):
obj = obj.__of__(self.context)
temporarily_wrapped = True

# Update fields
deserializer = queryMultiAdapter((obj, self.request), IDeserializeFromJson)
if deserializer is None:
self.request.response.setStatus(501)
return dict(
error=dict(message="Cannot deserialize type {}".format(obj.portal_type))
)

try:
deserializer(validate_all=True, create=True)
except DeserializationError as e:
self.request.response.setStatus(400)
return dict(error=dict(type="DeserializationError", message=str(e)))

if temporarily_wrapped:
obj = aq_base(obj)

if not getattr(deserializer, "notifies_create", False):
notify(ObjectCreatedEvent(obj))

obj = add(self.context, obj, rename=not bool(id_))

# Link translation given the translation_of property
if PAM_INSTALLED and PLONE5:
from plone.app.multilingual.interfaces import (
IPloneAppMultilingualInstalled,
) # noqa
from plone.app.multilingual.interfaces import ITranslationManager

if (
IPloneAppMultilingualInstalled.providedBy(self.request)
and translation_of
and language
):
source = self.get_object(translation_of)
if source:
manager = ITranslationManager(source)
manager.register_translation(language, obj)

self.request.response.setStatus(201)
self.request.response.setHeader("Location", obj.absolute_url())

# imio.restapi, begin changes, manage returning full object or summary
return_full_object = data.get(
"return_fullobject", get_return_fullobject_after_creation_default())
if return_full_object:
serializer = queryMultiAdapter((obj, self.request), ISerializeToJson)
else:
serializer = queryMultiAdapter((obj, self.request), ISerializeToJsonSummary)

serialized_obj = serializer()
# imio.restapi, end changes, manage returning full object or summary

# HypermediaBatch can't determine the correct canonical URL for
# objects that have just been created via POST - so we make sure
# to set it here
serialized_obj["@id"] = obj.absolute_url()

return serialized_obj

def _reply(self):
children = []
if "__children__" in self.data:
children = self.data.pop("__children__")
self.request.set("BODY", json.dumps(self.data))
result = super(FolderPost, self).reply()
result = self.do_reply()
self.wf_transitions(result)
self._after_reply_hook(result)
result["@warnings"] = self.warnings
Expand Down
17 changes: 15 additions & 2 deletions src/imio/restapi/settings/dataprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from imio.restapi.settings.interfaces import ISettings
from plone import api
from plone.restapi.deserializer import boolean_value
from z3c.form.interfaces import IForm
from z3c.form.interfaces import IFormLayer
from z3c.form.interfaces import IValue
Expand All @@ -19,14 +20,21 @@
class SettingsDataProvider(object):
adapts(Interface, IFormLayer, IForm, IField, IWidget)

_env_keys = ("WS_URL", "CLIENT_ID", "APPLICATION_ID", "APPLICATION_URL")
# env vars with name as key and default value as value
_env_keys = {
"WS_URL": "",
"CLIENT_ID": "",
"APPLICATION_ID": "",
"APPLICATION_URL": "",
"RETURN_FULLOBJECT_AFTER_CREATION_DEFAULT": True}

def __init__(self, context, request, form, field, widget):
self.context = context
self.request = request
self.form = form
self.field = field
self.widget = widget
self._values = {}

def get(self):
key = self.field.__name__
Expand All @@ -38,4 +46,9 @@ def get(self):

@property
def values(self):
return {k.lower(): os.getenv(k) for k in self._env_keys}
if not self._values:
self._values = {
k.lower(): boolean_value(os.getenv(k, default))
if isinstance(default, bool) else os.getenv(k, default)
for k, default in self._env_keys.items()}
return self._values
11 changes: 11 additions & 0 deletions src/imio/restapi/settings/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ class ISettings(Interface):
required=False,
)

form.mode(ws_url="return_fullobject_after_creation_default")
return_fullobject_after_creation_default = schema.Bool(
title=_(u"Return full object after creation by default?"),
description=_(
u"When an element is created, check the box if the full object "
u"must be returned by default (default is true)"
),
default=True,
required=False,
)


class ISettingsForm(Interface):
"""Marker interface for the settings form"""
5 changes: 5 additions & 0 deletions src/imio/restapi/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING
from plone.restapi.testing import PLONE_RESTAPI_WORKFLOWS_INTEGRATION_TESTING
from plone.testing import z2
from zope.globalrequest import setLocal

import collective.documentgenerator
import imio.restapi
Expand Down Expand Up @@ -40,6 +41,10 @@ def setUpZope(self, app, configurationContext):
self.loadZCML(package=collective.documentgenerator)

def setUpPloneSite(self, portal):
# necessary for collective.fingerpointing used
# when installing "collective.documentgenerator:demo"
setLocal('request', portal.REQUEST)

applyProfile(portal, "imio.restapi:default")
applyProfile(portal, "collective.documentgenerator:demo")

Expand Down

0 comments on commit 0e4fbb0

Please sign in to comment.