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,113 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Setup tests for this package."""
|
|
3
|
+
from plone.app.testing import setRoles
|
|
4
|
+
from plone.app.testing import TEST_USER_ID
|
|
5
|
+
from rer.solrpush.utils.solr_common import init_solr_push
|
|
6
|
+
from rer.solrpush.utils.solr_common import get_solr_connection
|
|
7
|
+
from rer.solrpush.utils import push_to_solr
|
|
8
|
+
from rer.solrpush.testing import RER_SOLRPUSH_API_FUNCTIONAL_TESTING
|
|
9
|
+
from transaction import commit
|
|
10
|
+
from zope.component import getUtility
|
|
11
|
+
from zope.schema.interfaces import IVocabularyFactory
|
|
12
|
+
|
|
13
|
+
import unittest
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestVocabularies(unittest.TestCase):
|
|
17
|
+
"""Test show in search field behavior."""
|
|
18
|
+
|
|
19
|
+
layer = RER_SOLRPUSH_API_FUNCTIONAL_TESTING
|
|
20
|
+
|
|
21
|
+
def setUp(self):
|
|
22
|
+
"""
|
|
23
|
+
We create objects before initializing solr settings, so solr core is
|
|
24
|
+
always empty on setUp.
|
|
25
|
+
"""
|
|
26
|
+
self.portal = self.layer["portal"]
|
|
27
|
+
self.request = self.layer["request"]
|
|
28
|
+
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
|
29
|
+
|
|
30
|
+
init_solr_push()
|
|
31
|
+
commit()
|
|
32
|
+
|
|
33
|
+
push_to_solr(
|
|
34
|
+
{
|
|
35
|
+
"title": "Document from site foo",
|
|
36
|
+
"portal_type": "Document",
|
|
37
|
+
"site_name": "foo",
|
|
38
|
+
"UID": "000",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
push_to_solr(
|
|
42
|
+
{
|
|
43
|
+
"title": "Document from site foo with subject",
|
|
44
|
+
"portal_type": "Document",
|
|
45
|
+
"site_name": "foo",
|
|
46
|
+
"Subject": ["foo-subject"],
|
|
47
|
+
"UID": "111",
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
push_to_solr(
|
|
51
|
+
{
|
|
52
|
+
"title": "Document from site bar",
|
|
53
|
+
"portal_type": "Document",
|
|
54
|
+
"site_name": "bar",
|
|
55
|
+
"UID": "222",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
push_to_solr(
|
|
59
|
+
{
|
|
60
|
+
"title": "News Item from site bar",
|
|
61
|
+
"portal_type": "News Item",
|
|
62
|
+
"site_name": "bar",
|
|
63
|
+
"Subject": ["bar-subject"],
|
|
64
|
+
"UID": "333",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def tearDown(self):
|
|
69
|
+
solr = get_solr_connection()
|
|
70
|
+
solr.delete(q="*:*", commit=True)
|
|
71
|
+
commit()
|
|
72
|
+
|
|
73
|
+
def test_sites_vocabulary_return_all_stored_ones(self):
|
|
74
|
+
factory = getUtility(
|
|
75
|
+
IVocabularyFactory, "rer.solrpush.vocabularies.AvailableSites"
|
|
76
|
+
)
|
|
77
|
+
vocabulary = factory(self.portal)
|
|
78
|
+
|
|
79
|
+
self.assertEqual(len(vocabulary), 2)
|
|
80
|
+
self.assertEqual([x.title for x in vocabulary], ["bar", "foo"])
|
|
81
|
+
self.assertEqual(vocabulary.getTerm("foo").title, "foo")
|
|
82
|
+
self.assertEqual(vocabulary.getTerm("bar").title, "bar")
|
|
83
|
+
|
|
84
|
+
def test_Subject_vocabulary_return_all_stored_ones(self):
|
|
85
|
+
factory = getUtility(
|
|
86
|
+
IVocabularyFactory, "rer.solrpush.vocabularies.AvailableSubjects"
|
|
87
|
+
)
|
|
88
|
+
vocabulary = factory(self.portal)
|
|
89
|
+
|
|
90
|
+
self.assertEqual(len(vocabulary), 2)
|
|
91
|
+
self.assertEqual(
|
|
92
|
+
[x.title for x in vocabulary], ["bar-subject", "foo-subject"]
|
|
93
|
+
)
|
|
94
|
+
self.assertEqual(
|
|
95
|
+
vocabulary.getTerm("foo-subject").title, "foo-subject"
|
|
96
|
+
)
|
|
97
|
+
self.assertEqual(
|
|
98
|
+
vocabulary.getTerm("bar-subject").title, "bar-subject"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def test_portal_type_vocabulary_return_all_stored_ones(self):
|
|
102
|
+
factory = getUtility(
|
|
103
|
+
IVocabularyFactory,
|
|
104
|
+
"rer.solrpush.vocabularies.AvailablePortalTypes",
|
|
105
|
+
)
|
|
106
|
+
vocabulary = factory(self.portal)
|
|
107
|
+
|
|
108
|
+
self.assertEqual(len(vocabulary), 2)
|
|
109
|
+
self.assertEqual(
|
|
110
|
+
[x.title for x in vocabulary], ["Document", "News Item"]
|
|
111
|
+
)
|
|
112
|
+
self.assertEqual(vocabulary.getTerm("Document").title, "Document")
|
|
113
|
+
self.assertEqual(vocabulary.getTerm("News Item").title, "News Item")
|
rer/solrpush/upgrades.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from plone import api
|
|
3
|
+
from plone.app.upgrade.utils import installOrReinstallProduct
|
|
4
|
+
from rer.solrpush.utils.solr_common import init_solr_push
|
|
5
|
+
from rer.solrpush.utils.solr_indexer import push_to_solr
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
default_profile = "profile-rer.solrpush:default"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def update_profile(context, name):
|
|
15
|
+
setup_tool = api.portal.get_tool(name="portal_setup")
|
|
16
|
+
setup_tool.runImportStepFromProfile(default_profile, name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def update_registry(context):
|
|
20
|
+
update_profile(context=context, name="plone.app.registry")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def update_controlpanel(context):
|
|
24
|
+
update_profile(context=context, name="controlpanel")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def update_actions(context):
|
|
28
|
+
update_profile(context=context, name="actions")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def to_1100(context):
|
|
32
|
+
setup_tool = api.portal.get_tool(name="portal_setup")
|
|
33
|
+
setup_tool.runAllImportStepsFromProfile("profile-rer.solrpush:to_1100")
|
|
34
|
+
setup_tool.runImportStepFromProfile(default_profile, "plone.app.registry")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def to_1200(context):
|
|
38
|
+
setup_tool = api.portal.get_tool(name="portal_setup")
|
|
39
|
+
setup_tool.runImportStepFromProfile(default_profile, "plone.app.registry")
|
|
40
|
+
setup_tool.runImportStepFromProfile(default_profile, "rolemap")
|
|
41
|
+
setup_tool.runImportStepFromProfile(default_profile, "controlpanel")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def to_1300(context):
|
|
45
|
+
setup_tool = api.portal.get_tool(name="portal_setup")
|
|
46
|
+
setup_tool.runAllImportStepsFromProfile("profile-rer.solrpush:to_1300")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def to_1400(context):
|
|
50
|
+
installOrReinstallProduct(
|
|
51
|
+
api.portal.get(), "collective.z3cform.jsonwidget"
|
|
52
|
+
)
|
|
53
|
+
update_registry(context)
|
|
54
|
+
update_controlpanel(context)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def to_1600(context):
|
|
58
|
+
# add new criteria
|
|
59
|
+
update_registry(context)
|
|
60
|
+
# reload schema
|
|
61
|
+
init_solr_push()
|
|
62
|
+
brains = api.content.find(portal_type=["File", "Image"])
|
|
63
|
+
tot = len(brains)
|
|
64
|
+
logger.info("Reindexing {} contents for updated mime_types.".format(tot))
|
|
65
|
+
i = 0
|
|
66
|
+
|
|
67
|
+
for brain in brains:
|
|
68
|
+
i += 1
|
|
69
|
+
if i % 500 == 0:
|
|
70
|
+
logger.info("[PROGRESS] - {}/{}".format(i, tot))
|
|
71
|
+
item = brain.getObject()
|
|
72
|
+
try:
|
|
73
|
+
push_to_solr(item)
|
|
74
|
+
except Exception:
|
|
75
|
+
# solr can't index it, pass
|
|
76
|
+
continue
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:genericsetup="http://namespaces.zope.org/genericsetup">
|
|
4
|
+
|
|
5
|
+
<genericsetup:upgradeStep
|
|
6
|
+
source="1000"
|
|
7
|
+
destination="1100"
|
|
8
|
+
title="Remove persistent utility"
|
|
9
|
+
description=""
|
|
10
|
+
profile="rer.solrpush:default"
|
|
11
|
+
handler=".upgrades.to_1100"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<genericsetup:upgradeStep
|
|
15
|
+
source="1100"
|
|
16
|
+
destination="1200"
|
|
17
|
+
title="Added css resource and bundle"
|
|
18
|
+
description="Styles for controlpanel"
|
|
19
|
+
profile="rer.solrpush:default"
|
|
20
|
+
handler=".upgrades.to_1200"
|
|
21
|
+
/>
|
|
22
|
+
<genericsetup:upgradeStep
|
|
23
|
+
source="1200"
|
|
24
|
+
destination="1300"
|
|
25
|
+
title="Remove unused control panel"
|
|
26
|
+
description=""
|
|
27
|
+
profile="rer.solrpush:default"
|
|
28
|
+
handler=".upgrades.to_1300"
|
|
29
|
+
/>
|
|
30
|
+
<genericsetup:upgradeStep
|
|
31
|
+
source="1300"
|
|
32
|
+
destination="1400"
|
|
33
|
+
title="Remove unused control panel"
|
|
34
|
+
description=""
|
|
35
|
+
profile="rer.solrpush:default"
|
|
36
|
+
handler=".upgrades.to_1400"
|
|
37
|
+
/>
|
|
38
|
+
<genericsetup:upgradeStep
|
|
39
|
+
source="1400"
|
|
40
|
+
destination="1500"
|
|
41
|
+
title="Add new field in controlpanel: force_commit"
|
|
42
|
+
description=""
|
|
43
|
+
profile="rer.solrpush:default"
|
|
44
|
+
handler=".upgrades.update_registry"
|
|
45
|
+
/>
|
|
46
|
+
<genericsetup:upgradeStep
|
|
47
|
+
source="1500"
|
|
48
|
+
destination="1600"
|
|
49
|
+
title="Add new criteria and reindex files"
|
|
50
|
+
description=""
|
|
51
|
+
profile="rer.solrpush:default"
|
|
52
|
+
handler=".upgrades.to_1600"
|
|
53
|
+
/>
|
|
54
|
+
<genericsetup:upgradeStep
|
|
55
|
+
source="1600"
|
|
56
|
+
destination="1700"
|
|
57
|
+
title="Add new field in controlpanel: query_debug"
|
|
58
|
+
description=""
|
|
59
|
+
profile="rer.solrpush:default"
|
|
60
|
+
handler=".upgrades.update_registry"
|
|
61
|
+
/>
|
|
62
|
+
<genericsetup:upgradeStep
|
|
63
|
+
source="1700"
|
|
64
|
+
destination="1800"
|
|
65
|
+
title="Add new criteria: solr_portal_types"
|
|
66
|
+
description=""
|
|
67
|
+
profile="rer.solrpush:default"
|
|
68
|
+
handler=".upgrades.update_registry"
|
|
69
|
+
/>
|
|
70
|
+
<genericsetup:upgradeStep
|
|
71
|
+
source="1800"
|
|
72
|
+
destination="1900"
|
|
73
|
+
title="Add elevate control panel link in user actions"
|
|
74
|
+
description=""
|
|
75
|
+
profile="rer.solrpush:default"
|
|
76
|
+
handler=".upgrades.update_actions"
|
|
77
|
+
/>
|
|
78
|
+
<genericsetup:upgradeStep
|
|
79
|
+
source="1900"
|
|
80
|
+
destination="2000"
|
|
81
|
+
title="Add search_enabled setting field"
|
|
82
|
+
description=""
|
|
83
|
+
profile="rer.solrpush:default"
|
|
84
|
+
handler=".upgrades.update_registry"
|
|
85
|
+
/>
|
|
86
|
+
</configure>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from rer.solrpush.utils.solr_common import init_solr_push # noqa
|
|
3
|
+
from rer.solrpush.utils.solr_indexer import push_to_solr # noqa
|
|
4
|
+
from rer.solrpush.utils.solr_indexer import remove_from_solr # noqa
|
|
5
|
+
from rer.solrpush.utils.solr_indexer import reset_solr # noqa
|
|
6
|
+
from rer.solrpush.utils.solr_search import search # noqa
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from lxml import etree
|
|
3
|
+
from plone import api
|
|
4
|
+
from rer.solrpush import _
|
|
5
|
+
from rer.solrpush.interfaces.settings import IRerSolrpushSettings
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import pysolr
|
|
9
|
+
import requests
|
|
10
|
+
import six
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_setting(field, interface=IRerSolrpushSettings):
|
|
14
|
+
return api.portal.get_registry_record(
|
|
15
|
+
field, interface=interface, default=False
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def set_setting(field, value, interface=IRerSolrpushSettings):
|
|
20
|
+
return api.portal.set_registry_record(
|
|
21
|
+
field, interface=interface, value=value
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_solr_active():
|
|
26
|
+
"""Just checking if solr indexing is set to active in control panel"""
|
|
27
|
+
return get_setting(field="active")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def should_force_commit():
|
|
31
|
+
return get_setting(field="force_commit") or False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_index_fields():
|
|
35
|
+
json_str = get_setting(field="index_fields")
|
|
36
|
+
return json.loads(json_str)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def extract_fields(nodes):
|
|
40
|
+
fields = {}
|
|
41
|
+
for node in nodes:
|
|
42
|
+
field_name = node.get("name")
|
|
43
|
+
field_type = node.get("type")
|
|
44
|
+
if six.PY2:
|
|
45
|
+
field_name = six.text_type(field_name)
|
|
46
|
+
field_type = six.text_type(field_type)
|
|
47
|
+
fields[field_name] = {"type": field_type}
|
|
48
|
+
return fields
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def init_solr_push():
|
|
52
|
+
"""Inizializza la voce di registro 'index_fields'
|
|
53
|
+
|
|
54
|
+
Lo fa leggendo il file xml di SOLR.
|
|
55
|
+
|
|
56
|
+
:param solr_url: [required] L'url a cui richiedere il file xml
|
|
57
|
+
:type solr_url: string
|
|
58
|
+
:returns: Empty String if everything's good
|
|
59
|
+
:rtype: String
|
|
60
|
+
"""
|
|
61
|
+
solr_url = get_setting(field="solr_url")
|
|
62
|
+
|
|
63
|
+
if solr_url:
|
|
64
|
+
if not solr_url.endswith("/"):
|
|
65
|
+
solr_url = solr_url + "/"
|
|
66
|
+
try:
|
|
67
|
+
respo = requests.get(solr_url + "admin/file?file=schema.xml")
|
|
68
|
+
except requests.exceptions.RequestException as err:
|
|
69
|
+
ErrorMessage = "Connection problem:\n{0}".format(err)
|
|
70
|
+
return ErrorMessage
|
|
71
|
+
if respo.status_code != 200:
|
|
72
|
+
ErrorMessage = "Problems fetching schema:\n{0}\n{1}".format(
|
|
73
|
+
respo.status_code, respo.reason
|
|
74
|
+
)
|
|
75
|
+
return ErrorMessage
|
|
76
|
+
|
|
77
|
+
root = etree.fromstring(respo.content)
|
|
78
|
+
chosen_fields = json.dumps(
|
|
79
|
+
extract_fields(nodes=root.findall(".//field"))
|
|
80
|
+
)
|
|
81
|
+
if six.PY2:
|
|
82
|
+
chosen_fields = chosen_fields.decode("utf-8")
|
|
83
|
+
set_setting(field="index_fields", value=chosen_fields)
|
|
84
|
+
set_setting(field="ready", value=True)
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
return _("No SOLR url provided")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_solr_connection(context=None, **kwargs):
|
|
91
|
+
# TODO: fix sporadic
|
|
92
|
+
# ResourceWarning: Enable tracemalloc to get the object allocation traceback
|
|
93
|
+
# ResourceWarning: unclosed <socket.socket ... raddr=('127.0.0.1', 8983)>
|
|
94
|
+
# -> avoid explicit close connection
|
|
95
|
+
|
|
96
|
+
# XXX: rivalutare il default True per always_commit !
|
|
97
|
+
if "always_commit" not in kwargs:
|
|
98
|
+
kwargs["always_commit"] = True
|
|
99
|
+
is_ready = get_setting(field="ready")
|
|
100
|
+
solr_url = get_setting(field="solr_url")
|
|
101
|
+
if not is_ready or not solr_url:
|
|
102
|
+
return
|
|
103
|
+
if context is None:
|
|
104
|
+
context = api.portal.get()
|
|
105
|
+
if context is None:
|
|
106
|
+
client = pysolr.Solr(solr_url, **kwargs)
|
|
107
|
+
else:
|
|
108
|
+
jar = getattr(context, "_p_jar", None)
|
|
109
|
+
oid = getattr(context, "_p_oid", None)
|
|
110
|
+
if jar is None or oid is None:
|
|
111
|
+
# object is not persistent or is not yet associated with a
|
|
112
|
+
# connection
|
|
113
|
+
cache = context._v_solr_client_cache = {}
|
|
114
|
+
else:
|
|
115
|
+
cache = getattr(jar, "foreign_connections", None)
|
|
116
|
+
if cache is None:
|
|
117
|
+
cache = jar.foreign_connections = {}
|
|
118
|
+
cache_key = "solr_%s_%s" % (solr_url, kwargs)
|
|
119
|
+
client = cache.get(cache_key)
|
|
120
|
+
if client is None:
|
|
121
|
+
client = cache[cache_key] = pysolr.Solr(solr_url, **kwargs)
|
|
122
|
+
return client
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from DateTime import DateTime
|
|
3
|
+
from plone import api
|
|
4
|
+
from plone.indexer.interfaces import IIndexableObject
|
|
5
|
+
from plone.registry.interfaces import IRegistry
|
|
6
|
+
from rer.solrpush import _
|
|
7
|
+
from rer.solrpush.interfaces.adapter import IExtractFileFromTika
|
|
8
|
+
from six.moves import map
|
|
9
|
+
from zope.component import getUtility
|
|
10
|
+
from zope.component import queryMultiAdapter
|
|
11
|
+
from rer.solrpush.utils.solr_common import get_solr_connection
|
|
12
|
+
from rer.solrpush.utils.solr_common import get_setting
|
|
13
|
+
from rer.solrpush.utils.solr_common import get_index_fields
|
|
14
|
+
from rer.solrpush.utils.solr_common import is_solr_active
|
|
15
|
+
from rer.solrpush.utils.solr_common import should_force_commit
|
|
16
|
+
|
|
17
|
+
import datetime
|
|
18
|
+
import logging
|
|
19
|
+
import pysolr
|
|
20
|
+
import six
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if six.PY2:
|
|
24
|
+
from ftfy import fix_text
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# rer.agidtheme overrides site tile field
|
|
28
|
+
from rer.agidtheme.base.interfaces import IRERSiteSchema as ISiteSchema
|
|
29
|
+
from rer.agidtheme.base.utility.interfaces import ICustomFields
|
|
30
|
+
|
|
31
|
+
RER_THEME = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
from Products.CMFPlone.interfaces.controlpanel import ISiteSchema
|
|
34
|
+
|
|
35
|
+
RER_THEME = False
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
ADDITIONAL_FIELDS = ["searchwords"]
|
|
40
|
+
|
|
41
|
+
RESTAPI_METADATA_FIELDS = ["@id", "@type", "description", "title"]
|
|
42
|
+
|
|
43
|
+
# HELPER METHODS
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_site_title():
|
|
47
|
+
registry = getUtility(IRegistry)
|
|
48
|
+
site_settings = registry.forInterface(
|
|
49
|
+
ISiteSchema, prefix="plone", check=False
|
|
50
|
+
)
|
|
51
|
+
site_title = getattr(site_settings, "site_title") or ""
|
|
52
|
+
if RER_THEME:
|
|
53
|
+
site_subtitle_style = (
|
|
54
|
+
getattr(site_settings, "site_subtitle_style") or ""
|
|
55
|
+
)
|
|
56
|
+
fields_value = getUtility(ICustomFields)
|
|
57
|
+
site_title = fields_value.titleLang(site_title)
|
|
58
|
+
site_subtitle = fields_value.subtitleLang(
|
|
59
|
+
getattr(site_settings, "site_subtitle") or "{}"
|
|
60
|
+
)
|
|
61
|
+
if site_subtitle and site_subtitle_style == "subtitle-normal":
|
|
62
|
+
site_title += " {}".format(site_subtitle)
|
|
63
|
+
|
|
64
|
+
if six.PY2:
|
|
65
|
+
site_title = site_title.decode("utf-8")
|
|
66
|
+
return site_title
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_date_as_datetime(value):
|
|
70
|
+
"""Sistemiamo le date"""
|
|
71
|
+
if value:
|
|
72
|
+
format = "%Y-%m-%dT%H:%M:%S"
|
|
73
|
+
return value.utcdatetime().strftime(format) + "Z"
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_date_str(value):
|
|
78
|
+
if isinstance(value, datetime.date):
|
|
79
|
+
value = datetime.datetime.combine(value, datetime.time.min)
|
|
80
|
+
return parse_date_as_datetime(DateTime(value))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def attachment_to_index(item):
|
|
84
|
+
"""
|
|
85
|
+
If item has a provider to extract text, return the file to be indexed
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
provider = IExtractFileFromTika(item)
|
|
89
|
+
return provider.get_file_to_index()
|
|
90
|
+
except TypeError:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def is_right_portal_type(item):
|
|
95
|
+
enabled_types = get_setting(field="enabled_types")
|
|
96
|
+
if not enabled_types:
|
|
97
|
+
return True
|
|
98
|
+
return item.portal_type in enabled_types
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def can_index(item):
|
|
102
|
+
"""Check if the item passed as argument can and has to be indexed"""
|
|
103
|
+
with api.env.adopt_roles(["Anonymous"]):
|
|
104
|
+
if not api.user.has_permission("View", obj=item):
|
|
105
|
+
return False
|
|
106
|
+
if not is_solr_active():
|
|
107
|
+
return False
|
|
108
|
+
return is_right_portal_type(item)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def create_index_dict(item):
|
|
112
|
+
"""Restituisce un dizionario pronto per essere 'mandato' a SOLR per
|
|
113
|
+
l'indicizzazione.
|
|
114
|
+
"""
|
|
115
|
+
index_fields = get_index_fields()
|
|
116
|
+
frontend_url = get_setting(field="frontend_url")
|
|
117
|
+
if frontend_url:
|
|
118
|
+
frontend_url = frontend_url.rstrip("/")
|
|
119
|
+
|
|
120
|
+
catalog = api.portal.get_tool(name="portal_catalog")
|
|
121
|
+
adapter = queryMultiAdapter((item, catalog), IIndexableObject)
|
|
122
|
+
index_me = {}
|
|
123
|
+
for field, field_infos in index_fields.items():
|
|
124
|
+
if field in RESTAPI_METADATA_FIELDS:
|
|
125
|
+
# skip. These fields are only metadata fields needed in restap-like
|
|
126
|
+
# repsonses and can be copied in solr configuration.
|
|
127
|
+
continue
|
|
128
|
+
field_type = field_infos.get("type")
|
|
129
|
+
if six.PY2:
|
|
130
|
+
field = field.encode("ascii")
|
|
131
|
+
value = getattr(adapter, field, None)
|
|
132
|
+
if not value and value is not False:
|
|
133
|
+
continue
|
|
134
|
+
if callable(value):
|
|
135
|
+
value = value()
|
|
136
|
+
if six.PY2:
|
|
137
|
+
value = fix_py2_strings(value)
|
|
138
|
+
if isinstance(value, DateTime):
|
|
139
|
+
value = parse_date_as_datetime(value)
|
|
140
|
+
else:
|
|
141
|
+
if field_type == "date":
|
|
142
|
+
value = parse_date_str(value)
|
|
143
|
+
index_me[field] = value
|
|
144
|
+
|
|
145
|
+
for field in ADDITIONAL_FIELDS:
|
|
146
|
+
value = getattr(item, field, None)
|
|
147
|
+
if value is not None:
|
|
148
|
+
index_me[field] = value
|
|
149
|
+
portal = api.portal.get()
|
|
150
|
+
|
|
151
|
+
# extra-catalog schema
|
|
152
|
+
index_me["site_name"] = get_site_title()
|
|
153
|
+
index_me["path"] = "/".join(item.getPhysicalPath())
|
|
154
|
+
index_me["path_depth"] = len(item.getPhysicalPath()) - 2
|
|
155
|
+
index_me["attachment"] = attachment_to_index(item)
|
|
156
|
+
index_me["getIcon"] = False
|
|
157
|
+
if "view_name" in index_fields.keys():
|
|
158
|
+
index_me["view_name"] = item.getLayout()
|
|
159
|
+
|
|
160
|
+
# convert url to frontend one
|
|
161
|
+
if frontend_url:
|
|
162
|
+
index_me["url"] = item.absolute_url().replace(
|
|
163
|
+
portal.portal_url(), frontend_url
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
index_me["url"] = item.absolute_url()
|
|
167
|
+
|
|
168
|
+
has_image = getattr(item.aq_base, "image", None)
|
|
169
|
+
if has_image:
|
|
170
|
+
index_me["getIcon"] = True
|
|
171
|
+
|
|
172
|
+
return index_me
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def fix_py2_strings(value):
|
|
176
|
+
"""REMOVE ON PYTHON 3"""
|
|
177
|
+
if isinstance(value, six.string_types):
|
|
178
|
+
if not isinstance(value, six.text_type):
|
|
179
|
+
value = value.replace("\xc0?", "").decode("utf-8", "ignore")
|
|
180
|
+
return fix_text(value)
|
|
181
|
+
if isinstance(value, list):
|
|
182
|
+
return list(map(fix_py2_strings, value))
|
|
183
|
+
if isinstance(value, tuple):
|
|
184
|
+
return tuple(map(fix_py2_strings, value))
|
|
185
|
+
return value
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def encode_strings_for_attachments(value):
|
|
189
|
+
"""
|
|
190
|
+
Needed because attachments POST use standard requests encoding
|
|
191
|
+
and not utf-8
|
|
192
|
+
"""
|
|
193
|
+
if isinstance(value, six.string_types):
|
|
194
|
+
return value.encode("ISO-8859-1", "ignore")
|
|
195
|
+
if isinstance(value, list):
|
|
196
|
+
return list(map(encode_strings_for_attachments, value))
|
|
197
|
+
if isinstance(value, tuple):
|
|
198
|
+
return tuple(map(encode_strings_for_attachments, value))
|
|
199
|
+
return value
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def add_with_attachment(solr, attachment, fields):
|
|
203
|
+
params = {
|
|
204
|
+
"extractOnly": "false",
|
|
205
|
+
"lowernames": "false",
|
|
206
|
+
"fmap.content": "SearchableText",
|
|
207
|
+
"fmap.title": "SearchableText",
|
|
208
|
+
"literalsOverride": "true",
|
|
209
|
+
"commit": should_force_commit() and "true" or "false",
|
|
210
|
+
}
|
|
211
|
+
params.update(
|
|
212
|
+
{
|
|
213
|
+
"literal.{key}".format(key=key): encode_strings_for_attachments(
|
|
214
|
+
value
|
|
215
|
+
)
|
|
216
|
+
for (key, value) in fields.items()
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
path = "update/extract"
|
|
220
|
+
return solr._send_request(
|
|
221
|
+
method="post",
|
|
222
|
+
path=path,
|
|
223
|
+
body=params,
|
|
224
|
+
files={"file": ("extracted", attachment)},
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# END HELPER METHODS
|
|
229
|
+
|
|
230
|
+
# LIBRARY METHODS
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def push_to_solr(item_or_obj):
|
|
234
|
+
"""
|
|
235
|
+
Perform push to solr
|
|
236
|
+
"""
|
|
237
|
+
solr = get_solr_connection()
|
|
238
|
+
if not solr:
|
|
239
|
+
logger.error("Unable to push to solr. Configuration is incomplete.")
|
|
240
|
+
return
|
|
241
|
+
context = item_or_obj
|
|
242
|
+
if isinstance(item_or_obj, dict):
|
|
243
|
+
obj = api.content.get(UID=item_or_obj.get("UID", ""))
|
|
244
|
+
if obj:
|
|
245
|
+
context = obj
|
|
246
|
+
if not can_index(context):
|
|
247
|
+
return False
|
|
248
|
+
index_dict = create_index_dict(context)
|
|
249
|
+
attachment = index_dict.pop("attachment", None)
|
|
250
|
+
if attachment:
|
|
251
|
+
add_with_attachment(
|
|
252
|
+
solr=solr, attachment=attachment, fields=index_dict
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
solr.add(docs=[index_dict], commit=should_force_commit())
|
|
256
|
+
return True
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def remove_from_solr(uid):
|
|
260
|
+
"""
|
|
261
|
+
Perform remove item from solr
|
|
262
|
+
"""
|
|
263
|
+
if not is_solr_active():
|
|
264
|
+
return
|
|
265
|
+
solr = get_solr_connection()
|
|
266
|
+
portal = api.portal.get()
|
|
267
|
+
if not solr:
|
|
268
|
+
logger.error("Unable to push to solr. Configuration is incomplete.")
|
|
269
|
+
return
|
|
270
|
+
try:
|
|
271
|
+
solr.delete(
|
|
272
|
+
q="UID:{}".format(uid),
|
|
273
|
+
commit=should_force_commit(),
|
|
274
|
+
)
|
|
275
|
+
except (pysolr.SolrError, TypeError) as err:
|
|
276
|
+
logger.error(err)
|
|
277
|
+
message = _(
|
|
278
|
+
"content_remove_error",
|
|
279
|
+
default=u"There was a problem removing this content from SOLR. "
|
|
280
|
+
" Please contact site administrator.",
|
|
281
|
+
)
|
|
282
|
+
api.portal.show_message(
|
|
283
|
+
message=message, request=portal.REQUEST, type="warning"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def reset_solr():
|
|
288
|
+
solr = get_solr_connection()
|
|
289
|
+
if not solr:
|
|
290
|
+
logger.error("Unable to push to solr. Configuration is incomplete.")
|
|
291
|
+
return
|
|
292
|
+
solr.delete(
|
|
293
|
+
q='site_name:"{}"'.format(get_site_title()),
|
|
294
|
+
commit=should_force_commit(),
|
|
295
|
+
)
|