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.
Files changed (116) hide show
  1. rer/solrpush/__init__.py +7 -0
  2. rer/solrpush/adapters/__init__.py +1 -0
  3. rer/solrpush/adapters/configure.zcml +13 -0
  4. rer/solrpush/adapters/file.py +52 -0
  5. rer/solrpush/adapters/rss_feed.py +28 -0
  6. rer/solrpush/behaviors/__init__.py +0 -0
  7. rer/solrpush/behaviors/configure.zcml +16 -0
  8. rer/solrpush/behaviors/solr_fields.py +41 -0
  9. rer/solrpush/browser/__init__.py +0 -0
  10. rer/solrpush/browser/configure.zcml +111 -0
  11. rer/solrpush/browser/controlpanel.py +96 -0
  12. rer/solrpush/browser/elevate_settings.py +35 -0
  13. rer/solrpush/browser/maintenance.py +476 -0
  14. rer/solrpush/browser/query_debug.py +12 -0
  15. rer/solrpush/browser/querybuilder.py +168 -0
  16. rer/solrpush/browser/scales.py +80 -0
  17. rer/solrpush/browser/static/.gitkeep +0 -0
  18. rer/solrpush/browser/static/dist/dev/main.js +58240 -0
  19. rer/solrpush/browser/static/dist/dev/main.js.map +180 -0
  20. rer/solrpush/browser/static/dist/prod/main.js +9604 -0
  21. rer/solrpush/browser/static/dist/prod/main.js.map +1 -0
  22. rer/solrpush/browser/static/javascripts/FacetsContainer/index.js +46 -0
  23. rer/solrpush/browser/static/javascripts/ProgressBar/index.js +40 -0
  24. rer/solrpush/browser/static/javascripts/ProgressBarContainer/index.js +66 -0
  25. rer/solrpush/browser/static/javascripts/index.js +14 -0
  26. rer/solrpush/browser/static/package.json +13 -0
  27. rer/solrpush/browser/static/styles.css +22 -0
  28. rer/solrpush/browser/static/styles.css.map +1 -0
  29. rer/solrpush/browser/static/styles.scss +27 -0
  30. rer/solrpush/browser/static/yarn.lock +1408 -0
  31. rer/solrpush/browser/templates/controlpanel_layout.pt +49 -0
  32. rer/solrpush/browser/templates/query_debug.pt +17 -0
  33. rer/solrpush/browser/templates/reindex_solr.pt +35 -0
  34. rer/solrpush/configure.zcml +81 -0
  35. rer/solrpush/indexer.py +120 -0
  36. rer/solrpush/interfaces/__init__.py +8 -0
  37. rer/solrpush/interfaces/adapter.py +13 -0
  38. rer/solrpush/interfaces/elevate.py +98 -0
  39. rer/solrpush/interfaces/layer.py +6 -0
  40. rer/solrpush/interfaces/queueprocessor.py +6 -0
  41. rer/solrpush/interfaces/settings.py +194 -0
  42. rer/solrpush/locales/README.rst +37 -0
  43. rer/solrpush/locales/__init__.py +0 -0
  44. rer/solrpush/locales/en/LC_MESSAGES/rer.solrpush.mo +0 -0
  45. rer/solrpush/locales/en/LC_MESSAGES/rer.solrpush.po +399 -0
  46. rer/solrpush/locales/it/LC_MESSAGES/rer.solrpush.mo +0 -0
  47. rer/solrpush/locales/it/LC_MESSAGES/rer.solrpush.po +399 -0
  48. rer/solrpush/locales/rer.solrpush.pot +402 -0
  49. rer/solrpush/locales/update.py +71 -0
  50. rer/solrpush/locales/update.sh +12 -0
  51. rer/solrpush/parser.py +211 -0
  52. rer/solrpush/permissions.zcml +18 -0
  53. rer/solrpush/profiles/default/actions.xml +21 -0
  54. rer/solrpush/profiles/default/browserlayer.xml +7 -0
  55. rer/solrpush/profiles/default/catalog.xml +4 -0
  56. rer/solrpush/profiles/default/controlpanel.xml +31 -0
  57. rer/solrpush/profiles/default/metadata.xml +8 -0
  58. rer/solrpush/profiles/default/registry/bundle.xml +25 -0
  59. rer/solrpush/profiles/default/registry/criteria.xml +55 -0
  60. rer/solrpush/profiles/default/registry/settings.xml +6 -0
  61. rer/solrpush/profiles/default/rolemap.xml +13 -0
  62. rer/solrpush/profiles/default/types/Document.xml +12 -0
  63. rer/solrpush/profiles/to_1100/componentregistry.xml +9 -0
  64. rer/solrpush/profiles/to_1100/metadata.xml +5 -0
  65. rer/solrpush/profiles/to_1300/controlpanel.xml +13 -0
  66. rer/solrpush/profiles/to_1300/registry.xml +5 -0
  67. rer/solrpush/profiles/uninstall/browserlayer.xml +7 -0
  68. rer/solrpush/profiles/uninstall/controlpanel.xml +21 -0
  69. rer/solrpush/profiles/uninstall/registry.xml +17 -0
  70. rer/solrpush/restapi/__init__.py +0 -0
  71. rer/solrpush/restapi/configure.zcml +8 -0
  72. rer/solrpush/restapi/services/__init__.py +0 -0
  73. rer/solrpush/restapi/services/configure.zcml +8 -0
  74. rer/solrpush/restapi/services/elevate_schema/__init__.py +0 -0
  75. rer/solrpush/restapi/services/elevate_schema/configure.zcml +14 -0
  76. rer/solrpush/restapi/services/elevate_schema/get.py +12 -0
  77. rer/solrpush/restapi/services/solr_search/__init__.py +0 -0
  78. rer/solrpush/restapi/services/solr_search/batch.py +129 -0
  79. rer/solrpush/restapi/services/solr_search/configure.zcml +14 -0
  80. rer/solrpush/restapi/services/solr_search/get.py +25 -0
  81. rer/solrpush/restapi/services/solr_search/solr_search_handler.py +86 -0
  82. rer/solrpush/setuphandlers.py +20 -0
  83. rer/solrpush/testing.py +161 -0
  84. rer/solrpush/tests/__init__.py +0 -0
  85. rer/solrpush/tests/docs/example.docx +0 -0
  86. rer/solrpush/tests/docs/example.ods +0 -0
  87. rer/solrpush/tests/docs/example.pdf +0 -0
  88. rer/solrpush/tests/docs/plone_logo.png +0 -0
  89. rer/solrpush/tests/test_behaviors.py +61 -0
  90. rer/solrpush/tests/test_elevate.py +124 -0
  91. rer/solrpush/tests/test_events.py +181 -0
  92. rer/solrpush/tests/test_image_tags.py +85 -0
  93. rer/solrpush/tests/test_maintenance.py +165 -0
  94. rer/solrpush/tests/test_querybuilder.py +272 -0
  95. rer/solrpush/tests/test_restapi_search.py +127 -0
  96. rer/solrpush/tests/test_search.py +177 -0
  97. rer/solrpush/tests/test_setup.py +67 -0
  98. rer/solrpush/tests/test_solr_interface.py +359 -0
  99. rer/solrpush/tests/test_tika.py +85 -0
  100. rer/solrpush/tests/test_vocabularies.py +113 -0
  101. rer/solrpush/upgrades.py +76 -0
  102. rer/solrpush/upgrades.zcml +86 -0
  103. rer/solrpush/utils/__init__.py +6 -0
  104. rer/solrpush/utils/solr_common.py +122 -0
  105. rer/solrpush/utils/solr_indexer.py +295 -0
  106. rer/solrpush/utils/solr_search.py +352 -0
  107. rer/solrpush/vocabularies.py +50 -0
  108. rer.solrpush-1.3.4-py3.10-nspkg.pth +1 -0
  109. rer.solrpush-1.3.4.dist-info/LICENSE.GPL +339 -0
  110. rer.solrpush-1.3.4.dist-info/LICENSE.rst +15 -0
  111. rer.solrpush-1.3.4.dist-info/METADATA +507 -0
  112. rer.solrpush-1.3.4.dist-info/RECORD +116 -0
  113. rer.solrpush-1.3.4.dist-info/WHEEL +5 -0
  114. rer.solrpush-1.3.4.dist-info/entry_points.txt +5 -0
  115. rer.solrpush-1.3.4.dist-info/namespace_packages.txt +1 -0
  116. 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