Skip to content

Commit 04a0f9b

Browse files
committed
first working
1 parent f28c11d commit 04a0f9b

9 files changed

Lines changed: 284 additions & 80 deletions

File tree

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
from kitconcept.keywordmanager.interfaces import IKeywordManager
22
from plone.restapi.deserializer import json_body
33
from plone.restapi.services import Service
4-
from zope.component import getUtility
5-
from zExceptions import HTTPAccepted as Accepted
64
from zExceptions import BadRequest
5+
from zope.component import getUtility
76

87

98
class TagsDelete(Service):
109
def reply(self):
11-
km = getUtility(IKeywordManager)
1210
data = json_body(self.request)
13-
if keywords := data.get("items"):
14-
keywords = keywords.split(",")
15-
else:
16-
raise BadRequest("Please provide keywords to delete")
17-
breakpoint()
11+
km = getUtility(IKeywordManager)
12+
keywords = data.get("items", [])
13+
14+
if isinstance(keywords, str):
15+
keywords = [keywords]
1816

19-
deleted = km.delete(keywords)
17+
try:
18+
km.delete(keywords)
19+
except Exception as err:
20+
raise BadRequest(err) from err
2021

21-
return Accepted(f"Updated {deleted} objects")
22+
return self.reply_no_content()

backend/src/kitconcept/keywordmanager/services/get.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from kitconcept.keywordmanager.interfaces import IKeywordManager
2-
from plone import api
32
from plone.restapi.batching import HypermediaBatch
43
from plone.restapi.deserializer import json_body
54
from plone.restapi.services import Service
@@ -8,23 +7,18 @@
87

98
class TagsGet(Service):
109
def reply(self):
11-
km = getUtility(IKeywordManager)
1210
data = json_body(self.request)
13-
query = {}
11+
km = getUtility(IKeywordManager)
12+
query = {"withLengths": True}
1413
if idx := data.get("idx"):
1514
query = {"indexName": idx}
1615

1716
keywords = km.getKeywords(**query)
1817

1918
batch = HypermediaBatch(self.request, keywords)
20-
# get the total count of each keyword in the batch
21-
# TODO: can we optimize this?
2219
items = []
2320
for kw in batch:
24-
catalog = api.portal.get_tool("portal_catalog")
25-
brains = catalog(Subject=kw)
26-
total = len(brains)
27-
items.append({"name": kw, "total": total})
21+
items.append({"name": kw[0], "total": kw[1]})
2822

