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,352 @@
1
+ # -*- coding: utf-8 -*-
2
+ from plone import api
3
+ from rer.solrpush import _
4
+ from rer.solrpush.interfaces import IElevateSettings
5
+ from zope.i18n import translate
6
+ from rer.solrpush.utils.solr_common import get_solr_connection
7
+ from rer.solrpush.utils.solr_common import get_setting
8
+ from rer.solrpush.utils.solr_common import get_index_fields
9
+ from rer.solrpush.interfaces.settings import IRerSolrpushSettings
10
+ from rer.solrpush.utils.solr_indexer import parse_date_str
11
+ from DateTime import DateTime
12
+ from pysolr import safe_urlencode
13
+ from zope.annotation.interfaces import IAnnotations
14
+ from zope.globalrequest import getRequest
15
+
16
+ import logging
17
+ import json
18
+ import re
19
+ import requests
20
+ import six
21
+
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ TRIM = re.compile(r"\s+")
26
+ ESCAPE_CHARS_RE = re.compile(r'(?<!\\)(?P<char>[&|+\-!(){}[\]^"~*?:])')
27
+
28
+ # HELPER METHODS
29
+
30
+
31
+ def fix_value(value, index_type="", wrap=True):
32
+ operator = "or"
33
+ if isinstance(value, dict):
34
+ query = value.get("query", "")
35
+ range = value.get("range", "")
36
+ operator = value.get("operator", "or")
37
+ if not query:
38
+ return ""
39
+ if range:
40
+ if range == "min":
41
+ value = "[{} TO *]".format(parse_date_str(query))
42
+ if range == "max":
43
+ value = "[* TO {}]".format(parse_date_str(query))
44
+ if range == "minmax":
45
+ value = "[{} TO {}]".format(
46
+ parse_date_str(query[0]), parse_date_str(query[1])
47
+ )
48
+ return value
49
+ else:
50
+ value = query
51
+ if isinstance(value, DateTime):
52
+ return "[{} TO *]".format(parse_date_str(value))
53
+ if isinstance(value, six.string_types):
54
+ if index_type == "date":
55
+ if "TO" not in value:
56
+ return "[{} TO *]".format(parse_date_str(value))
57
+ else:
58
+ return value
59
+ return escape_special_characters(value, wrap)
60
+ elif isinstance(value, list):
61
+ join_str = " {} ".format(operator.upper())
62
+ return "({})".format(
63
+ join_str.join([escape_special_characters(x, wrap) for x in value])
64
+ )
65
+ logger.warning(
66
+ "[fix_value]: unable to escape value: {}. skipping".format(value)
67
+ )
68
+ return
69
+
70
+
71
+ def escape_special_characters(value, wrap):
72
+ new_value = ESCAPE_CHARS_RE.sub(r"\\\g<char>", value)
73
+ if six.PY2 and isinstance(new_value, six.string_types):
74
+ try:
75
+ new_value = new_value.encode("utf-8")
76
+ except UnicodeDecodeError:
77
+ pass
78
+ if wrap:
79
+ return '"{}"'.format(new_value)
80
+ return new_value
81
+
82
+
83
+ def set_sort_parameter(query):
84
+ sort_on = query.get("sort_on")
85
+ if sort_on == "sortable_title":
86
+ sort_on = "Title"
87
+ sort_order = query.get("sort_order", "asc")
88
+ if sort_order in ["reverse", "descending"]:
89
+ sort_order = "desc"
90
+ if sort_order in ["ascending"]:
91
+ sort_order = "asc"
92
+ return "{sort_on} {sort_order}".format(
93
+ sort_on=sort_on, sort_order=sort_order
94
+ )
95
+
96
+
97
+ def generate_query(
98
+ query,
99
+ fl=None,
100
+ facets=False,
101
+ facet_fields=["Subject", "portal_type"],
102
+ ):
103
+ solr_query = {
104
+ "q": "",
105
+ "fq": [],
106
+ "facet": facets and "true" or "false",
107
+ "start": query.get("b_start", 0),
108
+ "rows": query.get("b_size", 20),
109
+ "json.nl": "arrmap",
110
+ }
111
+ solr_query.update(extract_from_query(query=query))
112
+
113
+ if not solr_query["q"]:
114
+ solr_query["q"] = "*:*"
115
+ if "sort_on" in query:
116
+ solr_query["sort"] = set_sort_parameter(query)
117
+ if facets:
118
+ solr_query["facet.field"] = facet_fields
119
+ solr_query["facet.mincount"] = 1
120
+ solr_query["facet.limit"] = -1
121
+ if fl:
122
+ if "UID" not in fl:
123
+ # we need it because if we ask for [elevated] value, solr returns
124
+ # error if we don't ask also the primary key
125
+ if isinstance(fl, six.text_type):
126
+ fl = "{} UID".format(fl)
127
+ elif isinstance(fl, list):
128
+ fl.append("UID")
129
+ solr_query["fl"] = fl
130
+
131
+ solr_query.update(add_query_tweaks())
132
+ # elevate
133
+ elevate = manage_elevate(query=query)
134
+ if elevate.get("enableElevation", False):
135
+ solr_query.update(manage_elevate(query=query))
136
+ if "fl" not in solr_query:
137
+ solr_query["fl"] = "* [elevated]"
138
+ else:
139
+ if "[elevated]" not in solr_query["fl"]:
140
+ if isinstance(solr_query["fl"], six.text_type):
141
+ solr_query["fl"] = "{} [elevated]".format(solr_query["fl"])
142
+ elif isinstance(solr_query["fl"], list):
143
+ solr_query["fl"].append("[elevated]")
144
+ return solr_query
145
+
146
+
147
+ def manage_elevate(query):
148
+ params = {}
149
+ params["enableElevation"] = "false"
150
+ searchableText = query.get("SearchableText", "").rstrip("*")
151
+ if not searchableText:
152
+ return params
153
+ if not searchableText.replace(" ", ""):
154
+ return params
155
+ elevate_schema = extract_elevate_schema(query=query)
156
+
157
+ if not elevate_schema:
158
+ return params
159
+ try:
160
+ if six.PY2:
161
+ text = (
162
+ TRIM.sub(" ", searchableText).strip().decode("utf-8").lower()
163
+ )
164
+ else:
165
+ text = TRIM.sub(" ", searchableText).strip().lower()
166
+ except Exception:
167
+ logger.exception("error parsing %r", searchableText)
168
+ text = None
169
+ if text:
170
+ for config in elevate_schema:
171
+ # exact match
172
+ # if text == s:
173
+
174
+ # contains
175
+ # if s in text:
176
+
177
+ # contains regexp
178
+ for word in config.get("text", []):
179
+ if re.search("(^|\s+)" + word + "(\s+|$)", text): # noqa
180
+ uids = [x.get("UID", "") for x in config.get("uid", [])]
181
+ params["enableElevation"] = "true"
182
+ params["elevateIds"] = ",".join(uids)
183
+ break
184
+ return params
185
+
186
+
187
+ def extract_elevate_schema(query):
188
+ """
189
+ If no site_name is passed in query and remote_elevate_schema is set,
190
+ return the schema from remote site.
191
+ """
192
+ local_schema_json = get_setting(
193
+ field="elevate_schema", interface=IElevateSettings
194
+ )
195
+ remote_schema = get_setting(
196
+ field="remote_elevate_schema", interface=IRerSolrpushSettings
197
+ )
198
+
199
+ # if local_schema is set and there is a filter on site_name, return the
200
+ # local elevate schema
201
+ if local_schema_json:
202
+ local_schema = json.loads(local_schema_json)
203
+ if local_schema:
204
+ if query.get("site_name", []):
205
+ return local_schema
206
+ if not remote_schema:
207
+ return local_schema
208
+ if remote_schema:
209
+ try:
210
+ resp = requests.get(
211
+ remote_schema, headers={"Accept": "application/json"}
212
+ )
213
+ except requests.exceptions.RequestException as err:
214
+ logger.error("Connection problem:\n{0}".format(err))
215
+ return []
216
+ if resp.status_code != 200:
217
+ logger.error(
218
+ "Problems fetching schema:\n{0}\n{1}".format(
219
+ resp.status_code, resp.reason
220
+ )
221
+ )
222
+ return []
223
+ return resp.json()
224
+ return []
225
+
226
+
227
+ def extract_from_query(query):
228
+ index_fields = get_index_fields()
229
+ params = {"q": "", "fq": []}
230
+ for index, value in query.items():
231
+ if not value and value is not False:
232
+ continue
233
+ if index == "*":
234
+ params["q"] = "*:*"
235
+ continue
236
+ if index in ["", "SearchableText"]:
237
+ params["q"] = fix_value(value, wrap=False)
238
+
239
+ continue
240
+ index_infos = index_fields.get(index, {})
241
+ if not index_infos:
242
+ continue
243
+ # other indexes will be added in fq
244
+ value = fix_value(value=value, index_type=index_infos.get("type", ""))
245
+ if value or value is False:
246
+ if index == "path":
247
+ index = "path_parents"
248
+ params["fq"].append(
249
+ "{index}:{value}".format(index=index, value=value)
250
+ )
251
+ can_access_inactive = api.user.has_permission(
252
+ "Access inactive portal content"
253
+ )
254
+ if not can_access_inactive:
255
+ # do not show expired or not yet published items
256
+ if "expires" not in query:
257
+ params["fq"].append("expires:[NOW TO *]")
258
+ if "effective" not in query:
259
+ params["fq"].append("effective:[* TO NOW]")
260
+ return params
261
+
262
+
263
+ def add_query_tweaks():
264
+ """
265
+ Add query tweaks set in control panel
266
+ """
267
+ params = {}
268
+ for id in ["qf", "bq", "bf"]:
269
+ value = get_setting(field=id)
270
+ if value:
271
+ params[id] = value
272
+ if params:
273
+ params["defType"] = "edismax"
274
+ return params
275
+
276
+
277
+ # END HELPER METHODS
278
+
279
+ # LIBRARY METHODS
280
+ def search(
281
+ query,
282
+ fl=None,
283
+ facets=False,
284
+ facet_fields=["Subject", "portal_type"],
285
+ **kwargs
286
+ ):
287
+ """[summary] TODO
288
+
289
+ Args:
290
+ query ([type]): [description] TODO
291
+ fl (str, optional): [description]. Defaults to "".
292
+ facets (bool, optional): [description]. Defaults to False.
293
+ facet_fields (list, optional): [description].
294
+ Defaults to ["Subject", "portal_type"].
295
+
296
+ Returns:
297
+ [type]: [description]
298
+ """
299
+ solr = get_solr_connection()
300
+ if not solr:
301
+ msg = u"Unable to search using solr. Configuration is incomplete."
302
+ logger.error(msg)
303
+ return {
304
+ "error": True,
305
+ "message": translate(
306
+ _("solr_configuration_error_label", default=msg),
307
+ context=api.portal.get().REQUEST,
308
+ ),
309
+ }
310
+ solr_query = generate_query(
311
+ query,
312
+ fl=fl,
313
+ facets=facets,
314
+ facet_fields=facet_fields,
315
+ )
316
+ try:
317
+ _set_query_debug(solr=solr, params=solr_query)
318
+ res = solr.search(**solr_query)
319
+ return res
320
+ except Exception as e:
321
+ logger.exception(e)
322
+ return {
323
+ "error": True,
324
+ "message": translate(
325
+ _(
326
+ "search_error_label",
327
+ default=u"Unable to perform a search with SOLR."
328
+ u" Please contact the site administrator or wait some"
329
+ u" minutes.",
330
+ ),
331
+ context=api.portal.get().REQUEST,
332
+ ),
333
+ }
334
+
335
+
336
+ def _set_query_debug(solr, params):
337
+ try:
338
+ if not get_setting(
339
+ field="query_debug", interface=IRerSolrpushSettings
340
+ ):
341
+ return
342
+ except KeyError:
343
+ # key not available: do not save data
344
+ return
345
+ request = getRequest()
346
+ annotations = IAnnotations(request)
347
+ annotations["solr_query"] = {
348
+ "url": "{url}/select?{params}".format(
349
+ url=solr.url, params=safe_urlencode(params, True)
350
+ ),
351
+ "params": params,
352
+ }
@@ -0,0 +1,50 @@
1
+ # -*- coding: utf-8 -*-
2
+ from rer.solrpush.utils import search
3
+ from zope.interface import implementer
4
+ from zope.schema.interfaces import IVocabularyFactory
5
+ from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
6
+
7
+
8
+ class FacetsVocabulary(object):
9
+ def get_terms(self):
10
+ solr_results = search(
11
+ query={"*": "*", "b_size": 1},
12
+ fl="UID",
13
+ facets=True,
14
+ facet_fields=self.facet_field,
15
+ )
16
+ if isinstance(solr_results, dict) and solr_results.get("error", False):
17
+ return []
18
+ facets = solr_results.facets["facet_fields"].get(self.facet_field, [])
19
+ if not facets:
20
+ return []
21
+ terms = []
22
+ for facet in facets:
23
+ for key in facet.keys():
24
+ terms.append(
25
+ SimpleTerm(value=key, token=key.encode("utf-8"), title=key)
26
+ )
27
+ return terms
28
+
29
+ def __call__(self, context):
30
+ return SimpleVocabulary(self.get_terms(), swallow_duplicates=True)
31
+
32
+
33
+ @implementer(IVocabularyFactory)
34
+ class AvailableSitesVocabularyFactory(FacetsVocabulary):
35
+ facet_field = "site_name"
36
+
37
+
38
+ @implementer(IVocabularyFactory)
39
+ class AvailableSubjectsVocabularyFactory(FacetsVocabulary):
40
+ facet_field = "Subject"
41
+
42
+
43
+ @implementer(IVocabularyFactory)
44
+ class AvailablePortalTypesVocabularyFactory(FacetsVocabulary):
45
+ facet_field = "portal_type"
46
+
47
+
48
+ AvailablePortalTypesVocabulary = AvailablePortalTypesVocabularyFactory()
49
+ AvailableSitesVocabulary = AvailableSitesVocabularyFactory()
50
+ AvailableSubjectsVocabulary = AvailableSubjectsVocabularyFactory()
@@ -0,0 +1 @@
1
+ import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('rer',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('rer', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('rer', [os.path.dirname(p)])));m = m or sys.modules.setdefault('rer', types.ModuleType('rer'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)