From 5d60a839bce079e501a36996d15399a5fba2cd76 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Jul 2024 11:00:10 +0200 Subject: [PATCH 01/25] Add 'read' field to the comment --- CHANGES.rst | 3 ++- src/collective/feedback/storage/store.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 701e29b..97e95e9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,8 @@ Changelog 1.1.4 (unreleased) ------------------ -- Nothing changed yet. +- Add read field to the comment. + [folix-01] 1.1.3 (2024-04-29) diff --git a/src/collective/feedback/storage/store.py b/src/collective/feedback/storage/store.py index dd0805b..f371005 100644 --- a/src/collective/feedback/storage/store.py +++ b/src/collective/feedback/storage/store.py @@ -21,7 +21,7 @@ class CollectiveFeedbackStore(object): """Store class for collective.feedback catalog soup""" - fields = ["uid", "url", "title", "comment", "vote", "answer", "date"] + fields = ["uid", "url", "title", "comment", "vote", "answer", "date", "read"] text_index = "title" indexes = ["title", "vote", "uid"] keyword_indexes = [] From a341fa21443399799b519e451abf6cc0e3961777 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:32:32 +0200 Subject: [PATCH 02/25] Update comment endpoint --- CHANGES.rst | 3 + src/collective/feedback/permissions.zcml | 4 + .../feedback/profiles/default/rolemap.xml | 7 +- .../feedback/restapi/services/configure.zcml | 8 ++ .../feedback/restapi/services/get.py | 3 + .../feedback/restapi/services/update.py | 79 +++++++++++++++++++ src/collective/feedback/storage/store.py | 16 +++- src/collective/feedback/upgrades.zcml | 10 +++ 8 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 src/collective/feedback/restapi/services/update.py diff --git a/CHANGES.rst b/CHANGES.rst index 97e95e9..1d3dcc6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog 1.1.4 (unreleased) ------------------ +- Add feedback update endpoint. + [folix-01] + - Add read field to the comment. [folix-01] diff --git a/src/collective/feedback/permissions.zcml b/src/collective/feedback/permissions.zcml index ee69ca2..1368e60 100644 --- a/src/collective/feedback/permissions.zcml +++ b/src/collective/feedback/permissions.zcml @@ -21,6 +21,10 @@ id="collective.feedback.ShowDeletedFeedbacks" title="collective.feedback: Show Deleted Feedbacks" /> + diff --git a/src/collective/feedback/profiles/default/rolemap.xml b/src/collective/feedback/profiles/default/rolemap.xml index cfdb62b..62639c6 100644 --- a/src/collective/feedback/profiles/default/rolemap.xml +++ b/src/collective/feedback/profiles/default/rolemap.xml @@ -25,6 +25,11 @@ - + + + + + + diff --git a/src/collective/feedback/restapi/services/configure.zcml b/src/collective/feedback/restapi/services/configure.zcml index c80e9b3..402ba97 100644 --- a/src/collective/feedback/restapi/services/configure.zcml +++ b/src/collective/feedback/restapi/services/configure.zcml @@ -37,5 +37,13 @@ layer="collective.feedback.interfaces.ICollectiveFeedbackLayer" name="@feedback-delete" /> + diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index 41d7ccc..a664e92 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -135,6 +135,8 @@ def get_data(self): uid = feedback._attrs.get("uid", "") date = feedback._attrs.get("date", "") vote = feedback._attrs.get("vote", "") + id = feedback.id + if uid not in feedbacks: obj = self.get_commented_obj(uid=uid) if not obj and not api.user.has_permission( @@ -148,6 +150,7 @@ def get_data(self): "comments": 0, "title": feedback._attrs.get("title", ""), "uid": uid, + "id": id, } if obj: diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py new file mode 100644 index 0000000..c828e5e --- /dev/null +++ b/src/collective/feedback/restapi/services/update.py @@ -0,0 +1,79 @@ +from collective.feedback.interfaces import ICollectiveFeedbackStore +from plone import api +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, implementer +from zope.publisher.interfaces import IPublishTraverse + + +@implementer(IPublishTraverse) +class FeedbackUpdate(Service): + """ + Service for update feedback to object, you can only update `read` field + """ + + def __init__(self, context, request): + super().__init__(context, request) + self.params = [] + + def publishTraverse(self, request, id): + # Consume any path segments after /@users as parameters + self.params.append(id) + return self + + def reply(self): + alsoProvides(self.request, IDisableCSRFProtection) + + if self.params: + id = self.params[0] + + comment = tool.get(id) + + if comment.get("error", "") == "NotFound": + raise NotFound(f"Object with id={id} not found") + + form_data = json_body(self.request) + + self.validate_form(form_data=form_data) + + form_data = self.extract_data(form_data) + + tool = getUtility(ICollectiveFeedbackStore) + + try: + res = tool.update(id, form_data) + except ValueError as e: + self.request.response.setStatus(500) + return dict( + error=dict( + type="InternalServerError", + message=getattr(e, "message", e.__str__()), + ) + ) + + if res == None: + return self.reply_no_content() + + self.request.response.setStatus(500) + + return dict( + error=dict( + type="InternalServerError", + message="Unable to add. Contact site manager.", + ) + ) + + def extract_data(sefl, form_data): + return {"read": form_data.get("read")} + + def validate_form(self, form_data): + """ + check all required fields and parameters + """ + for field in ["read"]: + value = form_data.get(field, "") + if not value: + raise BadRequest("Campo obbligatorio mancante: {}".format(field)) diff --git a/src/collective/feedback/storage/store.py b/src/collective/feedback/storage/store.py index f371005..c701c90 100644 --- a/src/collective/feedback/storage/store.py +++ b/src/collective/feedback/storage/store.py @@ -83,10 +83,10 @@ def parse_query_params(self, index, value): # return "{} in any('{}')".format(index, value) else: return Eq(index, value) - if isinstance(value, int): - return "{} == {}".format(index, value) - else: - return "{} == '{}'".format(index, value) + # if isinstance(value, int): + # return "{} == {}".format(index, value) + # else: + # return "{} == '{}'".format(index, value) def get_record(self, id): if isinstance(id, six.text_type) or isinstance(id, str): @@ -113,6 +113,7 @@ def update(self, id, data): else: record.attrs[k] = v + self.soup.reindex(records=[record]) def delete(self, id): @@ -123,5 +124,12 @@ def delete(self, id): return {"error": "NotFound"} del self.soup[record] + def get(self, id): + try: + return self.soup.get(id) + except KeyError: + logger.error('[GET] Subscription with id "{}" not found.'.format(id)) + return {"error": "NotFound"} + def clear(self): self.soup.clear() diff --git a/src/collective/feedback/upgrades.zcml b/src/collective/feedback/upgrades.zcml index bb953d3..4b23689 100644 --- a/src/collective/feedback/upgrades.zcml +++ b/src/collective/feedback/upgrades.zcml @@ -37,4 +37,14 @@ handler=".upgrades.update_actions" /> + + + From 2fa30ac9260c8c20bb0a84b1e88b3f725151e291 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:46:25 +0200 Subject: [PATCH 03/25] fix attr name --- src/collective/feedback/restapi/services/get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index a664e92..b0db8f1 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -135,7 +135,7 @@ def get_data(self): uid = feedback._attrs.get("uid", "") date = feedback._attrs.get("date", "") vote = feedback._attrs.get("vote", "") - id = feedback.id + id = feedback.intid if uid not in feedbacks: obj = self.get_commented_obj(uid=uid) From 218a4edf9d6dcbcb0a8567768b29bd3bd37a7fba Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:50:11 +0200 Subject: [PATCH 04/25] fix order --- src/collective/feedback/restapi/services/update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py index c828e5e..d6f3381 100644 --- a/src/collective/feedback/restapi/services/update.py +++ b/src/collective/feedback/restapi/services/update.py @@ -27,6 +27,8 @@ def publishTraverse(self, request, id): def reply(self): alsoProvides(self.request, IDisableCSRFProtection) + tool = getUtility(ICollectiveFeedbackStore) + if self.params: id = self.params[0] @@ -41,8 +43,6 @@ def reply(self): form_data = self.extract_data(form_data) - tool = getUtility(ICollectiveFeedbackStore) - try: res = tool.update(id, form_data) except ValueError as e: From e4d6c792e76985882fb49922ca499768d69c2a66 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:52:13 +0200 Subject: [PATCH 05/25] Fix --- src/collective/feedback/restapi/services/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py index d6f3381..d55ea7c 100644 --- a/src/collective/feedback/restapi/services/update.py +++ b/src/collective/feedback/restapi/services/update.py @@ -35,7 +35,7 @@ def reply(self): comment = tool.get(id) if comment.get("error", "") == "NotFound": - raise NotFound(f"Object with id={id} not found") + raise NotFound() form_data = json_body(self.request) From d6d555d40b232469e0ccca6686671c10c052a732 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:52:50 +0200 Subject: [PATCH 06/25] formatting --- .python-version | 1 + src/collective/feedback/__init__.py | 1 - src/collective/feedback/locales/update.py | 2 +- .../feedback/restapi/services/add.py | 3 ++- .../feedback/restapi/services/delete.py | 6 ++--- .../feedback/restapi/services/get.py | 7 +++--- .../feedback/restapi/services/update.py | 3 ++- src/collective/feedback/storage/catalog.py | 3 +-- src/collective/feedback/storage/store.py | 16 +++++--------- src/collective/feedback/testing.py | 17 +++++++------- .../feedback/tests/test_delete_content.py | 22 ++++++++++--------- .../feedback/tests/test_feedbacks_add.py | 19 +++++++++------- .../feedback/tests/test_feedbacks_get.py | 22 ++++++++++--------- src/collective/feedback/tests/test_store.py | 12 +++++----- src/collective/feedback/upgrades.py | 5 ++--- 15 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/src/collective/feedback/__init__.py b/src/collective/feedback/__init__.py index b661503..43b0762 100644 --- a/src/collective/feedback/__init__.py +++ b/src/collective/feedback/__init__.py @@ -2,5 +2,4 @@ """Init and utils.""" from zope.i18nmessageid import MessageFactory - _ = MessageFactory("collective.feedback") diff --git a/src/collective/feedback/locales/update.py b/src/collective/feedback/locales/update.py index ce89f54..9e6a067 100644 --- a/src/collective/feedback/locales/update.py +++ b/src/collective/feedback/locales/update.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- import os -import pkg_resources import subprocess +import pkg_resources domain = "collective.feedback" os.chdir(pkg_resources.resource_filename(domain, "")) diff --git a/src/collective/feedback/restapi/services/add.py b/src/collective/feedback/restapi/services/add.py index 77e418c..2b8151e 100644 --- a/src/collective/feedback/restapi/services/add.py +++ b/src/collective/feedback/restapi/services/add.py @@ -1,4 +1,3 @@ -from collective.feedback.interfaces import ICollectiveFeedbackStore from plone import api from plone.protect.interfaces import IDisableCSRFProtection from plone.restapi.deserializer import json_body @@ -7,6 +6,8 @@ from zope.component import getUtility from zope.interface import alsoProvides +from collective.feedback.interfaces import ICollectiveFeedbackStore + class FeedbackAdd(Service): """ diff --git a/src/collective/feedback/restapi/services/delete.py b/src/collective/feedback/restapi/services/delete.py index 36fd8ff..9afed5f 100644 --- a/src/collective/feedback/restapi/services/delete.py +++ b/src/collective/feedback/restapi/services/delete.py @@ -1,12 +1,12 @@ -from collective.feedback.interfaces import ICollectiveFeedbackStore from plone.protect.interfaces import IDisableCSRFProtection from plone.restapi.services import Service from zExceptions import BadRequest from zope.component import getUtility -from zope.interface import alsoProvides -from zope.interface import implementer +from zope.interface import alsoProvides, implementer from zope.publisher.interfaces import IPublishTraverse +from collective.feedback.interfaces import ICollectiveFeedbackStore + @implementer(IPublishTraverse) class FeedbackDelete(Service): diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index b0db8f1..a32b16f 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -1,7 +1,8 @@ -from AccessControl import Unauthorized -from collective.feedback.interfaces import ICollectiveFeedbackStore +import csv from copy import deepcopy from datetime import datetime + +from AccessControl import Unauthorized from plone import api from plone.restapi.batching import HypermediaBatch from plone.restapi.search.utils import unflatten_dotted_dict @@ -12,7 +13,7 @@ from zope.interface import implementer from zope.publisher.interfaces import IPublishTraverse -import csv +from collective.feedback.interfaces import ICollectiveFeedbackStore @implementer(IPublishTraverse) diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py index d55ea7c..ca4bcfc 100644 --- a/src/collective/feedback/restapi/services/update.py +++ b/src/collective/feedback/restapi/services/update.py @@ -1,4 +1,3 @@ -from collective.feedback.interfaces import ICollectiveFeedbackStore from plone import api from plone.protect.interfaces import IDisableCSRFProtection from plone.restapi.deserializer import json_body @@ -8,6 +7,8 @@ from zope.interface import alsoProvides, implementer from zope.publisher.interfaces import IPublishTraverse +from collective.feedback.interfaces import ICollectiveFeedbackStore + @implementer(IPublishTraverse) class FeedbackUpdate(Service): diff --git a/src/collective/feedback/storage/catalog.py b/src/collective/feedback/storage/catalog.py index e2f6b9a..4266051 100644 --- a/src/collective/feedback/storage/catalog.py +++ b/src/collective/feedback/storage/catalog.py @@ -3,8 +3,7 @@ from repoze.catalog.indexes.field import CatalogFieldIndex from repoze.catalog.indexes.text import CatalogTextIndex from souper.interfaces import ICatalogFactory -from souper.soup import NodeAttributeIndexer -from souper.soup import NodeTextIndexer +from souper.soup import NodeAttributeIndexer, NodeTextIndexer from zope.interface import implementer diff --git a/src/collective/feedback/storage/store.py b/src/collective/feedback/storage/store.py index c701c90..b795ea4 100644 --- a/src/collective/feedback/storage/store.py +++ b/src/collective/feedback/storage/store.py @@ -1,18 +1,14 @@ # -*- coding: utf-8 -*- -from collective.feedback.interfaces import ICollectiveFeedbackStore +import logging from datetime import datetime -from plone import api -from repoze.catalog.query import And -from repoze.catalog.query import Any -from repoze.catalog.query import Contains -from repoze.catalog.query import Eq -from souper.soup import get_soup -from souper.soup import Record -from zope.interface import implementer -import logging import six +from plone import api +from repoze.catalog.query import And, Any, Contains, Eq +from souper.soup import Record, get_soup +from zope.interface import implementer +from collective.feedback.interfaces import ICollectiveFeedbackStore logger = logging.getLogger(__name__) diff --git a/src/collective/feedback/testing.py b/src/collective/feedback/testing.py index a04d653..df70938 100644 --- a/src/collective/feedback/testing.py +++ b/src/collective/feedback/testing.py @@ -1,16 +1,17 @@ -from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE -from plone.app.testing import applyProfile -from plone.app.testing import FunctionalTesting -from plone.app.testing import IntegrationTesting -from plone.app.testing import PloneSandboxLayer -from plone.testing.zope import WSGI_SERVER_FIXTURE - -import collective.feedback import collective.honeypot import collective.honeypot.config import plone.restapi import souper.plone +from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE +from plone.app.testing import ( + FunctionalTesting, + IntegrationTesting, + PloneSandboxLayer, + applyProfile, +) +from plone.testing.zope import WSGI_SERVER_FIXTURE +import collective.feedback collective.honeypot.config.EXTRA_PROTECTED_ACTIONS = set(["feedback-add"]) collective.honeypot.config.HONEYPOT_FIELD = "honey" diff --git a/src/collective/feedback/tests/test_delete_content.py b/src/collective/feedback/tests/test_delete_content.py index a17a00c..d41169e 100644 --- a/src/collective/feedback/tests/test_delete_content.py +++ b/src/collective/feedback/tests/test_delete_content.py @@ -1,19 +1,21 @@ # -*- coding: utf-8 -*- -from collective.feedback.interfaces import ICollectiveFeedbackStore -from collective.feedback.testing import RESTAPI_TESTING +import unittest from datetime import datetime + +import transaction from plone import api -from plone.app.testing import setRoles -from plone.app.testing import SITE_OWNER_NAME -from plone.app.testing import SITE_OWNER_PASSWORD -from plone.app.testing import TEST_USER_ID +from plone.app.testing import ( + SITE_OWNER_NAME, + SITE_OWNER_PASSWORD, + TEST_USER_ID, + setRoles, +) from plone.restapi.testing import RelativeSession -from souper.soup import get_soup -from souper.soup import Record +from souper.soup import Record, get_soup from zope.component import getUtility -import transaction -import unittest +from collective.feedback.interfaces import ICollectiveFeedbackStore +from collective.feedback.testing import RESTAPI_TESTING class TestCustomerSatisfactionGet(unittest.TestCase): diff --git a/src/collective/feedback/tests/test_feedbacks_add.py b/src/collective/feedback/tests/test_feedbacks_add.py index 5d20231..08f9b3c 100644 --- a/src/collective/feedback/tests/test_feedbacks_add.py +++ b/src/collective/feedback/tests/test_feedbacks_add.py @@ -1,16 +1,19 @@ # -*- coding: utf-8 -*- -from collective.feedback.interfaces import ICollectiveFeedbackStore -from collective.feedback.testing import RESTAPI_TESTING +import unittest + +import transaction from plone import api -from plone.app.testing import setRoles -from plone.app.testing import SITE_OWNER_NAME -from plone.app.testing import SITE_OWNER_PASSWORD -from plone.app.testing import TEST_USER_ID +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 -import transaction -import unittest +from collective.feedback.interfaces import ICollectiveFeedbackStore +from collective.feedback.testing import RESTAPI_TESTING class TestAdd(unittest.TestCase): diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index 6f413e0..b505567 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -1,20 +1,22 @@ # -*- coding: utf-8 -*- -from collective.feedback.interfaces import ICollectiveFeedbackStore -from collective.feedback.testing import RESTAPI_TESTING +import unittest from datetime import datetime + +import transaction from plone import api -from plone.app.testing import setRoles -from plone.app.testing import SITE_OWNER_NAME -from plone.app.testing import SITE_OWNER_PASSWORD -from plone.app.testing import TEST_USER_ID +from plone.app.testing import ( + SITE_OWNER_NAME, + SITE_OWNER_PASSWORD, + TEST_USER_ID, + setRoles, +) from plone.restapi.serializer.converters import json_compatible from plone.restapi.testing import RelativeSession -from souper.soup import get_soup -from souper.soup import Record +from souper.soup import Record, get_soup from zope.component import getUtility -import transaction -import unittest +from collective.feedback.interfaces import ICollectiveFeedbackStore +from collective.feedback.testing import RESTAPI_TESTING class TestGet(unittest.TestCase): diff --git a/src/collective/feedback/tests/test_store.py b/src/collective/feedback/tests/test_store.py index 1ffaffa..3a09aa1 100644 --- a/src/collective/feedback/tests/test_store.py +++ b/src/collective/feedback/tests/test_store.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- -from collective.feedback.interfaces import ICollectiveFeedbackStore -from collective.feedback.testing import FUNCTIONAL_TESTING -from plone.app.testing import setRoles -from plone.app.testing import TEST_USER_ID -from zope.component import getUtility +import unittest import transaction -import unittest +from plone.app.testing import TEST_USER_ID, setRoles +from zope.component import getUtility + +from collective.feedback.interfaces import ICollectiveFeedbackStore +from collective.feedback.testing import FUNCTIONAL_TESTING class TestTool(unittest.TestCase): diff --git a/src/collective/feedback/upgrades.py b/src/collective/feedback/upgrades.py index 0f42610..f06ae53 100644 --- a/src/collective/feedback/upgrades.py +++ b/src/collective/feedback/upgrades.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from plone import api -from plone.app.upgrade.utils import installOrReinstallProduct - import logging +from plone import api +from plone.app.upgrade.utils import installOrReinstallProduct logger = logging.getLogger(__name__) From 687dbe9bf65eb03bf25324440ab2256a5f6af3c3 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:53:16 +0200 Subject: [PATCH 07/25] Update version --- src/collective/feedback/profiles/default/metadata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/feedback/profiles/default/metadata.xml b/src/collective/feedback/profiles/default/metadata.xml index 498df83..6e1e374 100644 --- a/src/collective/feedback/profiles/default/metadata.xml +++ b/src/collective/feedback/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 1201 + 1202 profile-plone.restapi:default profile-souper.plone:default From 469c0a56a5dfed99d242507a4b62a73a37ac195e Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:55:02 +0200 Subject: [PATCH 08/25] Fix --- src/collective/feedback/restapi/services/update.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py index ca4bcfc..001a07b 100644 --- a/src/collective/feedback/restapi/services/update.py +++ b/src/collective/feedback/restapi/services/update.py @@ -1,4 +1,3 @@ -from plone import api from plone.protect.interfaces import IDisableCSRFProtection from plone.restapi.deserializer import json_body from plone.restapi.services import Service @@ -55,7 +54,7 @@ def reply(self): ) ) - if res == None: + if res is None: return self.reply_no_content() self.request.response.setStatus(500) From 3a75d4fb397a2579ecf14ac9fb91fbfbe71cc845 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 10:56:43 +0200 Subject: [PATCH 09/25] fix tests --- src/collective/feedback/tests/test_feedbacks_get.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index b505567..6638c8c 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -37,7 +37,7 @@ def add_record(self, date=None, vote="", uid="", comment="", title=""): record.attrs["uid"] = uid if title: record.attrs["title"] = title - soup.add(record) + return soup.add(record) transaction.commit() def setUp(self): @@ -115,7 +115,7 @@ def test_endpoint_returns_data(self): res = response.json() self.assertEqual(res["items_total"], 0) now = datetime.now() - self.add_record(vote=1, comment="is ok", date=now) + id = self.add_record(vote=1, comment="is ok", date=now) response = self.api_session.get(self.url) res = response.json() @@ -126,6 +126,7 @@ def test_endpoint_returns_data(self): [ { "comments": 1, + "id": id, "last_vote": json_compatible(now), "title": "", "uid": "", From c4e3eaa4875212a04c393873eab40e8ee5feb56c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 11:03:23 +0200 Subject: [PATCH 10/25] gitignore --- .gitignore | 1 + .python-version | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3988bfb..481d0ef 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ report.html reports/ venv/ # excludes +.python-version diff --git a/.python-version b/.python-version index 2c07333..f52f52c 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11 +collective.feedback From 6b590f70f9b12059cb4c4ff6e0c1c18de23417ea Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 11:03:35 +0200 Subject: [PATCH 11/25] remove python verison file --- .python-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .python-version diff --git a/.python-version b/.python-version deleted file mode 100644 index f52f52c..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -collective.feedback From f958752a60b490b6baf7f0213321ee0c4aedb0d8 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 11:29:38 +0200 Subject: [PATCH 12/25] Fix tests --- src/collective/feedback/tests/test_feedbacks_get.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index 6638c8c..193940a 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -117,6 +117,8 @@ def test_endpoint_returns_data(self): now = datetime.now() id = self.add_record(vote=1, comment="is ok", date=now) + transaction.commit() + response = self.api_session.get(self.url) res = response.json() @@ -160,6 +162,9 @@ def test_global_editor_can_see_all_data(self): comment="ok also for restricted", uid=self.restricted_document.UID(), ) + + transaction.commit() + api_session = RelativeSession(self.portal_url) api_session.headers.update({"Accept": "application/json"}) api_session.auth = ("global", "secret!!") @@ -180,6 +185,9 @@ def test_local_editor_can_see_only_data_for_his_contents(self): comment="ok also for restricted", uid=self.restricted_document.UID(), ) + + transaction.commit() + api_session = RelativeSession(self.portal_url) api_session.headers.update({"Accept": "application/json"}) api_session.auth = ("local", "secret!!") @@ -200,6 +208,8 @@ def test_only_admins_can_see_deleted_contents(self): uid="qwertyuiop", ) + transaction.commit() + response = self.api_session.get(self.url) res = response.json() self.assertEqual(res["items_total"], 2) From ddd93748fda3bcb3c054570230e67c5b65ddde69 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 11:36:31 +0200 Subject: [PATCH 13/25] Fix tests --- src/collective/feedback/tests/test_feedbacks_get.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index 193940a..902089f 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -37,9 +37,11 @@ def add_record(self, date=None, vote="", uid="", comment="", title=""): record.attrs["uid"] = uid if title: record.attrs["title"] = title - return soup.add(record) + transaction.commit() + return soup.add(record) + def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] @@ -117,8 +119,6 @@ def test_endpoint_returns_data(self): now = datetime.now() id = self.add_record(vote=1, comment="is ok", date=now) - transaction.commit() - response = self.api_session.get(self.url) res = response.json() @@ -163,8 +163,6 @@ def test_global_editor_can_see_all_data(self): uid=self.restricted_document.UID(), ) - transaction.commit() - api_session = RelativeSession(self.portal_url) api_session.headers.update({"Accept": "application/json"}) api_session.auth = ("global", "secret!!") @@ -186,8 +184,6 @@ def test_local_editor_can_see_only_data_for_his_contents(self): uid=self.restricted_document.UID(), ) - transaction.commit() - api_session = RelativeSession(self.portal_url) api_session.headers.update({"Accept": "application/json"}) api_session.auth = ("local", "secret!!") @@ -208,8 +204,6 @@ def test_only_admins_can_see_deleted_contents(self): uid="qwertyuiop", ) - transaction.commit() - response = self.api_session.get(self.url) res = response.json() self.assertEqual(res["items_total"], 2) From 7251c0ab8a7acf46775a3c1b0b2f9eaa60dbdb55 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 12:22:19 +0200 Subject: [PATCH 14/25] Fix tests --- src/collective/feedback/tests/test_feedbacks_get.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index 902089f..f238fdf 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -38,9 +38,11 @@ def add_record(self, date=None, vote="", uid="", comment="", title=""): if title: record.attrs["title"] = title + id = soup.add(record) + transaction.commit() - return soup.add(record) + return id def setUp(self): self.app = self.layer["app"] From 55ea0962cb3b041af29ef8a5768eab53f1ef8fb5 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 25 Jul 2024 12:33:58 +0200 Subject: [PATCH 15/25] fix logic --- src/collective/feedback/restapi/services/get.py | 3 +-- src/collective/feedback/tests/test_feedbacks_get.py | 11 ++--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index a32b16f..ab91d66 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -116,6 +116,7 @@ def get_single_object_feedbacks(self, uid): "answer": record._attrs.get("answer", ""), "comment": record._attrs.get("comment", ""), "title": commented_object.title, + "id": record.intid, } ) @@ -136,7 +137,6 @@ def get_data(self): uid = feedback._attrs.get("uid", "") date = feedback._attrs.get("date", "") vote = feedback._attrs.get("vote", "") - id = feedback.intid if uid not in feedbacks: obj = self.get_commented_obj(uid=uid) @@ -151,7 +151,6 @@ def get_data(self): "comments": 0, "title": feedback._attrs.get("title", ""), "uid": uid, - "id": id, } if obj: diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index f238fdf..b505567 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -37,13 +37,9 @@ def add_record(self, date=None, vote="", uid="", comment="", title=""): record.attrs["uid"] = uid if title: record.attrs["title"] = title - - id = soup.add(record) - + soup.add(record) transaction.commit() - return id - def setUp(self): self.app = self.layer["app"] self.portal = self.layer["portal"] @@ -119,7 +115,7 @@ def test_endpoint_returns_data(self): res = response.json() self.assertEqual(res["items_total"], 0) now = datetime.now() - id = self.add_record(vote=1, comment="is ok", date=now) + self.add_record(vote=1, comment="is ok", date=now) response = self.api_session.get(self.url) res = response.json() @@ -130,7 +126,6 @@ def test_endpoint_returns_data(self): [ { "comments": 1, - "id": id, "last_vote": json_compatible(now), "title": "", "uid": "", @@ -164,7 +159,6 @@ def test_global_editor_can_see_all_data(self): comment="ok also for restricted", uid=self.restricted_document.UID(), ) - api_session = RelativeSession(self.portal_url) api_session.headers.update({"Accept": "application/json"}) api_session.auth = ("global", "secret!!") @@ -185,7 +179,6 @@ def test_local_editor_can_see_only_data_for_his_contents(self): comment="ok also for restricted", uid=self.restricted_document.UID(), ) - api_session = RelativeSession(self.portal_url) api_session.headers.update({"Accept": "application/json"}) api_session.auth = ("local", "secret!!") From b81709c82106fe23a75fbc9857b4169ccfa07969 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 26 Jul 2024 12:54:40 +0200 Subject: [PATCH 16/25] Convert id to int --- src/collective/feedback/restapi/services/update.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py index 001a07b..5ba26f1 100644 --- a/src/collective/feedback/restapi/services/update.py +++ b/src/collective/feedback/restapi/services/update.py @@ -30,7 +30,10 @@ def reply(self): tool = getUtility(ICollectiveFeedbackStore) if self.params: - id = self.params[0] + try: + id = int(self.params[0]) + except ValueError: + raise BadRequest(f"Bad id={self.params[0]} format provided") comment = tool.get(id) From 6467b82d84a5f284bc6c36715d2509b91f1bb95b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 26 Jul 2024 14:11:28 +0200 Subject: [PATCH 17/25] add read key to response --- src/collective/feedback/restapi/services/get.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index ab91d66..8c2e924 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -117,6 +117,7 @@ def get_single_object_feedbacks(self, uid): "comment": record._attrs.get("comment", ""), "title": commented_object.title, "id": record.intid, + "read": record._attrs.get("read", ""), } ) From 4e0d5fef42e98ce6bb096e98b926c09d53a5a038 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 26 Jul 2024 14:27:47 +0200 Subject: [PATCH 18/25] fix endpoint --- src/collective/feedback/restapi/services/update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/feedback/restapi/services/update.py b/src/collective/feedback/restapi/services/update.py index 5ba26f1..ce09740 100644 --- a/src/collective/feedback/restapi/services/update.py +++ b/src/collective/feedback/restapi/services/update.py @@ -77,6 +77,6 @@ def validate_form(self, form_data): check all required fields and parameters """ for field in ["read"]: - value = form_data.get(field, "") - if not value: + value = form_data.get(field, None) + if value is None: raise BadRequest("Campo obbligatorio mancante: {}".format(field)) From 8ca28cca048691cdc869f2eb53bce14824f44e78 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Jul 2024 10:10:20 +0200 Subject: [PATCH 19/25] Unread comments --- src/collective/feedback/restapi/services/get.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index 8c2e924..1a16a55 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -165,6 +165,11 @@ def get_data(self): data["vote_num"] += 1 data["vote_sum"] += vote + # Sign if page has unread comments + data["has_unread"] = data.get("has_unread", False) or feedback._attrs.get( + "read", False + ) + # number of comment comment = feedback._attrs.get("comment", "") answer = feedback._attrs.get("answer", "") From 2435bf9540e7b82c71a54bd6726c5655fdcca1af Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Jul 2024 10:15:13 +0200 Subject: [PATCH 20/25] Fix logics --- src/collective/feedback/restapi/services/get.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index 1a16a55..d1c0f75 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -166,9 +166,9 @@ def get_data(self): data["vote_sum"] += vote # Sign if page has unread comments - data["has_unread"] = data.get("has_unread", False) or feedback._attrs.get( - "read", False - ) + data["has_unread"] = data.get( + "has_unread", False + ) or not feedback._attrs.get("read", False) # number of comment comment = feedback._attrs.get("comment", "") From a09c265f897b609417841a10ca39d24552eec430 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 31 Jul 2024 10:52:20 +0200 Subject: [PATCH 21/25] has_unread search filter --- .../feedback/restapi/services/get.py | 15 ++++- .../feedback/tests/test_feedbacks_get.py | 64 ++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index d1c0f75..ae6b60d 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -183,10 +183,23 @@ def get_data(self): if data["last_vote"] < date: data["last_vote"] = date - # avg calculation + pages_to_remove = [] + for uid, feedback in feedbacks.items(): + # avg calculation feedback["vote"] = feedback.pop("vote_sum") / feedback.pop("vote_num") + has_undread = query.get("has_unread", None) + + # Use has_unread filter + if has_undread is not None: + has_undread = not (has_undread == "false") and has_undread == "true" + if feedback["has_unread"] != has_undread: + pages_to_remove.append(uid) + + for uid in pages_to_remove: + del feedbacks[uid] + result = list(feedbacks.values()) # sort diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index b505567..68547b5 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -22,11 +22,14 @@ class TestGet(unittest.TestCase): layer = RESTAPI_TESTING - def add_record(self, date=None, vote="", uid="", comment="", title=""): + def add_record(self, date=None, vote="", uid="", comment="", title="", read=False): if not date: date = datetime.now() + soup = get_soup("feedback_soup", self.portal) + transaction.commit() + record = Record() record.attrs["vote"] = vote record.attrs["date"] = date @@ -37,6 +40,9 @@ def add_record(self, date=None, vote="", uid="", comment="", title=""): record.attrs["uid"] = uid if title: record.attrs["title"] = title + if read: + record.attrs["read"] = read + soup.add(record) transaction.commit() @@ -233,3 +239,59 @@ def test_users_without_permission_dont_have_can_delete_feedbacks_action(self): self.assertIn("can_delete_feedbacks", res["actions"]) self.assertFalse(res["actions"]["can_delete_feedbacks"]) + + def test_has_unread_filter(self): + response = self.api_session.get(self.url) + res = response.json() + + self.assertEqual(res["items_total"], 0) + + now = datetime.now() + + self.add_record(vote=1, comment="is ok", date=now, read=True) + + response = self.api_session.get(self.url) + + res = response.json() + + self.assertEqual(res["items_total"], 1) + + self.assertEqual( + res["items"], + [ + { + "comments": 1, + "last_vote": json_compatible(now), + "title": "", + "uid": "", + "vote": 1.0, + "has_unread": False, + } + ], + ) + + self.add_record(vote=1, comment="is ok", date=now, read=False) + + # has_unread=true case + response = self.api_session.get(self.url + "?has_unread=true") + + self.assertEqual( + response.json()["items"], + [ + { + "comments": 2, + "last_vote": json_compatible(now), + "title": "", + "uid": "", + "vote": 1.0, + "has_unread": True, + } + ], + ) + + self.add_record(vote=1, comment="is ok", date=now) + + # has_unread=false case + response = self.api_session.get(self.url + "?has_unread=false") + + self.assertEqual(response.json()["items"], []) From 7841f17824d2844f6698bfbca360d385a5aea520 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 31 Jul 2024 11:40:04 +0200 Subject: [PATCH 22/25] fix logics --- src/collective/feedback/restapi/services/get.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index ae6b60d..17a79b4 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -193,9 +193,11 @@ def get_data(self): # Use has_unread filter if has_undread is not None: - has_undread = not (has_undread == "false") and has_undread == "true" - if feedback["has_unread"] != has_undread: - pages_to_remove.append(uid) + if has_undread in ("true", "false"): + has_undread = not (has_undread == "false") and has_undread == "true" + + if feedback["has_unread"] != has_undread: + pages_to_remove.append(uid) for uid in pages_to_remove: del feedbacks[uid] From 83c2483ac50fdf4288f5e143b23b6827bf60db6c Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 31 Jul 2024 11:40:48 +0200 Subject: [PATCH 23/25] Fix --- src/collective/feedback/tests/test_feedbacks_get.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/collective/feedback/tests/test_feedbacks_get.py b/src/collective/feedback/tests/test_feedbacks_get.py index 68547b5..224c43a 100644 --- a/src/collective/feedback/tests/test_feedbacks_get.py +++ b/src/collective/feedback/tests/test_feedbacks_get.py @@ -132,6 +132,7 @@ def test_endpoint_returns_data(self): [ { "comments": 1, + "has_unread": True, "last_vote": json_compatible(now), "title": "", "uid": "", From 782f65c074d28b54f8816e3dc7aec6f173bc590f Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 31 Jul 2024 11:41:57 +0200 Subject: [PATCH 24/25] refactor --- src/collective/feedback/restapi/services/get.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index 17a79b4..4a53a9b 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -185,12 +185,12 @@ def get_data(self): pages_to_remove = [] + has_undread = query.get("has_unread", None) + for uid, feedback in feedbacks.items(): # avg calculation feedback["vote"] = feedback.pop("vote_sum") / feedback.pop("vote_num") - has_undread = query.get("has_unread", None) - # Use has_unread filter if has_undread is not None: if has_undread in ("true", "false"): From d68e292aecfcdbb4d315301fc7c857894dfd334c Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 31 Jul 2024 11:43:58 +0200 Subject: [PATCH 25/25] refactor --- src/collective/feedback/restapi/services/get.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/collective/feedback/restapi/services/get.py b/src/collective/feedback/restapi/services/get.py index 4a53a9b..64454e5 100644 --- a/src/collective/feedback/restapi/services/get.py +++ b/src/collective/feedback/restapi/services/get.py @@ -187,17 +187,19 @@ def get_data(self): has_undread = query.get("has_unread", None) + if has_undread in ("true", "false"): + has_undread = not (has_undread == "false") and has_undread == "true" + else: + has_undread = None + for uid, feedback in feedbacks.items(): # avg calculation feedback["vote"] = feedback.pop("vote_sum") / feedback.pop("vote_num") # Use has_unread filter if has_undread is not None: - if has_undread in ("true", "false"): - has_undread = not (has_undread == "false") and has_undread == "true" - - if feedback["has_unread"] != has_undread: - pages_to_remove.append(uid) + if feedback["has_unread"] != has_undread: + pages_to_remove.append(uid) for uid in pages_to_remove: del feedbacks[uid]