eea.plotly 1.0__tar.gz → 1.2__tar.gz
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.
- {eea_plotly-1.0/eea.plotly.egg-info → eea_plotly-1.2}/PKG-INFO +14 -1
- eea_plotly-1.2/docs/HISTORY.txt +22 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/__init__.py +0 -1
- eea_plotly-1.2/eea/plotly/behaviors.py +35 -0
- eea_plotly-1.2/eea/plotly/browser/configure.zcml +26 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/browser/controlpanel.py +1 -1
- eea_plotly-1.2/eea/plotly/browser/preview.py +93 -0
- eea_plotly-1.2/eea/plotly/browser/pt/visualization_view.pt +17 -0
- eea_plotly-1.2/eea/plotly/configure.zcml +30 -0
- eea_plotly-1.2/eea/plotly/controlpanel.py +64 -0
- eea_plotly-1.2/eea/plotly/deserializer/blocks.py +32 -0
- eea_plotly-1.2/eea/plotly/deserializer/configure.zcml +11 -0
- eea_plotly-1.2/eea/plotly/deserializer/dxcontent.py +113 -0
- eea_plotly-1.2/eea/plotly/deserializer/dxfields.py +28 -0
- eea_plotly-1.2/eea/plotly/interfaces.py +19 -0
- eea_plotly-1.2/eea/plotly/io_csv.py +49 -0
- eea_plotly-1.2/eea/plotly/permissions.zcml +6 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/profiles/default/controlpanel.xml +1 -1
- eea_plotly-1.2/eea/plotly/profiles/default/rolemap.xml +14 -0
- eea_plotly-1.2/eea/plotly/profiles/default/types/visualization.xml +55 -0
- eea_plotly-1.2/eea/plotly/profiles/default/types.xml +4 -0
- eea_plotly-1.2/eea/plotly/profiles/uninstall/registry.xml +4 -0
- eea_plotly-1.2/eea/plotly/profiles.zcml +31 -0
- eea_plotly-1.2/eea/plotly/restapi/configure.zcml +7 -0
- eea_plotly-1.2/eea/plotly/restapi/controlpanel/__init__.py +0 -0
- eea_plotly-1.2/eea/plotly/restapi/controlpanel/configure.zcml +11 -0
- eea_plotly-1.2/eea/plotly/restapi/controlpanel/get.py +51 -0
- eea_plotly-1.2/eea/plotly/restapi/visualization/configure.zcml +19 -0
- eea_plotly-1.2/eea/plotly/restapi/visualization/get.py +49 -0
- eea_plotly-1.2/eea/plotly/serializer/blocks.py +131 -0
- eea_plotly-1.2/eea/plotly/serializer/configure.zcml +13 -0
- eea_plotly-1.2/eea/plotly/serializer/dxfields.py +62 -0
- eea_plotly-1.2/eea/plotly/serializer/utils.py +76 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/setuphandlers.py +1 -1
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/tests/base.py +5 -1
- eea_plotly-1.2/eea/plotly/upgrades/configure.zcml +16 -0
- eea_plotly-1.2/eea/plotly/utils.py +73 -0
- eea_plotly-1.2/eea/plotly/version.txt +1 -0
- {eea_plotly-1.0 → eea_plotly-1.2/eea.plotly.egg-info}/PKG-INFO +14 -1
- {eea_plotly-1.0 → eea_plotly-1.2}/eea.plotly.egg-info/SOURCES.txt +21 -2
- {eea_plotly-1.0 → eea_plotly-1.2}/eea.plotly.egg-info/requires.txt +3 -0
- eea_plotly-1.2/eea.plotly.egg-info/top_level.txt +1 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/setup.py +5 -1
- eea_plotly-1.0/docs/HISTORY.txt +0 -12
- eea_plotly-1.0/eea/plotly/browser/configure.zcml +0 -14
- eea_plotly-1.0/eea/plotly/configure.zcml +0 -15
- eea_plotly-1.0/eea/plotly/interfaces.py +0 -96
- eea_plotly-1.0/eea/plotly/permissions.zcml +0 -7
- eea_plotly-1.0/eea/plotly/profiles/default/rolemap.xml +0 -11
- eea_plotly-1.0/eea/plotly/profiles/uninstall/registry.xml +0 -4
- eea_plotly-1.0/eea/plotly/profiles.zcml +0 -33
- eea_plotly-1.0/eea/plotly/restapi/configure.zcml +0 -20
- eea_plotly-1.0/eea/plotly/restapi/controlpanel.py +0 -22
- eea_plotly-1.0/eea/plotly/restapi/get.py +0 -30
- eea_plotly-1.0/eea/plotly/upgrades/configure.zcml +0 -18
- eea_plotly-1.0/eea/plotly/version.txt +0 -1
- eea_plotly-1.0/eea.plotly.egg-info/namespace_packages.txt +0 -2
- {eea_plotly-1.0 → eea_plotly-1.2}/CONTRIBUTING.md +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/MANIFEST.in +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/README.rst +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/docs/LICENSE.GPL +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/docs/LICENSE.txt +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/__init__.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/README.txt +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/browser/__init__.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/__init__.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/bg/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/bg/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/cs/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/cs/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/da/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/da/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/de/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/de/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/eea.pot +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/el/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/el/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/en/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/en/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/es/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/es/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/et/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/et/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/eu/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/eu/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/fi/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/fi/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/fr/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/fr/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/hr/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/hr/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/hu/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/hu/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/is/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/is/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/it/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/it/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/kl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/kl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/lt/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/lt/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/lv/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/lv/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/mt/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/mt/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/nl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/nl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/no/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/no/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/pl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/pl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/plone-manual.pot +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/pt/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/pt/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/pt_BR/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/pt_BR/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/ro/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/ro/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/ru/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/ru/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/sk/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/sk/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/sl/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/sl/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/sv/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/sv/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/tr/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/tr/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/update.sh +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/zh_TW/LC_MESSAGES/eea.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/locales/zh_TW/LC_MESSAGES/plone.po +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/profiles/default/browserlayer.xml +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/profiles/default/metadata.xml +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/profiles/default/registry.xml +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/profiles/uninstall/browserlayer.xml +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/profiles/uninstall/controlpanel.xml +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/restapi/__init__.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/tests/__init__.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/tests/test_doctests.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea/plotly/upgrades/__init__.py +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea.plotly.egg-info/dependency_links.txt +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea.plotly.egg-info/entry_points.txt +0 -0
- /eea_plotly-1.0/eea.plotly.egg-info/top_level.txt → /eea_plotly-1.2/eea.plotly.egg-info/namespace_packages.txt +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/eea.plotly.egg-info/not-zip-safe +0 -0
- {eea_plotly-1.0 → eea_plotly-1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eea.plotly
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
4
4
|
Summary: volto-plotlycharts backend support
|
|
5
5
|
Home-page: https://github.com/eea/eea.plotly
|
|
6
6
|
Author: European Environment Agency: IDM2 A-Team
|
|
@@ -21,6 +21,9 @@ Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
|
|
|
21
21
|
Description-Content-Type: text/x-rst
|
|
22
22
|
Requires-Dist: setuptools
|
|
23
23
|
Requires-Dist: plone.volto
|
|
24
|
+
Requires-Dist: collective.folderishtypes
|
|
25
|
+
Requires-Dist: plotly==5.24.1
|
|
26
|
+
Requires-Dist: kaleido
|
|
24
27
|
Provides-Extra: test
|
|
25
28
|
Requires-Dist: plone.app.testing; extra == "test"
|
|
26
29
|
Dynamic: author
|
|
@@ -142,6 +145,16 @@ EEA_ - European Environment Agency (EU)
|
|
|
142
145
|
Changelog
|
|
143
146
|
=========
|
|
144
147
|
|
|
148
|
+
1.2 - (2025-03-13)
|
|
149
|
+
---------------------------
|
|
150
|
+
* Change: Add theme property
|
|
151
|
+
[razvanMiu]
|
|
152
|
+
|
|
153
|
+
1.1 - (2025-02-07)
|
|
154
|
+
---------------------------
|
|
155
|
+
* Change: permission typo
|
|
156
|
+
[razvanMiu]
|
|
157
|
+
|
|
145
158
|
1.0 - (2025-02-06)
|
|
146
159
|
---------------------------
|
|
147
160
|
* Change: Release [razvanMiu]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
1.2 - (2025-03-13)
|
|
5
|
+
---------------------------
|
|
6
|
+
* Change: Add theme property
|
|
7
|
+
[razvanMiu]
|
|
8
|
+
|
|
9
|
+
1.1 - (2025-02-07)
|
|
10
|
+
---------------------------
|
|
11
|
+
* Change: permission typo
|
|
12
|
+
[razvanMiu]
|
|
13
|
+
|
|
14
|
+
1.0 - (2025-02-06)
|
|
15
|
+
---------------------------
|
|
16
|
+
* Change: Release [razvanMiu]
|
|
17
|
+
|
|
18
|
+
1.0 - (2025-02-06)
|
|
19
|
+
-----------------------
|
|
20
|
+
|
|
21
|
+
* Initial release.
|
|
22
|
+
[eea]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
""" behavior module """
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from plone.autoform.interfaces import IFormFieldProvider
|
|
6
|
+
from plone.supermodel import model
|
|
7
|
+
from plone.schema import JSONField
|
|
8
|
+
from plone.namedfile.field import NamedBlobFile
|
|
9
|
+
from zope.interface import directlyProvides, provider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from eea.plotly.interfaces import IPlotlyVisualizationField, IPlotlyFileField
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
VIZ_SCHEMA = json.dumps({"type": "object", "properties": {}})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@provider(IFormFieldProvider)
|
|
19
|
+
class IPlotlyVisualization(model.Schema):
|
|
20
|
+
""" A plotly visualization behavior provider """
|
|
21
|
+
|
|
22
|
+
visualization = JSONField(
|
|
23
|
+
title="Visualization", required=False, default={}, schema=VIZ_SCHEMA
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
directlyProvides(visualization, IPlotlyVisualizationField)
|
|
27
|
+
|
|
28
|
+
file = NamedBlobFile(
|
|
29
|
+
title="File",
|
|
30
|
+
required=False,
|
|
31
|
+
description="Data sources file",
|
|
32
|
+
readonly=True,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
directlyProvides(file, IPlotlyFileField)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<configure xmlns="http://namespaces.zope.org/zope"
|
|
2
|
+
xmlns:browser="http://namespaces.zope.org/browser" i18n_domain="eea">
|
|
3
|
+
|
|
4
|
+
<browser:page
|
|
5
|
+
name="plotly-controlpanel"
|
|
6
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
7
|
+
class=".controlpanel.PlotlyControlPanelFormWrapper"
|
|
8
|
+
layer="eea.plotly.interfaces.IPlotlyLayer"
|
|
9
|
+
permission="cmf.ManagePortal"
|
|
10
|
+
/>
|
|
11
|
+
|
|
12
|
+
<browser:page
|
|
13
|
+
name="view"
|
|
14
|
+
for="eea.plotly.behaviors.IPlotlyVisualization"
|
|
15
|
+
template="pt/visualization_view.pt"
|
|
16
|
+
permission="zope2.View"
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<browser:view
|
|
20
|
+
name="plotly_preview.svg"
|
|
21
|
+
for="eea.plotly.behaviors.IPlotlyVisualization"
|
|
22
|
+
class=".preview.PlotlyPreview"
|
|
23
|
+
permission="zope2.View"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
</configure>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper
|
|
3
3
|
from plone.app.registry.browser.controlpanel import RegistryEditForm
|
|
4
4
|
|
|
5
|
-
from eea.plotly.
|
|
5
|
+
from eea.plotly.controlpanel import IPlotlySettings
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class PlotlyRegistryEditForm(RegistryEditForm):
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""RestAPI enpoint @plotly GET"""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import plotly.io as pio
|
|
6
|
+
from plone import api
|
|
7
|
+
from zope.interface import implementer
|
|
8
|
+
from zope.publisher.interfaces import IPublishTraverse
|
|
9
|
+
from Products.Five.browser import BrowserView
|
|
10
|
+
from eea.plotly.controlpanel import IPlotlySettings
|
|
11
|
+
from eea.plotly.utils import sanitizeVisualization
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def deepUpdate(original, update):
|
|
15
|
+
""" Recursively update a dictionary with another dictionary."""
|
|
16
|
+
for key, value in update.items():
|
|
17
|
+
if isinstance(
|
|
18
|
+
value, dict) and key in original and isinstance(
|
|
19
|
+
original[key],
|
|
20
|
+
dict):
|
|
21
|
+
deepUpdate(original[key], value)
|
|
22
|
+
elif key in original:
|
|
23
|
+
original[key] = value
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@implementer(IPublishTraverse)
|
|
27
|
+
class PlotlyPreview(BrowserView):
|
|
28
|
+
"""Plotly Preview"""
|
|
29
|
+
|
|
30
|
+
name = None
|
|
31
|
+
|
|
32
|
+
def render(self):
|
|
33
|
+
"""Render"""
|
|
34
|
+
|
|
35
|
+
visualization = copy.deepcopy(
|
|
36
|
+
sanitizeVisualization(self.context.visualization)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if not visualization:
|
|
40
|
+
self.request.response.setStatus(404)
|
|
41
|
+
return {
|
|
42
|
+
"message": "Visualization is not defined",
|
|
43
|
+
"type": "NotFound"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if self.name:
|
|
47
|
+
theme = None
|
|
48
|
+
themes = api.portal.get_registry_record(
|
|
49
|
+
"themes",
|
|
50
|
+
interface=IPlotlySettings,
|
|
51
|
+
default=[]
|
|
52
|
+
)
|
|
53
|
+
for t in themes:
|
|
54
|
+
if t.get("id") == self.name:
|
|
55
|
+
theme = copy.deepcopy(t)
|
|
56
|
+
break
|
|
57
|
+
if theme and "layout" in visualization:
|
|
58
|
+
data = theme.get("data", {})
|
|
59
|
+
layout = theme.get("layout", {})
|
|
60
|
+
for trIndex, tr in enumerate(visualization.get("data", [])):
|
|
61
|
+
trType = tr.get("type", "")
|
|
62
|
+
if trType in data:
|
|
63
|
+
newTrIndex = min(trIndex, len(data[trType])-1)
|
|
64
|
+
newTr = data[trType][newTrIndex]
|
|
65
|
+
deepUpdate(tr, newTr)
|
|
66
|
+
deepUpdate(visualization["layout"], layout)
|
|
67
|
+
visualization["layout"]["template"] = theme
|
|
68
|
+
|
|
69
|
+
fig = pio.from_json(json.dumps(visualization), skip_invalid=True)
|
|
70
|
+
|
|
71
|
+
if "template" not in visualization["layout"]:
|
|
72
|
+
fig.update_layout(template=None)
|
|
73
|
+
|
|
74
|
+
image = fig.to_image(format="svg", width=1200, height=900)
|
|
75
|
+
|
|
76
|
+
sh = self.request.response.setHeader
|
|
77
|
+
|
|
78
|
+
sh(
|
|
79
|
+
"Content-Type",
|
|
80
|
+
"image/svg+xml"
|
|
81
|
+
)
|
|
82
|
+
sh("Content-Disposition", "inline; filename=%s.svg" % "x")
|
|
83
|
+
|
|
84
|
+
return image
|
|
85
|
+
|
|
86
|
+
def publishTraverse(self, request, name):
|
|
87
|
+
"""used for traversal via publisher, i.e. when using as a url"""
|
|
88
|
+
self.name = name
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def __call__(self):
|
|
92
|
+
"""Call"""
|
|
93
|
+
return self.render()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
|
|
2
|
+
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
|
3
|
+
xmlns:metal="http://xml.zope.org/namespaces/metal"
|
|
4
|
+
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
|
|
5
|
+
lang="en"
|
|
6
|
+
metal:use-macro="context/@@main_template/macros/master"
|
|
7
|
+
i18n:domain="plone">
|
|
8
|
+
|
|
9
|
+
<body>
|
|
10
|
+
<metal:main fill-slot="main">
|
|
11
|
+
<h1 class="documentFirstHeading" tal:content="context/Title">Title</h1>
|
|
12
|
+
|
|
13
|
+
<img src="" tal:attributes="src string:${context/absolute_url}/@@plotly_preview.svg" i18n:attributes="alt" alt="Preview" />
|
|
14
|
+
|
|
15
|
+
</metal:main>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<configure xmlns="http://namespaces.zope.org/zope" xmlns:i18n="http://namespaces.zope.org/i18n"
|
|
2
|
+
xmlns:plone="http://namespaces.plone.org/plone"
|
|
3
|
+
i18n_domain="eea">
|
|
4
|
+
|
|
5
|
+
<i18n:registerTranslations directory="locales" />
|
|
6
|
+
|
|
7
|
+
<include file="profiles.zcml" />
|
|
8
|
+
<include file="permissions.zcml" />
|
|
9
|
+
|
|
10
|
+
<include package=".browser" />
|
|
11
|
+
<include package=".restapi" />
|
|
12
|
+
<include package=".serializer" />
|
|
13
|
+
<include package=".deserializer" />
|
|
14
|
+
<include package=".upgrades" />
|
|
15
|
+
|
|
16
|
+
<interface interface="eea.plotly.interfaces.IPlotlyVisualization" />
|
|
17
|
+
|
|
18
|
+
<adapter
|
|
19
|
+
factory=".controlpanel.PlotlyControlpanel"
|
|
20
|
+
name="plotly"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<plone:behavior
|
|
24
|
+
name="eea.plotly.visualization"
|
|
25
|
+
title="EEA-Viz_Plotly"
|
|
26
|
+
description="Allows adding an interactive plotly chart."
|
|
27
|
+
provides="eea.plotly.behaviors.IPlotlyVisualization"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
</configure>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Plotly Controlpanel API"""
|
|
2
|
+
import json
|
|
3
|
+
from zope.interface import Interface
|
|
4
|
+
from zope.component import adapter
|
|
5
|
+
from plone.schema import JSONField
|
|
6
|
+
from plone.restapi.controlpanels import RegistryConfigletPanel
|
|
7
|
+
from eea.plotly.interfaces import IPlotlyLayer
|
|
8
|
+
from eea.plotly import EEAMessageFactory as _
|
|
9
|
+
|
|
10
|
+
TEMPLATES_SCHEMA = json.dumps({
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {}
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
THEMES_SCHEMA = json.dumps({
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"properties": {}
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class IPlotlySettings(Interface):
|
|
28
|
+
"""Client settings for EEA Plotly."""
|
|
29
|
+
themes = JSONField(
|
|
30
|
+
title=_(u"Themes"),
|
|
31
|
+
description=_(
|
|
32
|
+
u"The JSON representation of plotly themes."
|
|
33
|
+
),
|
|
34
|
+
schema=THEMES_SCHEMA,
|
|
35
|
+
default=[],
|
|
36
|
+
widget="plotly_themes",
|
|
37
|
+
required=False
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
templates = JSONField(
|
|
41
|
+
title=_(u"Templates"),
|
|
42
|
+
description=_(
|
|
43
|
+
u"The JSON representation of plotly templates."
|
|
44
|
+
),
|
|
45
|
+
schema=TEMPLATES_SCHEMA,
|
|
46
|
+
default=[],
|
|
47
|
+
widget="plotly_templates",
|
|
48
|
+
required=False
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@adapter(Interface, IPlotlyLayer)
|
|
53
|
+
class PlotlyControlpanel(RegistryConfigletPanel):
|
|
54
|
+
"""Plotly Control Panel"""
|
|
55
|
+
schema = IPlotlySettings
|
|
56
|
+
schema_prefix = None
|
|
57
|
+
configlet_id = "plotly"
|
|
58
|
+
configlet_category_id = "Products"
|
|
59
|
+
title = "Plotly Settings"
|
|
60
|
+
group = "Products"
|
|
61
|
+
data = {
|
|
62
|
+
"themes": [],
|
|
63
|
+
"templates": []
|
|
64
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
""" block-related utils """
|
|
2
|
+
|
|
3
|
+
from plone.restapi.behaviors import IBlocks
|
|
4
|
+
from plone.restapi.interfaces import IBlockFieldDeserializationTransformer
|
|
5
|
+
from plone.restapi.deserializer.utils import path2uid
|
|
6
|
+
from zope.component import adapter
|
|
7
|
+
from zope.interface import implementer
|
|
8
|
+
from zope.publisher.interfaces.browser import IBrowserRequest
|
|
9
|
+
from eea.plotly.utils import getLink, delProperty
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@implementer(IBlockFieldDeserializationTransformer)
|
|
13
|
+
@adapter(IBlocks, IBrowserRequest)
|
|
14
|
+
class EmbedVisualizationDeserializationTransformer:
|
|
15
|
+
"""Embed visualization deserialization"""
|
|
16
|
+
|
|
17
|
+
order = 9999
|
|
18
|
+
block_type = "embed_visualization"
|
|
19
|
+
|
|
20
|
+
def __init__(self, context, request):
|
|
21
|
+
self.context = context
|
|
22
|
+
self.request = request
|
|
23
|
+
|
|
24
|
+
def __call__(self, value):
|
|
25
|
+
delProperty(value, 'visualization')
|
|
26
|
+
delProperty(value, 'properties')
|
|
27
|
+
delProperty(value, 'image_scales')
|
|
28
|
+
if 'vis_url' in value:
|
|
29
|
+
value['vis_url'] = path2uid(
|
|
30
|
+
context=self.context, link=getLink(value['vis_url'])
|
|
31
|
+
)
|
|
32
|
+
return value
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<configure xmlns="http://namespaces.zope.org/zope" xmlns:zcml="http://namespaces.zope.org/zcml"
|
|
2
|
+
i18n_domain="plone.restapi">
|
|
3
|
+
|
|
4
|
+
<adapter factory=".dxcontent.DeserializeVisualizationFromJson" />
|
|
5
|
+
<adapter factory=".dxfields.VisualizationFieldDeserializer" />
|
|
6
|
+
|
|
7
|
+
<subscriber
|
|
8
|
+
provides="plone.restapi.interfaces.IBlockFieldDeserializationTransformer"
|
|
9
|
+
factory=".blocks.EmbedVisualizationDeserializationTransformer" />
|
|
10
|
+
|
|
11
|
+
</configure>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
""" Deserialize Plotly visualization from JSON """
|
|
2
|
+
from io import StringIO
|
|
3
|
+
import json
|
|
4
|
+
import plotly.io as pio
|
|
5
|
+
from plone.namedfile.file import NamedBlobFile, NamedBlobImage
|
|
6
|
+
from plone.restapi.interfaces import IDeserializeFromJson
|
|
7
|
+
from plone.restapi.deserializer.dxcontent import DeserializeFromJson
|
|
8
|
+
from zope.component import adapter
|
|
9
|
+
from zope.interface import implementer
|
|
10
|
+
from zope.interface import Interface
|
|
11
|
+
from zope.event import notify
|
|
12
|
+
from zope.lifecycleevent import Attributes, ObjectModifiedEvent
|
|
13
|
+
from eea.plotly.behaviors import IPlotlyVisualization
|
|
14
|
+
from eea.plotly.io_csv import CsvWriter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@implementer(IDeserializeFromJson)
|
|
18
|
+
@adapter(IPlotlyVisualization, Interface)
|
|
19
|
+
class DeserializeVisualizationFromJson(DeserializeFromJson):
|
|
20
|
+
""" Deserialize Plotly visualization from JSON """
|
|
21
|
+
|
|
22
|
+
def jsonToBinary(self, data):
|
|
23
|
+
"""Convert JSON data to binary"""
|
|
24
|
+
if not data:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
output = StringIO()
|
|
28
|
+
|
|
29
|
+
# Extract columns
|
|
30
|
+
columns = list(data.keys())
|
|
31
|
+
|
|
32
|
+
# Extract rows per column
|
|
33
|
+
rows = []
|
|
34
|
+
|
|
35
|
+
# Determine the maximum length of the columns
|
|
36
|
+
max_length = 0
|
|
37
|
+
for column in columns:
|
|
38
|
+
max_length = max(max_length, len(data[column]))
|
|
39
|
+
|
|
40
|
+
for column in columns:
|
|
41
|
+
values = [column] + data[column]
|
|
42
|
+
# Fill the values with None to match the max length
|
|
43
|
+
values += [None] * (max_length - len(data[column]))
|
|
44
|
+
for i, value in enumerate(values):
|
|
45
|
+
if len(rows) < i + 1:
|
|
46
|
+
rows.append([value])
|
|
47
|
+
else:
|
|
48
|
+
rows[i].append(value)
|
|
49
|
+
|
|
50
|
+
CsvWriter(output).writerows(rows)
|
|
51
|
+
|
|
52
|
+
return output.getvalue().encode('utf-8')
|
|
53
|
+
|
|
54
|
+
def __call__(
|
|
55
|
+
self, validate_all=False, data=None, create=False,
|
|
56
|
+
mask_validation_errors=True):
|
|
57
|
+
|
|
58
|
+
super().__call__(
|
|
59
|
+
validate_all, data, create, mask_validation_errors
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
ok = False
|
|
63
|
+
for interface, names in self.modified.items():
|
|
64
|
+
if (interface == IPlotlyVisualization and
|
|
65
|
+
"IPlotlyVisualization.visualization" in names):
|
|
66
|
+
ok = True
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
if not ok:
|
|
70
|
+
return self.context
|
|
71
|
+
|
|
72
|
+
data = self.jsonToBinary(self.context.visualization.get(
|
|
73
|
+
"dataSources", {})
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.context.file = NamedBlobFile(
|
|
77
|
+
data=data if data else b"",
|
|
78
|
+
filename="data.csv",
|
|
79
|
+
contentType="text/csv"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if "dataSources" in self.context.visualization:
|
|
83
|
+
del self.context.visualization["dataSources"]
|
|
84
|
+
|
|
85
|
+
# Notify the object modified event
|
|
86
|
+
modified = []
|
|
87
|
+
modified.append(
|
|
88
|
+
Attributes(
|
|
89
|
+
IPlotlyVisualization,
|
|
90
|
+
"IPlotlyVisualization.visualization",
|
|
91
|
+
"IPlotlyVisualization.file"
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
notify(ObjectModifiedEvent(self.context, *modified))
|
|
95
|
+
|
|
96
|
+
# Handle preview image
|
|
97
|
+
fig = pio.from_json(
|
|
98
|
+
json.dumps({
|
|
99
|
+
"data": self.context.visualization.get("data", []),
|
|
100
|
+
"layout": self.context.visualization.get("layout", {}),
|
|
101
|
+
"frames": self.context.visualization.get("frames", [])
|
|
102
|
+
}),
|
|
103
|
+
skip_invalid=True
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
image = fig.to_image(format="svg", width=1200, height=900)
|
|
107
|
+
self.context.preview_image = NamedBlobImage(
|
|
108
|
+
data=image,
|
|
109
|
+
filename="preview.svg",
|
|
110
|
+
contentType="image/svg+xml"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return self.context
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
""" dxfields deserializers """
|
|
2
|
+
|
|
3
|
+
from zope.component import adapter
|
|
4
|
+
from zope.interface import implementer
|
|
5
|
+
from zope.publisher.interfaces.browser import IBrowserRequest
|
|
6
|
+
from plone.dexterity.interfaces import IDexterityContent
|
|
7
|
+
from plone.restapi.interfaces import IFieldDeserializer
|
|
8
|
+
from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
|
|
9
|
+
from plone.restapi.deserializer.utils import path2uid
|
|
10
|
+
from eea.plotly.utils import getLink, sanitizeVisualization
|
|
11
|
+
from eea.plotly.interfaces import IPlotlyVisualizationField
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@implementer(IFieldDeserializer)
|
|
15
|
+
@adapter(IPlotlyVisualizationField, IDexterityContent, IBrowserRequest)
|
|
16
|
+
class VisualizationFieldDeserializer(DefaultFieldDeserializer):
|
|
17
|
+
"""Visualization field deserializer"""
|
|
18
|
+
|
|
19
|
+
def __call__(self, value):
|
|
20
|
+
value = sanitizeVisualization(value)
|
|
21
|
+
|
|
22
|
+
if 'provider_url' in value:
|
|
23
|
+
url = value["provider_url"]
|
|
24
|
+
value["provider_url"] = path2uid(
|
|
25
|
+
context=self.context, link=getLink(url)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return value
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Module where all interfaces, events and exceptions live."""
|
|
2
|
+
from zope.interface import Interface
|
|
3
|
+
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IPlotlyLayer(IDefaultBrowserLayer):
|
|
7
|
+
""" Marker interface that defines a browser layer. """
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IPlotlyVisualizationField(Interface):
|
|
11
|
+
""" Marker interface for plotly visualization JSON field """
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IPlotlyFileField(Interface):
|
|
15
|
+
""" Marker interface for plotly file field """
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IPlotlyVisualization(Interface):
|
|
19
|
+
""" Marker interface for plotly visualization """
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""CSV reader/writer that handles NULL values."""
|
|
2
|
+
import csv
|
|
3
|
+
NULL = 'NULL'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CsvWriter:
|
|
7
|
+
"""CSV writer that handles NULL values."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, *args, **kwrds):
|
|
10
|
+
dialect = csv.excel
|
|
11
|
+
self.csv_writer = csv.writer(*args, dialect=dialect, **kwrds)
|
|
12
|
+
|
|
13
|
+
def __getattr__(self, name):
|
|
14
|
+
return getattr(self.csv_writer, name)
|
|
15
|
+
|
|
16
|
+
def writerow(self, row):
|
|
17
|
+
"""Write a single row to the CSV file."""
|
|
18
|
+
self.csv_writer.writerow(
|
|
19
|
+
[item if item is not None else NULL
|
|
20
|
+
for item in row]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def writerows(self, rows):
|
|
24
|
+
"""Write multiple rows to the CSV file."""
|
|
25
|
+
for row in rows:
|
|
26
|
+
self.writerow(row)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CsvReader:
|
|
30
|
+
"""CSV reader that handles NULL values."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args, **kwrds):
|
|
33
|
+
try:
|
|
34
|
+
inputEntity = args[0] or kwrds.get('csvfile')
|
|
35
|
+
sample = inputEntity.read(1024)
|
|
36
|
+
inputEntity.seek(0)
|
|
37
|
+
dialect = csv.Sniffer().sniff(sample)
|
|
38
|
+
except (csv.Error, TypeError):
|
|
39
|
+
# No dialect found, use default
|
|
40
|
+
dialect = csv.excel
|
|
41
|
+
self.csv_reader = csv.reader(*args, dialect=dialect, **kwrds)
|
|
42
|
+
|
|
43
|
+
def __getattr__(self, name):
|
|
44
|
+
return getattr(self.csv_reader, name)
|
|
45
|
+
|
|
46
|
+
def __iter__(self):
|
|
47
|
+
rows = iter(self.csv_reader)
|
|
48
|
+
for row in rows:
|
|
49
|
+
yield [item if item != NULL else None for item in row]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<rolemap>
|
|
3
|
+
<permissions>
|
|
4
|
+
<permission name="eea.plotly: Manage" acquire="True">
|
|
5
|
+
<role name="Manager" />
|
|
6
|
+
<role name="Site Administrator" />
|
|
7
|
+
</permission>
|
|
8
|
+
<permission name="EEA-Viz: Add Chart (interactive)" acquire="True">
|
|
9
|
+
<role name="Manager" />
|
|
10
|
+
<role name="Site Administrator" />
|
|
11
|
+
<role name="Contributor" />
|
|
12
|
+
</permission>
|
|
13
|
+
</permissions>
|
|
14
|
+
</rolemap>
|