Skip to content

Commit

Permalink
Merge pull request #4 from RedTurtle/read_field
Browse files Browse the repository at this point in the history
Add 'read' field to the comment
  • Loading branch information
folix-01 authored Aug 21, 2024
2 parents cd442d5 + d68e292 commit db1f409
Show file tree
Hide file tree
Showing 21 changed files with 289 additions and 76 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ report.html
reports/
venv/
# excludes
.python-version
6 changes: 5 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ Changelog
1.1.4 (unreleased)
------------------

- Nothing changed yet.
- Add feedback update endpoint.
[folix-01]

- Add read field to the comment.
[folix-01]


1.1.3 (2024-04-29)
Expand Down
1 change: 0 additions & 1 deletion src/collective/feedback/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
"""Init and utils."""
from zope.i18nmessageid import MessageFactory


_ = MessageFactory("collective.feedback")
2 changes: 1 addition & 1 deletion src/collective/feedback/locales/update.py
Original file line number Diff line number Diff line change
@@ -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, ""))
Expand Down
4 changes: 4 additions & 0 deletions src/collective/feedback/permissions.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
id="collective.feedback.ShowDeletedFeedbacks"
title="collective.feedback: Show Deleted Feedbacks"
/>
<permission
id="collective.feedback.UpdateFeedbacks"
title="collective.feedback: Update Feedbacks"
/>

</configure>

Expand Down
2 changes: 1 addition & 1 deletion src/collective/feedback/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>1201</version>
<version>1202</version>
<dependencies>
<dependency>profile-plone.restapi:default</dependency>
<dependency>profile-souper.plone:default</dependency>
Expand Down
7 changes: 6 additions & 1 deletion src/collective/feedback/profiles/default/rolemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
<role name="Manager" />
<role name="Site Administrator" />
</permission>


<permission acquire="True" name="collective.feedback: Update Feedbacks">
<role name="Manager" />
<role name="Site Administrator" />
</permission>

</permissions>
</rolemap>
3 changes: 2 additions & 1 deletion src/collective/feedback/restapi/services/add.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -7,6 +6,8 @@
from zope.component import getUtility
from zope.interface import alsoProvides

from collective.feedback.interfaces import ICollectiveFeedbackStore


class FeedbackAdd(Service):
"""
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 @@ -37,5 +37,13 @@
layer="collective.feedback.interfaces.ICollectiveFeedbackLayer"
name="@feedback-delete"
/>
<plone:service
method="PATCH"
factory=".update.FeedbackUpdate"
for="plone.app.layout.navigation.interfaces.INavigationRoot"
permission="collective.feedback.FeedbacksOverview"
layer="collective.feedback.interfaces.ICollectiveFeedbackLayer"
name="@feedback"
/>

</configure>
6 changes: 3 additions & 3 deletions src/collective/feedback/restapi/services/delete.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
34 changes: 30 additions & 4 deletions src/collective/feedback/restapi/services/get.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -115,6 +116,8 @@ def get_single_object_feedbacks(self, uid):
"answer": record._attrs.get("answer", ""),
"comment": record._attrs.get("comment", ""),
"title": commented_object.title,
"id": record.intid,
"read": record._attrs.get("read", ""),
}
)

Expand All @@ -135,6 +138,7 @@ def get_data(self):
uid = feedback._attrs.get("uid", "")
date = feedback._attrs.get("date", "")
vote = feedback._attrs.get("vote", "")

if uid not in feedbacks:
obj = self.get_commented_obj(uid=uid)
if not obj and not api.user.has_permission(
Expand All @@ -161,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 not feedback._attrs.get("read", False)

# number of comment
comment = feedback._attrs.get("comment", "")
answer = feedback._attrs.get("answer", "")
Expand All @@ -174,10 +183,27 @@ def get_data(self):
if data["last_vote"] < date:
data["last_vote"] = date

# avg calculation
pages_to_remove = []

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 feedback["has_unread"] != has_undread:
pages_to_remove.append(uid)

for uid in pages_to_remove:
del feedbacks[uid]

result = list(feedbacks.values())

# sort
Expand Down
82 changes: 82 additions & 0 deletions src/collective/feedback/restapi/services/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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

from collective.feedback.interfaces import ICollectiveFeedbackStore


@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)

tool = getUtility(ICollectiveFeedbackStore)

if self.params:
try:
id = int(self.params[0])
except ValueError:
raise BadRequest(f"Bad id={self.params[0]} format provided")

comment = tool.get(id)

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

form_data = json_body(self.request)

self.validate_form(form_data=form_data)

form_data = self.extract_data(form_data)

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 is 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, None)
if value is None:
raise BadRequest("Campo obbligatorio mancante: {}".format(field))
3 changes: 1 addition & 2 deletions src/collective/feedback/storage/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
34 changes: 19 additions & 15 deletions src/collective/feedback/storage/store.py
Original file line number Diff line number Diff line change
@@ -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__)

Expand All @@ -21,7 +17,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 = []
Expand Down Expand Up @@ -83,10 +79,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):
Expand All @@ -113,6 +109,7 @@ def update(self, id, data):

else:
record.attrs[k] = v

self.soup.reindex(records=[record])

def delete(self, id):
Expand All @@ -123,5 +120,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()
17 changes: 9 additions & 8 deletions src/collective/feedback/testing.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Loading

0 comments on commit db1f409

Please sign in to comment.