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

Feedbacks list update #5

Merged
merged 3 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Changelog
1.1.5 (unreleased)
------------------

- Nothing changed yet.

- Feedbacks list update endpoint @@feedback-list.
[folix-01]

1.1.4 (2024-08-21)
------------------
Expand Down
21 changes: 21 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ Search reviews
Right now data is not indexed so search filters does not work. You only need to call search method to get all data.


List update
-----------

PATCH
~~~~~

This endpoint allows update feedbacks by list.
By now you can only change "read" property


Example::

curl http://localhost:8080/Plone/@feedback-list \
-X PATCH \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"101010101": {"read": true},
}'


Installation
------------

Expand Down
8 changes: 8 additions & 0 deletions src/collective/feedback/restapi/services/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,13 @@
layer="collective.feedback.interfaces.ICollectiveFeedbackLayer"
name="@feedback"
/>
<plone:service
method="PATCH"
factory=".list_update.FeedbacListkUpdate"
for="plone.app.layout.navigation.interfaces.INavigationRoot"
permission="collective.feedback.FeedbacksOverview"
layer="collective.feedback.interfaces.ICollectiveFeedbackLayer"
name="@feedback-list"
/>

</configure>
23 changes: 23 additions & 0 deletions src/collective/feedback/restapi/services/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

from collective.feedback.interfaces import ICollectiveFeedbackStore

DEFAULT_SORT_KEY = "date"


@implementer(IPublishTraverse)
class FeedbackGet(Service):
Expand All @@ -35,6 +37,9 @@ def reply(self):
results = self.get_single_object_feedbacks(self.params[0])
else:
results = self.get_data()

results = self.filter_unread(self.sort_results(results))

batch = HypermediaBatch(self.request, results)
data = {
"@id": batch.canonical_url,
Expand All @@ -48,6 +53,24 @@ def reply(self):
data["actions"] = {"can_delete_feedbacks": self.can_delete_feedbacks()}
return data

def sort_results(self, results):
sort_on = self.request.get("sort_on")
sort_order = self.request.get("sort_order", "")

return sorted(
results,
key=lambda item: item.get(sort_on, DEFAULT_SORT_KEY),
reverse=sort_order == "descending",
)

def filter_unread(self, results):
unread = self.request.get("unread")

if unread:
return list(filter(lambda item: not item.get("read"), results))

return results

def can_delete_feedbacks(self):
return api.user.has_permission("collective.feedback: Delete Feedbacks")

Expand Down
64 changes: 64 additions & 0 deletions src/collective/feedback/restapi/services/list_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from plone.protect.interfaces import IDisableCSRFProtection
from plone.restapi.deserializer import json_body
from plone.restapi.services import Service
from zExceptions import BadRequest, NotFound
from zope.component import getUtility
from zope.interface import alsoProvides

from collective.feedback.interfaces import ICollectiveFeedbackStore


class FeedbacListkUpdate(Service):
"""
Service for update feedback to object, you can only update `read` field
"""

def __init__(self, context, request):
super().__init__(context, request)

def reply(self):
alsoProvides(self.request, IDisableCSRFProtection)

tool = getUtility(ICollectiveFeedbackStore)

form_data = self.extract_data(json_body(self.request))

for id, value in form_data.items():
comment = tool.get(id)

if comment.get("error", "") == "NotFound":
raise NotFound()

try:
tool.update(id, value)
except ValueError as e:
self.request.response.setStatus(500)
return dict(
error=dict(
type="InternalServerError",
message=getattr(e, "message", e.__str__()),
)
)

return form_data

def extract_data(self, form_data):
data = {}

for id, value in form_data.items():
try:
self.validate_data(value)
data[int(id)] = {"read": value.get("read")}
except ValueError:
raise BadRequest(f"Bad id={id} format provided")

return data

def validate_data(self, data):
"""
check all required fields and parameters
"""
for field in ["read"]:
value = data.get(field, None)
if value is None:
raise BadRequest("Campo obbligatorio mancante: {}".format(field))
122 changes: 122 additions & 0 deletions src/collective/feedback/tests/test_restapi_services_list_upgdate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
import unittest

import transaction
from plone import api
from plone.app.testing import (
SITE_OWNER_NAME,
SITE_OWNER_PASSWORD,
TEST_USER_ID,
setRoles,
)
from plone.restapi.testing import RelativeSession
from zope.component import getUtility

from collective.feedback.interfaces import ICollectiveFeedbackStore
from collective.feedback.testing import RESTAPI_TESTING


class TestAdd(unittest.TestCase):
layer = RESTAPI_TESTING

def setUp(self):
self.app = self.layer["app"]
self.portal = self.layer["portal"]
self.portal_url = self.portal.absolute_url()
setRoles(self.portal, TEST_USER_ID, ["Manager"])

api.user.create(
email="[email protected]",
username="memberuser",
password="secret!!",
)

self.document = api.content.create(
title="Document", container=self.portal, type="Document"
)
api.content.transition(obj=self.document, transition="publish")

self.private_document = api.content.create(
title="restricted document", container=self.portal, type="Document"
)
transaction.commit()

self.api_session = RelativeSession(self.portal_url)
self.api_session.headers.update({"Accept": "application/json"})
self.api_session.auth = (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)
self.anon_api_session = RelativeSession(self.portal_url)
self.anon_api_session.headers.update({"Accept": "application/json"})

self.url = "{}/@feedback-add".format(self.document.absolute_url())
self.url_private_document = "{}/@feedback-add".format(
self.private_document.absolute_url()
)

def tearDown(self):
self.api_session.close()
self.anon_api_session.close()

def test_correctly_update_data(self):
self.anon_api_session.post(
self.url,
json={"vote": 3, "comment": "i disagree", "honey": ""},
)
self.anon_api_session.post(
self.url,
json={"vote": 2, "comment": "i disagree", "honey": ""},
)
transaction.commit()
tool = getUtility(ICollectiveFeedbackStore)
feedbacks = tool.search()

self.assertEqual(len(feedbacks), 2)

self.api_session.patch(
api.portal.get().absolute_url() + "/@feedback-list",
json={str(feedbacks[0].intid): {"read": True}},
)
transaction.commit()

self.assertTrue(tool.get(feedbacks[0].intid).attrs.get("read"))

def test_unknown_id(self):
self.anon_api_session.post(
self.url,
json={"vote": 3, "comment": "i disagree", "honey": ""},
)
transaction.commit()

tool = getUtility(ICollectiveFeedbackStore)
feedbacks = tool.search()

self.assertEqual(len(feedbacks), 1)

resp = self.api_session.patch(
api.portal.get().absolute_url() + "/@feedback-list",
json={"1111111111": {"read": True}},
)

transaction.commit()

self.assertEqual(resp.status_code, 404)

def test_bad_id(self):
self.anon_api_session.post(
self.url,
json={"vote": 3, "comment": "i disagree", "honey": ""},
)
transaction.commit()

tool = getUtility(ICollectiveFeedbackStore)
feedbacks = tool.search()

self.assertEqual(len(feedbacks), 1)

resp = self.api_session.patch(
api.portal.get().absolute_url() + "/@feedback-list",
json={"fffffffff": {"read": True}},
)

transaction.commit()

self.assertEqual(resp.status_code, 400)
4 changes: 4 additions & 0 deletions test_plone60.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,7 @@ collective.honeypot = 2.1

# Added by buildout at 2024-02-08 21:49:12.973900
zpretty = 3.1.0

# Added by buildout at 2024-10-08 14:48:52.237066
pluggy = 1.5.0
tomli = 2.0.2
Loading