eea.plotly 1.1__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.
Files changed (142) hide show
  1. {eea_plotly-1.1/eea.plotly.egg-info → eea_plotly-1.2}/PKG-INFO +9 -1
  2. {eea_plotly-1.1 → eea_plotly-1.2}/docs/HISTORY.txt +5 -0
  3. eea_plotly-1.2/eea/plotly/behaviors.py +35 -0
  4. eea_plotly-1.2/eea/plotly/browser/configure.zcml +26 -0
  5. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/browser/controlpanel.py +1 -1
  6. eea_plotly-1.2/eea/plotly/browser/preview.py +93 -0
  7. eea_plotly-1.2/eea/plotly/browser/pt/visualization_view.pt +17 -0
  8. eea_plotly-1.2/eea/plotly/configure.zcml +30 -0
  9. eea_plotly-1.1/eea/plotly/interfaces.py → eea_plotly-1.2/eea/plotly/controlpanel.py +19 -7
  10. eea_plotly-1.2/eea/plotly/deserializer/blocks.py +32 -0
  11. eea_plotly-1.2/eea/plotly/deserializer/configure.zcml +11 -0
  12. eea_plotly-1.2/eea/plotly/deserializer/dxcontent.py +113 -0
  13. eea_plotly-1.2/eea/plotly/deserializer/dxfields.py +28 -0
  14. eea_plotly-1.2/eea/plotly/interfaces.py +19 -0
  15. eea_plotly-1.2/eea/plotly/io_csv.py +49 -0
  16. eea_plotly-1.2/eea/plotly/permissions.zcml +6 -0
  17. eea_plotly-1.2/eea/plotly/profiles/default/rolemap.xml +14 -0
  18. eea_plotly-1.2/eea/plotly/profiles/default/types/visualization.xml +55 -0
  19. eea_plotly-1.2/eea/plotly/profiles/default/types.xml +4 -0
  20. eea_plotly-1.2/eea/plotly/profiles/uninstall/registry.xml +4 -0
  21. eea_plotly-1.2/eea/plotly/profiles.zcml +31 -0
  22. eea_plotly-1.2/eea/plotly/restapi/configure.zcml +7 -0
  23. eea_plotly-1.2/eea/plotly/restapi/controlpanel/__init__.py +0 -0
  24. eea_plotly-1.2/eea/plotly/restapi/controlpanel/configure.zcml +11 -0
  25. eea_plotly-1.2/eea/plotly/restapi/controlpanel/get.py +51 -0
  26. eea_plotly-1.2/eea/plotly/restapi/visualization/configure.zcml +19 -0
  27. eea_plotly-1.2/eea/plotly/restapi/visualization/get.py +49 -0
  28. eea_plotly-1.2/eea/plotly/serializer/blocks.py +131 -0
  29. eea_plotly-1.2/eea/plotly/serializer/configure.zcml +13 -0
  30. eea_plotly-1.2/eea/plotly/serializer/dxfields.py +62 -0
  31. eea_plotly-1.2/eea/plotly/serializer/utils.py +76 -0
  32. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/setuphandlers.py +1 -1
  33. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/tests/base.py +5 -1
  34. eea_plotly-1.2/eea/plotly/upgrades/configure.zcml +16 -0
  35. eea_plotly-1.2/eea/plotly/utils.py +73 -0
  36. eea_plotly-1.2/eea/plotly/version.txt +1 -0
  37. {eea_plotly-1.1 → eea_plotly-1.2/eea.plotly.egg-info}/PKG-INFO +9 -1
  38. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/SOURCES.txt +21 -2
  39. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/requires.txt +3 -0
  40. {eea_plotly-1.1 → eea_plotly-1.2}/setup.py +4 -0
  41. eea_plotly-1.1/eea/plotly/browser/configure.zcml +0 -14
  42. eea_plotly-1.1/eea/plotly/configure.zcml +0 -15
  43. eea_plotly-1.1/eea/plotly/permissions.zcml +0 -7
  44. eea_plotly-1.1/eea/plotly/profiles/default/rolemap.xml +0 -11
  45. eea_plotly-1.1/eea/plotly/profiles/uninstall/registry.xml +0 -4
  46. eea_plotly-1.1/eea/plotly/profiles.zcml +0 -33
  47. eea_plotly-1.1/eea/plotly/restapi/configure.zcml +0 -20
  48. eea_plotly-1.1/eea/plotly/restapi/controlpanel.py +0 -23
  49. eea_plotly-1.1/eea/plotly/restapi/get.py +0 -36
  50. eea_plotly-1.1/eea/plotly/upgrades/configure.zcml +0 -18
  51. eea_plotly-1.1/eea/plotly/version.txt +0 -1
  52. {eea_plotly-1.1 → eea_plotly-1.2}/CONTRIBUTING.md +0 -0
  53. {eea_plotly-1.1 → eea_plotly-1.2}/MANIFEST.in +0 -0
  54. {eea_plotly-1.1 → eea_plotly-1.2}/README.rst +0 -0
  55. {eea_plotly-1.1 → eea_plotly-1.2}/docs/LICENSE.GPL +0 -0
  56. {eea_plotly-1.1 → eea_plotly-1.2}/docs/LICENSE.txt +0 -0
  57. {eea_plotly-1.1 → eea_plotly-1.2}/eea/__init__.py +0 -0
  58. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/README.txt +0 -0
  59. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/__init__.py +0 -0
  60. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/browser/__init__.py +0 -0
  61. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/__init__.py +0 -0
  62. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/bg/LC_MESSAGES/eea.po +0 -0
  63. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/bg/LC_MESSAGES/plone.po +0 -0
  64. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/cs/LC_MESSAGES/eea.po +0 -0
  65. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/cs/LC_MESSAGES/plone.po +0 -0
  66. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/da/LC_MESSAGES/eea.po +0 -0
  67. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/da/LC_MESSAGES/plone.po +0 -0
  68. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/de/LC_MESSAGES/eea.po +0 -0
  69. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/de/LC_MESSAGES/plone.po +0 -0
  70. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/eea.pot +0 -0
  71. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/el/LC_MESSAGES/eea.po +0 -0
  72. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/el/LC_MESSAGES/plone.po +0 -0
  73. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/en/LC_MESSAGES/eea.po +0 -0
  74. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/en/LC_MESSAGES/plone.po +0 -0
  75. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/es/LC_MESSAGES/eea.po +0 -0
  76. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/es/LC_MESSAGES/plone.po +0 -0
  77. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/et/LC_MESSAGES/eea.po +0 -0
  78. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/et/LC_MESSAGES/plone.po +0 -0
  79. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/eu/LC_MESSAGES/eea.po +0 -0
  80. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/eu/LC_MESSAGES/plone.po +0 -0
  81. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/fi/LC_MESSAGES/eea.po +0 -0
  82. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/fi/LC_MESSAGES/plone.po +0 -0
  83. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/fr/LC_MESSAGES/eea.po +0 -0
  84. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/fr/LC_MESSAGES/plone.po +0 -0
  85. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/hr/LC_MESSAGES/eea.po +0 -0
  86. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/hr/LC_MESSAGES/plone.po +0 -0
  87. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/hu/LC_MESSAGES/eea.po +0 -0
  88. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/hu/LC_MESSAGES/plone.po +0 -0
  89. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/is/LC_MESSAGES/eea.po +0 -0
  90. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/is/LC_MESSAGES/plone.po +0 -0
  91. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/it/LC_MESSAGES/eea.po +0 -0
  92. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/it/LC_MESSAGES/plone.po +0 -0
  93. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/kl/LC_MESSAGES/eea.po +0 -0
  94. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/kl/LC_MESSAGES/plone.po +0 -0
  95. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/lt/LC_MESSAGES/eea.po +0 -0
  96. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/lt/LC_MESSAGES/plone.po +0 -0
  97. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/lv/LC_MESSAGES/eea.po +0 -0
  98. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/lv/LC_MESSAGES/plone.po +0 -0
  99. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/mt/LC_MESSAGES/eea.po +0 -0
  100. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/mt/LC_MESSAGES/plone.po +0 -0
  101. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/nl/LC_MESSAGES/eea.po +0 -0
  102. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/nl/LC_MESSAGES/plone.po +0 -0
  103. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/no/LC_MESSAGES/eea.po +0 -0
  104. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/no/LC_MESSAGES/plone.po +0 -0
  105. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/pl/LC_MESSAGES/eea.po +0 -0
  106. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/pl/LC_MESSAGES/plone.po +0 -0
  107. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/plone-manual.pot +0 -0
  108. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/pt/LC_MESSAGES/eea.po +0 -0
  109. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/pt/LC_MESSAGES/plone.po +0 -0
  110. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/pt_BR/LC_MESSAGES/eea.po +0 -0
  111. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/pt_BR/LC_MESSAGES/plone.po +0 -0
  112. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/ro/LC_MESSAGES/eea.po +0 -0
  113. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/ro/LC_MESSAGES/plone.po +0 -0
  114. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/ru/LC_MESSAGES/eea.po +0 -0
  115. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/ru/LC_MESSAGES/plone.po +0 -0
  116. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/sk/LC_MESSAGES/eea.po +0 -0
  117. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/sk/LC_MESSAGES/plone.po +0 -0
  118. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/sl/LC_MESSAGES/eea.po +0 -0
  119. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/sl/LC_MESSAGES/plone.po +0 -0
  120. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/sv/LC_MESSAGES/eea.po +0 -0
  121. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/sv/LC_MESSAGES/plone.po +0 -0
  122. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/tr/LC_MESSAGES/eea.po +0 -0
  123. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/tr/LC_MESSAGES/plone.po +0 -0
  124. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/update.sh +0 -0
  125. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/zh_TW/LC_MESSAGES/eea.po +0 -0
  126. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/locales/zh_TW/LC_MESSAGES/plone.po +0 -0
  127. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/profiles/default/browserlayer.xml +0 -0
  128. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/profiles/default/controlpanel.xml +0 -0
  129. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/profiles/default/metadata.xml +0 -0
  130. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/profiles/default/registry.xml +0 -0
  131. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/profiles/uninstall/browserlayer.xml +0 -0
  132. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/profiles/uninstall/controlpanel.xml +0 -0
  133. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/restapi/__init__.py +0 -0
  134. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/tests/__init__.py +0 -0
  135. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/tests/test_doctests.py +0 -0
  136. {eea_plotly-1.1 → eea_plotly-1.2}/eea/plotly/upgrades/__init__.py +0 -0
  137. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/dependency_links.txt +0 -0
  138. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/entry_points.txt +0 -0
  139. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/namespace_packages.txt +0 -0
  140. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/not-zip-safe +0 -0
  141. {eea_plotly-1.1 → eea_plotly-1.2}/eea.plotly.egg-info/top_level.txt +0 -0
  142. {eea_plotly-1.1 → 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.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,11 @@ 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
+
145
153
  1.1 - (2025-02-07)
146
154
  ---------------------------
147
155
  * Change: permission typo
@@ -1,6 +1,11 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ 1.2 - (2025-03-13)
5
+ ---------------------------
6
+ * Change: Add theme property
7
+ [razvanMiu]
8
+
4
9
  1.1 - (2025-02-07)
5
10
  ---------------------------
6
11
  * Change: permission typo
@@ -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.interfaces import IPlotlySettings
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>
@@ -1,11 +1,12 @@
1
- """Module where all interfaces, events and exceptions live."""
1
+ """Plotly Controlpanel API"""
2
2
  import json
3
3
  from zope.interface import Interface
4
- from zope.publisher.interfaces.browser import IDefaultBrowserLayer
4
+ from zope.component import adapter
5
5
  from plone.schema import JSONField
6
+ from plone.restapi.controlpanels import RegistryConfigletPanel
7
+ from eea.plotly.interfaces import IPlotlyLayer
6
8
  from eea.plotly import EEAMessageFactory as _
7
9
 
8
-
9
10
  TEMPLATES_SCHEMA = json.dumps({
10
11
  "type": "array",
11
12
  "items": {
@@ -23,10 +24,6 @@ THEMES_SCHEMA = json.dumps({
23
24
  })
24
25
 
25
26
 
26
- class IPlotlyLayer(IDefaultBrowserLayer):
27
- """Marker interface that defines a browser layer."""
28
-
29
-
30
27
  class IPlotlySettings(Interface):
31
28
  """Client settings for EEA Plotly."""
32
29
  themes = JSONField(
@@ -50,3 +47,18 @@ class IPlotlySettings(Interface):
50
47
  widget="plotly_templates",
51
48
  required=False
52
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,6 @@
1
+ <configure xmlns="http://namespaces.zope.org/zope" i18n_domain="eea">
2
+
3
+ <permission id="eea.plotly.manage" title="eea.plotly: Manage" />
4
+ <permission id="eea.plotly.add" title="EEA-Viz: Add Chart (interactive)" />
5
+
6
+ </configure>
@@ -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>
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <object name="visualization" meta_type="Dexterity FTI" i18n:domain="plone"
3
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n">
4
+ <property name="title" i18n:translate="">Chart (interactive)</property>
5
+ <property name="icon_expr"></property>
6
+ <property name="factory">visualization</property>
7
+ <property
8
+ name="add_view_expr">string:${folder_url}/++add++visualization</property>
9
+ <property name="link_target"></property>
10
+ <property name="immediate_view">view</property>
11
+ <property name="global_allow">True</property>
12
+ <property name="filter_content_types">True</property>
13
+ <property name="allowed_content_types" />
14
+ <property name="allow_discussion">False</property>
15
+ <property name="default_view">view</property>
16
+ <property name="view_methods">
17
+ </property>
18
+ <property name="default_view_fallback">False</property>
19
+ <property name="add_permission">eea.plotly.add</property>
20
+ <property name="klass">collective.folderishtypes.dx.content.FolderishDocument</property>
21
+ <property name="behaviors">
22
+ <element value="eea.plotly.visualization" />
23
+ <element value="eea.coremetadata.behavior" />
24
+ <element value="eea.dataconnector.figure_note" />
25
+ <element value="plone.namefromtitle" />
26
+ <element value="plone.shortname" />
27
+ <element value="plone.allowdiscussion" />
28
+ <element value="plone.excludefromnavigation" />
29
+ <element value="plone.dublincore" />
30
+ <element value="plone.richtext" />
31
+ <element value="plone.relateditems" />
32
+ <element value="plone.versioning" />
33
+ <element value="plone.tableofcontents" />
34
+ <element value="plone.locking" />
35
+ <element
36
+ value="Products.CMFPlone.interfaces.constrains.ISelectableConstrainTypes" />
37
+ </property>
38
+ <property
39
+ name="model_file">plone.app.contenttypes.schema:document.xml</property>
40
+ <property name="schema_policy">dexterity</property>
41
+ <alias from="(Default)" to="(dynamic view)" />
42
+ <alias from="edit" to="@@edit" />
43
+ <alias from="sharing" to="@@sharing" />
44
+ <alias from="view" to="(selected layout)" />
45
+ <action title="View" action_id="view" category="object" condition_expr=""
46
+ icon_expr="" link_target="" url_expr="string:${object_url}"
47
+ visible="True">
48
+ <permission value="View" />
49
+ </action>
50
+ <action title="Edit" action_id="edit" category="object" condition_expr=""
51
+ icon_expr="" link_target="" url_expr="string:${object_url}/edit"
52
+ visible="True">
53
+ <permission value="Modify portal content" />
54
+ </action>
55
+ </object>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0"?>
2
+ <object name="portal_types" meta_type="Plone Types Tool">
3
+ <object name="visualization" meta_type="Dexterity FTI" />
4
+ </object>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0"?>
2
+ <registry>
3
+ <records interface="eea.plotly.controlpanel.IPlotlySettings" remove="true" />
4
+ </registry>