rer.solrpush 1.3.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rer/solrpush/__init__.py +7 -0
- rer/solrpush/adapters/__init__.py +1 -0
- rer/solrpush/adapters/configure.zcml +13 -0
- rer/solrpush/adapters/file.py +52 -0
- rer/solrpush/adapters/rss_feed.py +28 -0
- rer/solrpush/behaviors/__init__.py +0 -0
- rer/solrpush/behaviors/configure.zcml +16 -0
- rer/solrpush/behaviors/solr_fields.py +41 -0
- rer/solrpush/browser/__init__.py +0 -0
- rer/solrpush/browser/configure.zcml +111 -0
- rer/solrpush/browser/controlpanel.py +96 -0
- rer/solrpush/browser/elevate_settings.py +35 -0
- rer/solrpush/browser/maintenance.py +476 -0
- rer/solrpush/browser/query_debug.py +12 -0
- rer/solrpush/browser/querybuilder.py +168 -0
- rer/solrpush/browser/scales.py +80 -0
- rer/solrpush/browser/static/.gitkeep +0 -0
- rer/solrpush/browser/static/dist/dev/main.js +58240 -0
- rer/solrpush/browser/static/dist/dev/main.js.map +180 -0
- rer/solrpush/browser/static/dist/prod/main.js +9604 -0
- rer/solrpush/browser/static/dist/prod/main.js.map +1 -0
- rer/solrpush/browser/static/javascripts/FacetsContainer/index.js +46 -0
- rer/solrpush/browser/static/javascripts/ProgressBar/index.js +40 -0
- rer/solrpush/browser/static/javascripts/ProgressBarContainer/index.js +66 -0
- rer/solrpush/browser/static/javascripts/index.js +14 -0
- rer/solrpush/browser/static/package.json +13 -0
- rer/solrpush/browser/static/styles.css +22 -0
- rer/solrpush/browser/static/styles.css.map +1 -0
- rer/solrpush/browser/static/styles.scss +27 -0
- rer/solrpush/browser/static/yarn.lock +1408 -0
- rer/solrpush/browser/templates/controlpanel_layout.pt +49 -0
- rer/solrpush/browser/templates/query_debug.pt +17 -0
- rer/solrpush/browser/templates/reindex_solr.pt +35 -0
- rer/solrpush/configure.zcml +81 -0
- rer/solrpush/indexer.py +120 -0
- rer/solrpush/interfaces/__init__.py +8 -0
- rer/solrpush/interfaces/adapter.py +13 -0
- rer/solrpush/interfaces/elevate.py +98 -0
- rer/solrpush/interfaces/layer.py +6 -0
- rer/solrpush/interfaces/queueprocessor.py +6 -0
- rer/solrpush/interfaces/settings.py +194 -0
- rer/solrpush/locales/README.rst +37 -0
- rer/solrpush/locales/__init__.py +0 -0
- rer/solrpush/locales/en/LC_MESSAGES/rer.solrpush.mo +0 -0
- rer/solrpush/locales/en/LC_MESSAGES/rer.solrpush.po +399 -0
- rer/solrpush/locales/it/LC_MESSAGES/rer.solrpush.mo +0 -0
- rer/solrpush/locales/it/LC_MESSAGES/rer.solrpush.po +399 -0
- rer/solrpush/locales/rer.solrpush.pot +402 -0
- rer/solrpush/locales/update.py +71 -0
- rer/solrpush/locales/update.sh +12 -0
- rer/solrpush/parser.py +211 -0
- rer/solrpush/permissions.zcml +18 -0
- rer/solrpush/profiles/default/actions.xml +21 -0
- rer/solrpush/profiles/default/browserlayer.xml +7 -0
- rer/solrpush/profiles/default/catalog.xml +4 -0
- rer/solrpush/profiles/default/controlpanel.xml +31 -0
- rer/solrpush/profiles/default/metadata.xml +8 -0
- rer/solrpush/profiles/default/registry/bundle.xml +25 -0
- rer/solrpush/profiles/default/registry/criteria.xml +55 -0
- rer/solrpush/profiles/default/registry/settings.xml +6 -0
- rer/solrpush/profiles/default/rolemap.xml +13 -0
- rer/solrpush/profiles/default/types/Document.xml +12 -0
- rer/solrpush/profiles/to_1100/componentregistry.xml +9 -0
- rer/solrpush/profiles/to_1100/metadata.xml +5 -0
- rer/solrpush/profiles/to_1300/controlpanel.xml +13 -0
- rer/solrpush/profiles/to_1300/registry.xml +5 -0
- rer/solrpush/profiles/uninstall/browserlayer.xml +7 -0
- rer/solrpush/profiles/uninstall/controlpanel.xml +21 -0
- rer/solrpush/profiles/uninstall/registry.xml +17 -0
- rer/solrpush/restapi/__init__.py +0 -0
- rer/solrpush/restapi/configure.zcml +8 -0
- rer/solrpush/restapi/services/__init__.py +0 -0
- rer/solrpush/restapi/services/configure.zcml +8 -0
- rer/solrpush/restapi/services/elevate_schema/__init__.py +0 -0
- rer/solrpush/restapi/services/elevate_schema/configure.zcml +14 -0
- rer/solrpush/restapi/services/elevate_schema/get.py +12 -0
- rer/solrpush/restapi/services/solr_search/__init__.py +0 -0
- rer/solrpush/restapi/services/solr_search/batch.py +129 -0
- rer/solrpush/restapi/services/solr_search/configure.zcml +14 -0
- rer/solrpush/restapi/services/solr_search/get.py +25 -0
- rer/solrpush/restapi/services/solr_search/solr_search_handler.py +86 -0
- rer/solrpush/setuphandlers.py +20 -0
- rer/solrpush/testing.py +161 -0
- rer/solrpush/tests/__init__.py +0 -0
- rer/solrpush/tests/docs/example.docx +0 -0
- rer/solrpush/tests/docs/example.ods +0 -0
- rer/solrpush/tests/docs/example.pdf +0 -0
- rer/solrpush/tests/docs/plone_logo.png +0 -0
- rer/solrpush/tests/test_behaviors.py +61 -0
- rer/solrpush/tests/test_elevate.py +124 -0
- rer/solrpush/tests/test_events.py +181 -0
- rer/solrpush/tests/test_image_tags.py +85 -0
- rer/solrpush/tests/test_maintenance.py +165 -0
- rer/solrpush/tests/test_querybuilder.py +272 -0
- rer/solrpush/tests/test_restapi_search.py +127 -0
- rer/solrpush/tests/test_search.py +177 -0
- rer/solrpush/tests/test_setup.py +67 -0
- rer/solrpush/tests/test_solr_interface.py +359 -0
- rer/solrpush/tests/test_tika.py +85 -0
- rer/solrpush/tests/test_vocabularies.py +113 -0
- rer/solrpush/upgrades.py +76 -0
- rer/solrpush/upgrades.zcml +86 -0
- rer/solrpush/utils/__init__.py +6 -0
- rer/solrpush/utils/solr_common.py +122 -0
- rer/solrpush/utils/solr_indexer.py +295 -0
- rer/solrpush/utils/solr_search.py +352 -0
- rer/solrpush/vocabularies.py +50 -0
- rer.solrpush-1.3.4-py3.10-nspkg.pth +1 -0
- rer.solrpush-1.3.4.dist-info/LICENSE.GPL +339 -0
- rer.solrpush-1.3.4.dist-info/LICENSE.rst +15 -0
- rer.solrpush-1.3.4.dist-info/METADATA +507 -0
- rer.solrpush-1.3.4.dist-info/RECORD +116 -0
- rer.solrpush-1.3.4.dist-info/WHEEL +5 -0
- rer.solrpush-1.3.4.dist-info/entry_points.txt +5 -0
- rer.solrpush-1.3.4.dist-info/namespace_packages.txt +1 -0
- rer.solrpush-1.3.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from AccessControl import Unauthorized
|
|
3
|
+
from persistent.dict import PersistentDict
|
|
4
|
+
from plone import api
|
|
5
|
+
from plone.memoize import ram
|
|
6
|
+
from plone.memoize.view import memoize
|
|
7
|
+
from plone.protect.authenticator import createToken
|
|
8
|
+
from Products.CMFCore.interfaces import IIndexQueueProcessor
|
|
9
|
+
from Products.Five import BrowserView
|
|
10
|
+
from pysolr import SolrError
|
|
11
|
+
from rer.solrpush import _
|
|
12
|
+
from rer.solrpush.utils import push_to_solr
|
|
13
|
+
from rer.solrpush.utils import remove_from_solr
|
|
14
|
+
from rer.solrpush.utils import reset_solr
|
|
15
|
+
from rer.solrpush.utils import search
|
|
16
|
+
from rer.solrpush.utils.solr_common import is_solr_active
|
|
17
|
+
from rer.solrpush.utils.solr_indexer import can_index
|
|
18
|
+
from rer.solrpush.utils.solr_indexer import get_site_title
|
|
19
|
+
from rer.solrpush.utils.solr_indexer import parse_date_as_datetime
|
|
20
|
+
from time import strftime
|
|
21
|
+
from time import time
|
|
22
|
+
from transaction import commit
|
|
23
|
+
from z3c.form import button
|
|
24
|
+
from z3c.form import form
|
|
25
|
+
from zope.annotation.interfaces import IAnnotations
|
|
26
|
+
from zope.component import getMultiAdapter
|
|
27
|
+
from zope.component import getSiteManager
|
|
28
|
+
from zope.i18n import translate
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import logging
|
|
32
|
+
import os
|
|
33
|
+
import pkg_resources
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
JS_TEMPLATE = (
|
|
37
|
+
"{portal_url}/++plone++rer.solrpush/dist/{env_mode}/{name}.js?v={version}"
|
|
38
|
+
)
|
|
39
|
+
CSS_TEMPLATE = (
|
|
40
|
+
"{portal_url}/++plone++rer.solrpush/dist/{env_mode}/{name}.css?v={version}"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def timer(func=time):
|
|
48
|
+
""" set up a generator returning the elapsed time since the last call """
|
|
49
|
+
|
|
50
|
+
def gen(last=func()):
|
|
51
|
+
while True:
|
|
52
|
+
elapsed = func() - last
|
|
53
|
+
last = func()
|
|
54
|
+
yield "%.3fs" % elapsed
|
|
55
|
+
|
|
56
|
+
return gen()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SolrMaintenanceBaseForm(form.Form):
|
|
60
|
+
|
|
61
|
+
# template = ViewPageTemplateFile('templates/reindex_solr.pt')
|
|
62
|
+
|
|
63
|
+
ignoreContext = True
|
|
64
|
+
|
|
65
|
+
@button.buttonAndHandler(_("start_label", default=u"Start"))
|
|
66
|
+
def handleApply(self, action):
|
|
67
|
+
data, errors = self.extractData()
|
|
68
|
+
if errors:
|
|
69
|
+
self.status = self.formErrorsMessage
|
|
70
|
+
return
|
|
71
|
+
self.do_action()
|
|
72
|
+
|
|
73
|
+
@button.buttonAndHandler(_("cancel_label", default=u"Cancel"))
|
|
74
|
+
def handleCancel(self, action):
|
|
75
|
+
msg_label = _("maintenance_cancel_action", default="Action cancelled")
|
|
76
|
+
api.portal.show_message(message=msg_label, request=self.request)
|
|
77
|
+
return self.request.response.redirect(
|
|
78
|
+
"{}/@@solrpush-settings".format(api.portal.get().absolute_url())
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ResetSolr(SolrMaintenanceBaseForm):
|
|
83
|
+
"""
|
|
84
|
+
Reset solr index
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
label = _("maintenance_reset_solr_label", default="Reset SOLR index")
|
|
88
|
+
description = _(
|
|
89
|
+
"maintenance_reset_solr_description",
|
|
90
|
+
default="Drop all items in SOLR index.",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def do_action(self):
|
|
94
|
+
reset_solr()
|
|
95
|
+
msg_label = _(
|
|
96
|
+
"maintenance_reset_success", default="SOLR index dropped"
|
|
97
|
+
)
|
|
98
|
+
logger.info("##### SOLR Index dropped #####")
|
|
99
|
+
api.portal.show_message(message=msg_label, request=self.request)
|
|
100
|
+
return self.request.response.redirect(
|
|
101
|
+
"{}/@@solrpush-settings".format(api.portal.get().absolute_url())
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ReindexBaseView(BrowserView):
|
|
106
|
+
@property
|
|
107
|
+
def solr_error_message(self):
|
|
108
|
+
return translate(
|
|
109
|
+
_(
|
|
110
|
+
"solr_error_connection",
|
|
111
|
+
default=u"There have been problems connecting to SOLR. "
|
|
112
|
+
u"Contact site administrator.",
|
|
113
|
+
),
|
|
114
|
+
context=self.request,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def setupAnnotations(self, items_len, message):
|
|
118
|
+
annotations = IAnnotations(api.portal.get())
|
|
119
|
+
annotations["solr_reindex"] = PersistentDict(
|
|
120
|
+
{
|
|
121
|
+
"in_progress": True,
|
|
122
|
+
"tot": items_len,
|
|
123
|
+
"counter": 0,
|
|
124
|
+
"timestamp": strftime("%Y/%m/%d-%H:%M:%S "),
|
|
125
|
+
"message": message,
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
commit()
|
|
129
|
+
return annotations["solr_reindex"]
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
@memoize
|
|
133
|
+
def solr_utility(self):
|
|
134
|
+
sm = getSiteManager()
|
|
135
|
+
return sm.queryUtility(IIndexQueueProcessor, name="solrpush.utility")
|
|
136
|
+
|
|
137
|
+
def reindexPloneToSolr(self):
|
|
138
|
+
if not self.solr_utility:
|
|
139
|
+
return
|
|
140
|
+
if not is_solr_active():
|
|
141
|
+
logger.warning(
|
|
142
|
+
"Trying to reindexing but solr is not set as active"
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
elapsed = timer()
|
|
146
|
+
if self.solr_utility.enabled_types:
|
|
147
|
+
brains_to_reindex = api.content.find(
|
|
148
|
+
portal_type=self.solr_utility.enabled_types
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
pc = api.portal.get_tool(name="portal_catalog")
|
|
152
|
+
brains_to_reindex = pc()
|
|
153
|
+
status = self.setupAnnotations(
|
|
154
|
+
items_len=brains_to_reindex.actual_result_count,
|
|
155
|
+
message="Sync items to SOLR",
|
|
156
|
+
)
|
|
157
|
+
logger.info("##### SOLR REINDEX START #####")
|
|
158
|
+
logger.info(
|
|
159
|
+
"Reindexing {} items.".format(
|
|
160
|
+
brains_to_reindex.actual_result_count
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
skipped = 0
|
|
164
|
+
indexed = 0
|
|
165
|
+
for brain in brains_to_reindex:
|
|
166
|
+
tot_indexed = indexed + skipped + 1
|
|
167
|
+
commit()
|
|
168
|
+
obj = brain.getObject()
|
|
169
|
+
try:
|
|
170
|
+
res = push_to_solr(obj)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.exception(e)
|
|
173
|
+
status["in_progress"] = False
|
|
174
|
+
status["error"] = True
|
|
175
|
+
status["message"] = self.solr_error_message
|
|
176
|
+
return
|
|
177
|
+
if res:
|
|
178
|
+
indexed += 1
|
|
179
|
+
else:
|
|
180
|
+
skipped += 1
|
|
181
|
+
logger.debug(
|
|
182
|
+
"[{indexed}/{total}] {path} ({type})".format(
|
|
183
|
+
indexed=tot_indexed,
|
|
184
|
+
total=brains_to_reindex.actual_result_count,
|
|
185
|
+
path=brain.getPath(),
|
|
186
|
+
type=brain.portal_type,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
if tot_indexed > 0 and tot_indexed % 200 == 0:
|
|
190
|
+
logger.info(
|
|
191
|
+
"[PROGRESS]: {}/{}".format(
|
|
192
|
+
tot_indexed, brains_to_reindex.actual_result_count
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
status["counter"] = tot_indexed
|
|
196
|
+
|
|
197
|
+
status["in_progress"] = False
|
|
198
|
+
elapsed_time = next(elapsed)
|
|
199
|
+
logger.info("SOLR Reindex completed in {}".format(elapsed_time))
|
|
200
|
+
|
|
201
|
+
def removeZombiesFromSolr(self, disable_progress=False):
|
|
202
|
+
"""
|
|
203
|
+
remove items stored in solr that are no longer present in Plone or
|
|
204
|
+
they are not indexeable anymore.
|
|
205
|
+
"""
|
|
206
|
+
if not self.solr_utility:
|
|
207
|
+
return
|
|
208
|
+
if not is_solr_active():
|
|
209
|
+
logger.warning("Trying to cleanup but solr is not set as active")
|
|
210
|
+
return
|
|
211
|
+
elapsed = timer()
|
|
212
|
+
pc = api.portal.get_tool(name="portal_catalog")
|
|
213
|
+
if self.solr_utility.enabled_types:
|
|
214
|
+
brains_to_sync = api.content.find(
|
|
215
|
+
portal_type=self.solr_utility.enabled_types
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
pc = api.portal.get_tool(name="portal_catalog")
|
|
219
|
+
brains_to_sync = pc()
|
|
220
|
+
good_uids = [x.UID for x in brains_to_sync]
|
|
221
|
+
solr_results = search(
|
|
222
|
+
query={"site_name": get_site_title(), "b_size": 100000000},
|
|
223
|
+
fl="UID",
|
|
224
|
+
)
|
|
225
|
+
uids_to_remove = [
|
|
226
|
+
x["UID"] for x in solr_results.docs if x["UID"] not in good_uids
|
|
227
|
+
]
|
|
228
|
+
if not disable_progress:
|
|
229
|
+
status = self.setupAnnotations(
|
|
230
|
+
items_len=len(uids_to_remove), message="Cleanup items on SOLR"
|
|
231
|
+
)
|
|
232
|
+
logger.info("##### CLEANUP CONTENTS FROM SOLR #####")
|
|
233
|
+
for uid in uids_to_remove:
|
|
234
|
+
remove_from_solr(uid)
|
|
235
|
+
if not disable_progress:
|
|
236
|
+
status["counter"] = status["counter"] + 1
|
|
237
|
+
commit()
|
|
238
|
+
|
|
239
|
+
if not disable_progress:
|
|
240
|
+
status["in_progress"] = False
|
|
241
|
+
elapsed_time = next(elapsed)
|
|
242
|
+
logger.info("Completed in {}.".format(elapsed_time))
|
|
243
|
+
if len(uids_to_remove):
|
|
244
|
+
logger.info(
|
|
245
|
+
"Removed {} items from SOLR that are no longer in Plone".format(
|
|
246
|
+
len(uids_to_remove)
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def syncPloneWithSolr(self, disable_progress=False):
|
|
251
|
+
if not self.solr_utility:
|
|
252
|
+
return
|
|
253
|
+
if not is_solr_active():
|
|
254
|
+
logger.warning("Trying to cleanup but solr is not set as active")
|
|
255
|
+
return
|
|
256
|
+
elapsed = timer()
|
|
257
|
+
logger.info("##### SYNC CONTENTS TO SOLR #####")
|
|
258
|
+
pc = api.portal.get_tool(name="portal_catalog")
|
|
259
|
+
solr_docs = search(
|
|
260
|
+
query={"site_name": get_site_title(), "b_size": 100000000},
|
|
261
|
+
fl="UID modified",
|
|
262
|
+
).docs
|
|
263
|
+
solr_items = {item["UID"]: item["modified"] for item in solr_docs}
|
|
264
|
+
if self.solr_utility.enabled_types:
|
|
265
|
+
brains_to_sync = api.content.find(
|
|
266
|
+
portal_type=self.solr_utility.enabled_types
|
|
267
|
+
)
|
|
268
|
+
else:
|
|
269
|
+
pc = api.portal.get_tool(name="portal_catalog")
|
|
270
|
+
brains_to_sync = pc()
|
|
271
|
+
results = self.sync_contents(
|
|
272
|
+
brains_to_sync=brains_to_sync,
|
|
273
|
+
solr_items=solr_items,
|
|
274
|
+
disable_progress=disable_progress,
|
|
275
|
+
)
|
|
276
|
+
elapsed_time = next(elapsed)
|
|
277
|
+
logger.info("Completed in {}.".format(elapsed_time))
|
|
278
|
+
if len(results["indexed"]):
|
|
279
|
+
logger.info(
|
|
280
|
+
"Indexed {} items (new or modified):".format(
|
|
281
|
+
len(results["indexed"])
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
for path in results["indexed"]:
|
|
285
|
+
logger.info("- {}".format(path))
|
|
286
|
+
if len(results["removed"]):
|
|
287
|
+
logger.info(
|
|
288
|
+
"Removed {} items (no more indexeable):".format(
|
|
289
|
+
len(results["removed"])
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
for path in results["removed"]:
|
|
293
|
+
logger.info("- {}".format(path))
|
|
294
|
+
if len(results["not_indexed"]):
|
|
295
|
+
logger.info(
|
|
296
|
+
"NOT indexed {} items (errors):".format(
|
|
297
|
+
len(results["not_indexed"])
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
for error in results["not_indexed"]:
|
|
301
|
+
logger.info(
|
|
302
|
+
"- {path} ({message})".format(
|
|
303
|
+
path=error.get("path", ""),
|
|
304
|
+
message=error.get("message", "Undefined Error"),
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def sync_contents(
|
|
309
|
+
self, brains_to_sync, solr_items, disable_progress=False
|
|
310
|
+
):
|
|
311
|
+
indexed = []
|
|
312
|
+
not_indexed = []
|
|
313
|
+
removed = []
|
|
314
|
+
if not disable_progress:
|
|
315
|
+
status = self.setupAnnotations(
|
|
316
|
+
items_len=len(brains_to_sync), message="Sync contents to SOLR"
|
|
317
|
+
)
|
|
318
|
+
for brain in brains_to_sync:
|
|
319
|
+
if not disable_progress:
|
|
320
|
+
status["counter"] = status["counter"] + 1
|
|
321
|
+
commit()
|
|
322
|
+
item = brain.getObject()
|
|
323
|
+
if brain.UID not in solr_items:
|
|
324
|
+
# missing from solr: try to index it
|
|
325
|
+
try:
|
|
326
|
+
res = push_to_solr(item)
|
|
327
|
+
if res:
|
|
328
|
+
indexed.append(brain.getPath())
|
|
329
|
+
except SolrError as e:
|
|
330
|
+
not_indexed.append(
|
|
331
|
+
{"path": brain.getPath(), "message": e.__str__()}
|
|
332
|
+
)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
not_indexed.append(
|
|
335
|
+
{"path": brain.getPath(), "message": str(e)}
|
|
336
|
+
)
|
|
337
|
+
else:
|
|
338
|
+
item = brain.getObject()
|
|
339
|
+
if not can_index(item):
|
|
340
|
+
remove_from_solr(brain.UID)
|
|
341
|
+
removed.append(brain.getPath())
|
|
342
|
+
continue
|
|
343
|
+
if (
|
|
344
|
+
parse_date_as_datetime(brain.modified)
|
|
345
|
+
!= solr_items[brain.UID] # noqa
|
|
346
|
+
):
|
|
347
|
+
# item has been modified and not synced in solr
|
|
348
|
+
try:
|
|
349
|
+
res = push_to_solr(item)
|
|
350
|
+
if res:
|
|
351
|
+
indexed.append(brain.getPath())
|
|
352
|
+
except Exception as e:
|
|
353
|
+
not_indexed.append(
|
|
354
|
+
{"path": brain.getPath(), "message": str(e)}
|
|
355
|
+
)
|
|
356
|
+
if not disable_progress:
|
|
357
|
+
status["in_progress"] = False
|
|
358
|
+
return {
|
|
359
|
+
"indexed": indexed,
|
|
360
|
+
"not_indexed": not_indexed,
|
|
361
|
+
"removed": removed,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class DoReindexView(ReindexBaseView):
|
|
366
|
+
def __call__(self):
|
|
367
|
+
authenticator = getMultiAdapter(
|
|
368
|
+
(self.context, self.request), name=u"authenticator"
|
|
369
|
+
)
|
|
370
|
+
if not authenticator.verify():
|
|
371
|
+
raise Unauthorized
|
|
372
|
+
# disable noisy logging from pysolr
|
|
373
|
+
debug = os.environ.get("DEBUG_SOLR", "").lower() not in ("true", "1")
|
|
374
|
+
if debug:
|
|
375
|
+
pysolr_logger = logging.getLogger("pysolr")
|
|
376
|
+
pt_logger = logging.getLogger("PortalTransforms")
|
|
377
|
+
pysolr_logger.setLevel(logging.WARNING)
|
|
378
|
+
pt_logger.setLevel(logging.WARNING)
|
|
379
|
+
|
|
380
|
+
self.reindexPloneToSolr()
|
|
381
|
+
if debug:
|
|
382
|
+
pysolr_logger.setLevel(logging.INFO)
|
|
383
|
+
pt_logger.setLevel(logging.INFO)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class DoSyncView(ReindexBaseView):
|
|
387
|
+
def __call__(self, cron_view=False):
|
|
388
|
+
if not cron_view:
|
|
389
|
+
authenticator = getMultiAdapter(
|
|
390
|
+
(self.context, self.request), name=u"authenticator"
|
|
391
|
+
)
|
|
392
|
+
if not authenticator.verify():
|
|
393
|
+
raise Unauthorized
|
|
394
|
+
# disable noisy logging from pysolr
|
|
395
|
+
if os.environ.get("DEBUG_SOLR", "").lower() not in ("true", "1"):
|
|
396
|
+
pysolr_logger = logging.getLogger("pysolr")
|
|
397
|
+
pt_logger = logging.getLogger("PortalTransforms")
|
|
398
|
+
pysolr_logger.setLevel(logging.WARNING)
|
|
399
|
+
pt_logger.setLevel(logging.WARNING)
|
|
400
|
+
|
|
401
|
+
self.removeZombiesFromSolr(disable_progress=cron_view)
|
|
402
|
+
self.syncPloneWithSolr(disable_progress=cron_view)
|
|
403
|
+
|
|
404
|
+
pysolr_logger.setLevel(logging.INFO)
|
|
405
|
+
pt_logger.setLevel(logging.INFO)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class ReindexProgressView(BrowserView):
|
|
409
|
+
def __call__(self):
|
|
410
|
+
self.request.response.setHeader("Content-Type", "application/json")
|
|
411
|
+
return json.dumps(self.status)
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def status(self):
|
|
415
|
+
annotations = IAnnotations(api.portal.get())
|
|
416
|
+
return dict(annotations.get("solr_reindex", {}))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# REACT VIEWS
|
|
420
|
+
class ReactView(BrowserView):
|
|
421
|
+
""" """
|
|
422
|
+
|
|
423
|
+
@ram.cache(lambda *args: time() // (60 * 60))
|
|
424
|
+
def get_version(self):
|
|
425
|
+
return pkg_resources.get_distribution("rer.solrpush").version
|
|
426
|
+
|
|
427
|
+
def get_env_mode(self):
|
|
428
|
+
return (
|
|
429
|
+
api.portal.get_registry_record("plone.resources.development")
|
|
430
|
+
and "dev" # noqa
|
|
431
|
+
or "prod" # noqa
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def get_resource_js(self, name="main"):
|
|
435
|
+
return JS_TEMPLATE.format(
|
|
436
|
+
portal_url=api.portal.get().absolute_url(),
|
|
437
|
+
env_mode=self.get_env_mode(),
|
|
438
|
+
name=name,
|
|
439
|
+
version=self.get_version(),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def get_resource_css(self, name="main"):
|
|
443
|
+
return CSS_TEMPLATE.format(
|
|
444
|
+
portal_url=api.portal.get().absolute_url(),
|
|
445
|
+
env_mode=self.get_env_mode(),
|
|
446
|
+
name=name,
|
|
447
|
+
version=self.get_version(),
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def token(self):
|
|
452
|
+
return createToken()
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def is_active(self):
|
|
456
|
+
return is_solr_active()
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class ReindexSolrView(ReactView):
|
|
460
|
+
|
|
461
|
+
label = _("maintenance_reindex_label", default="Reindex SOLR")
|
|
462
|
+
description = _(
|
|
463
|
+
"maintenance_reindex_help",
|
|
464
|
+
default="Get all Plone contents and reindex them on SOLR.",
|
|
465
|
+
)
|
|
466
|
+
action = "do-reindex"
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class SyncSolrView(ReactView):
|
|
470
|
+
|
|
471
|
+
label = _("maintenance_sync_label", default="Sync SOLR")
|
|
472
|
+
description = _(
|
|
473
|
+
"maintenance_sync_help",
|
|
474
|
+
default="Remove no more existing contents from SOLR and sync with Plone.", # noqa
|
|
475
|
+
)
|
|
476
|
+
action = "do-sync"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from plone.app.layout.viewlets.common import ViewletBase
|
|
3
|
+
from zope.annotation.interfaces import IAnnotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class QueryDebug(ViewletBase):
|
|
7
|
+
def update(self):
|
|
8
|
+
self.query = self.get_query()
|
|
9
|
+
|
|
10
|
+
def get_query(self):
|
|
11
|
+
annotations = IAnnotations(self.request)
|
|
12
|
+
return annotations.get("solr_query", "")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from operator import itemgetter
|
|
3
|
+
from plone import api
|
|
4
|
+
from plone.app.contentlisting.interfaces import IContentListing
|
|
5
|
+
from plone.app.querystring import queryparser
|
|
6
|
+
from plone.app.querystring.interfaces import IParsedQueryIndexModifier
|
|
7
|
+
from plone.app.querystring.interfaces import IQueryModifier
|
|
8
|
+
from plone.app.querystring.querybuilder import QueryBuilder as BaseView
|
|
9
|
+
from plone.batching import Batch
|
|
10
|
+
from Products.CMFCore.utils import getToolByName
|
|
11
|
+
from rer.solrpush.parser import SolrResponse
|
|
12
|
+
from rer.solrpush.utils.solr_indexer import get_site_title
|
|
13
|
+
from rer.solrpush.utils import search as solr_search
|
|
14
|
+
from zope.component import getUtilitiesFor
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
SORT_ON_MAPPING = {"sortable_title": "Title"}
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class QueryBuilder(BaseView):
|
|
26
|
+
def _makequery(
|
|
27
|
+
self,
|
|
28
|
+
query=None,
|
|
29
|
+
batch=False,
|
|
30
|
+
b_start=0,
|
|
31
|
+
b_size=30,
|
|
32
|
+
sort_on=None,
|
|
33
|
+
sort_order=None,
|
|
34
|
+
limit=0,
|
|
35
|
+
brains=False,
|
|
36
|
+
custom_query=None,
|
|
37
|
+
):
|
|
38
|
+
"""Parse the (form)query and return using multi-adapter"""
|
|
39
|
+
query_modifiers = getUtilitiesFor(IQueryModifier)
|
|
40
|
+
for name, modifier in sorted(query_modifiers, key=itemgetter(0)):
|
|
41
|
+
query = modifier(query)
|
|
42
|
+
|
|
43
|
+
parsedquery = queryparser.parseFormquery(
|
|
44
|
+
self.context, query, sort_on, sort_order
|
|
45
|
+
)
|
|
46
|
+
index_modifiers = getUtilitiesFor(IParsedQueryIndexModifier)
|
|
47
|
+
for name, modifier in index_modifiers:
|
|
48
|
+
if name in parsedquery:
|
|
49
|
+
new_name, query = modifier(parsedquery[name])
|
|
50
|
+
parsedquery[name] = query
|
|
51
|
+
# if a new index name has been returned, we need to replace
|
|
52
|
+
# the native ones
|
|
53
|
+
if name != new_name:
|
|
54
|
+
del parsedquery[name]
|
|
55
|
+
parsedquery[new_name] = query
|
|
56
|
+
|
|
57
|
+
# Check for valid indexes
|
|
58
|
+
catalog = getToolByName(self.context, "portal_catalog")
|
|
59
|
+
valid_indexes = [
|
|
60
|
+
index for index in parsedquery if index in catalog.indexes()
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
# We'll ignore any invalid index, but will return an empty set if none
|
|
64
|
+
# of the indexes are valid.
|
|
65
|
+
if not valid_indexes:
|
|
66
|
+
logger.warning(
|
|
67
|
+
"Using empty query because there are no valid indexes used."
|
|
68
|
+
)
|
|
69
|
+
parsedquery = {}
|
|
70
|
+
|
|
71
|
+
empty_query = not parsedquery # store emptiness
|
|
72
|
+
|
|
73
|
+
if batch:
|
|
74
|
+
parsedquery["b_start"] = b_start
|
|
75
|
+
parsedquery["b_size"] = b_size
|
|
76
|
+
elif limit:
|
|
77
|
+
parsedquery["sort_limit"] = limit
|
|
78
|
+
|
|
79
|
+
if "path" not in parsedquery:
|
|
80
|
+
parsedquery["path"] = {"query": ""}
|
|
81
|
+
|
|
82
|
+
if isinstance(custom_query, dict) and custom_query:
|
|
83
|
+
# Update the parsed query with an extra query dictionary. This may
|
|
84
|
+
# override the parsed query. The custom_query is a dictonary of
|
|
85
|
+
# index names and their associated query values.
|
|
86
|
+
parsedquery.update(custom_query)
|
|
87
|
+
empty_query = False
|
|
88
|
+
|
|
89
|
+
# filter bad term and operator in query
|
|
90
|
+
parsedquery = self.filter_query(parsedquery)
|
|
91
|
+
results = []
|
|
92
|
+
|
|
93
|
+
# RER.SOLRPUSH PATCH
|
|
94
|
+
search_with_solr = False
|
|
95
|
+
if "searchWithSolr" in parsedquery:
|
|
96
|
+
if parsedquery["searchWithSolr"]["query"]:
|
|
97
|
+
search_with_solr = True
|
|
98
|
+
del parsedquery["searchWithSolr"]
|
|
99
|
+
if not empty_query:
|
|
100
|
+
if search_with_solr:
|
|
101
|
+
if "SearchableText" in parsedquery:
|
|
102
|
+
if isinstance(parsedquery["SearchableText"], dict):
|
|
103
|
+
parsedquery["SearchableText"]["query"] = parsedquery[
|
|
104
|
+
"SearchableText"
|
|
105
|
+
]["query"].rstrip("*")
|
|
106
|
+
else:
|
|
107
|
+
parsedquery["SearchableText"] = parsedquery[
|
|
108
|
+
"SearchableText"
|
|
109
|
+
].rstrip("*")
|
|
110
|
+
results = SolrResponse(
|
|
111
|
+
data=solr_search(
|
|
112
|
+
**self.clean_query_for_solr(query=parsedquery)
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
results = catalog(**parsedquery)
|
|
117
|
+
if (
|
|
118
|
+
getattr(results, "actual_result_count", False)
|
|
119
|
+
and limit # noqa
|
|
120
|
+
and results.actual_result_count > limit # noqa
|
|
121
|
+
):
|
|
122
|
+
results.actual_result_count = limit
|
|
123
|
+
|
|
124
|
+
if not brains and not search_with_solr:
|
|
125
|
+
results = IContentListing(results)
|
|
126
|
+
if batch:
|
|
127
|
+
results = Batch(results, b_size, start=b_start)
|
|
128
|
+
return results
|
|
129
|
+
|
|
130
|
+
def clean_query_for_solr(self, query):
|
|
131
|
+
fixed_query = {}
|
|
132
|
+
filtered_sites = []
|
|
133
|
+
for k, v in query.items():
|
|
134
|
+
if k == "sort_on":
|
|
135
|
+
fixed_query[k] = SORT_ON_MAPPING.get(v, v)
|
|
136
|
+
elif k == "path":
|
|
137
|
+
portal_state = api.content.get_view(
|
|
138
|
+
context=self.context,
|
|
139
|
+
request=self.request,
|
|
140
|
+
name=u"plone_portal_state",
|
|
141
|
+
)
|
|
142
|
+
root_path = portal_state.navigation_root_path()
|
|
143
|
+
path = self.extract_value(v)
|
|
144
|
+
if len(path) == 1 and path[0].rstrip("/") == root_path:
|
|
145
|
+
if "solr_sites" not in query.keys():
|
|
146
|
+
filtered_sites.append(get_site_title())
|
|
147
|
+
else:
|
|
148
|
+
fixed_query["path_parents"] = path
|
|
149
|
+
elif k == "solr_sites":
|
|
150
|
+
sites = self.extract_value(v)
|
|
151
|
+
if sites != "null":
|
|
152
|
+
filtered_sites = sites
|
|
153
|
+
elif k == "solr_subjects":
|
|
154
|
+
fixed_query["Subject"] = self.extract_value(v)
|
|
155
|
+
continue
|
|
156
|
+
elif k == "solr_portal_types":
|
|
157
|
+
fixed_query["portal_type"] = self.extract_value(v)
|
|
158
|
+
continue
|
|
159
|
+
else:
|
|
160
|
+
fixed_query[k] = v
|
|
161
|
+
if filtered_sites:
|
|
162
|
+
fixed_query["site_name"] = filtered_sites
|
|
163
|
+
return {"query": fixed_query}
|
|
164
|
+
|
|
165
|
+
def extract_value(self, value):
|
|
166
|
+
if isinstance(value, dict) and "query" in value:
|
|
167
|
+
return value["query"]
|
|
168
|
+
return value
|