2923
keywords_data = {
3024
"@id": batch.canonical_url,
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
from kitconcept.keywordmanager.interfaces import IKeywordManager
2+
from plone.restapi.deserializer import json_body
13
from plone.restapi.services import Service
4+
from zExceptions import BadRequest
5+
from zope.component import getUtility
26

37

48
class TagsPatch(Service):
5-
""""""
9+
def reply(self):
10+
data = json_body(self.request)
11+
km = getUtility(IKeywordManager)
12+
13+
new_keyword = data.get("new_keyword", "")
14+
old_keywords = data.get("old_keywords", [])
15+
16+
try:
17+
km.change(new_keyword, old_keywords)
18+
except Exception as err:
19+
raise BadRequest(err) from err
20+
21+
return self.reply_no_content()

backend/src/kitconcept/keywordmanager/tool.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ def _getFullIndexList(self, indexName):
4242

4343
@security.protected(config.MANAGE_KEYWORDS_PERMISSION)
4444
def change(
45-
self, old_keywords, new_keyword, context=None, indexName="Subject"
45+
self,
46+
new_keyword: str,
47+
old_keywords: list,
48+
context=None,
49+
indexName: str = "Subject",
4650
) -> int:
4751
"""Updates all objects using the old_keywords.
4852
@@ -59,16 +63,16 @@ def change(
5963

6064
new_keyword = to_str(new_keyword)
6165
try:
62-
querySet = api.content.find(**query)
66+
brains = api.content.find(**query)
6367
except UnicodeDecodeError:
6468
old_keywords = [
6569
k.decode("utf8") if isinstance(k, str) else k for k in old_keywords
6670
]
6771
query[indexName] = old_keywords
68-
querySet = api.content.find(**query)
72+
brains = api.content.find(**query)
6973

70-
for item in querySet:
71-
obj = item.getObject()
74+
for brain in brains:
75+
obj = brain.getObject()
7276
# #MOD Dynamic field getting
7377

7478
value = self.getFieldValue(obj, indexName)
@@ -87,7 +91,7 @@ def change(
8791

8892
self.updateObject(obj, indexName, value)
8993

90-
return len(querySet)
94+
return len(brains)
9195

9296
@security.protected(config.MANAGE_KEYWORDS_PERMISSION)
9397
def delete(self, keywords: list, context=None, indexName: str = "Subject") -> int:
@@ -119,27 +123,46 @@ def delete(self, keywords: list, context=None, indexName: str = "Subject") -> in
119123

120124
return len(brains)
121125

122-
def updateObject(self, obj, indexName, value) -> None:
126+
def updateObject(self, obj, indexName: str, value) -> None:
123127
updateField = self.getSetter(obj, indexName)
124128
if updateField is not None:
125129
updateField(value)
126130
idxs = self._getFullIndexList(indexName)
127131
obj.reindexObject(idxs=idxs)
128132

129133
@security.protected(config.MANAGE_KEYWORDS_PERMISSION)
130-
def getKeywords(self, indexName="Subject"):
134+
def getKeywords(
135+
self, indexName: str = "Subject", withLengths: bool = False
136+
) -> list[str] | list[tuple[str, int]]:
137+
"""Return all unique keyword values from the specified catalog index,
138+
sorted alphabetically.
139+
140+
Args:
141+
indexName: The name of the keyword index to query. Defaults to "Subject".
142+
withLengths: If True, returns tuples of (keyword, count) instead of
143+
just keywords. Defaults to False.
144+
145+
Returns:
146+
A sorted list of keyword strings, or a sorted list of (keyword, count)
147+
tuples if withLengths is True.
148+
149+
Raises:
150+
ValueError: If indexName is not a valid keyword index.
151+
"""
131152
processQueue()
132153
if indexName not in self.getKeywordIndexes():
133-
raise ValueError(f"{indexName} is not a valid field")
154+
raise ValueError(f"'{indexName}' is not a valid index")
134155

135156
catalog = api.portal.get_tool("portal_catalog")
136-
keywords = [x for x in catalog.uniqueValuesFor(indexName) if x is not None]
137-
keywords.sort(key=lambda x: x.lower())
157+
keywords = sorted(
158+
catalog.Indexes[indexName].uniqueValues(withLengths=withLengths),
159+
key=lambda x: x.lower() if isinstance(x, str) else x[0].lower(),
160+
)
138161

139162
# can we turn this into a yield?
140163
return keywords
141164

142-
def getKeywordLength(self, key, indexName="Subject") -> int:
165+
def getKeywordLength(self, key, indexName: str = "Subject") -> int:
143166
processQueue()
144167
if indexName not in self.getKeywordIndexes():
145168
raise ValueError(f"{indexName} is not a valid field")
@@ -184,7 +207,7 @@ def getScoredMatches(self, word, possibilities, num, score, context=None):
184207
# Return first n terms without scores
185208
return [item[1] for item in res[:num]]
186209

187-
def getKeywordIndexes(self):
210+
def getKeywordIndexes(self) -> list[str]:
188211
"""Gets a list of indexes from the catalog. Uses config.py to choose the
189212
meta type and filters out a subset of known indexes that should not be
190213
managed.
@@ -200,7 +223,7 @@ def getKeywordIndexes(self):
200223
return idxs
201224

202225
@security.private
203-
def fieldNameForIndex(self, indexName):
226+
def fieldNameForIndex(self, indexName: str) -> str:
204227
"""The name of the index may not be the same as the field on the object,
205228
and we need the actual field name in order to find its mutator.
206229
"""

frontend/packages/volto-keywordmanager/src/actions/keywords.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import { GET_KEYWORDS } from 'volto-keywordmanager/constants/Keywords';
22
import { UPDATE_KEYWORDS } from 'volto-keywordmanager/constants/Keywords';
33
import { DELETE_KEYWORDS } from 'volto-keywordmanager/constants/Keywords';
44

5-
import { AnyAction } from 'redux';
6-
75
export function getKeywords(options = {}) {
86
const { groupKeywords, batchSize, batchStart, ...rest } = options;
97
const params = new URLSearchParams({
@@ -21,27 +19,24 @@ export function getKeywords(options = {}) {
2119
};
2220
}
2321

24-
export function updateKeywords() {
22+
export function updateKeywords(data) {
2523
return {
2624
type: UPDATE_KEYWORDS,
2725
request: {
28-
op: 'post',
26+
op: 'patch',
2927
path: '/@tags',
28+
data,
3029
},
3130
};
3231
}
3332

34-
/**
35-
* @param {string | string[]} keywords
36-
* @returns {AnyAction}
37-
*/
38-
export function deleteKeywords(keywords) {
39-
const result = Array.isArray(keywords) ? keywords.join(',') : keywords;
33+
export function deleteKeywords(data) {
4034
return {
4135
type: DELETE_KEYWORDS,
4236
request: {
4337
op: 'del',
44-
path: `/@tags?${result}`,
38+
path: '/@tags',
39+
data,
4540
},
4641
};
4742
}

0 commit comments

Comments
 (0)