umap-project 2.7.2__py3-none-any.whl → 2.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. umap/__init__.py +1 -1
  2. umap/forms.py +4 -14
  3. umap/locale/am_ET/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/am_ET/LC_MESSAGES/django.po +278 -151
  5. umap/locale/ar/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/ar/LC_MESSAGES/django.po +335 -141
  7. umap/locale/bg/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/bg/LC_MESSAGES/django.po +279 -152
  9. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/br/LC_MESSAGES/django.po +95 -79
  11. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/ca/LC_MESSAGES/django.po +85 -68
  13. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/cs_CZ/LC_MESSAGES/django.po +78 -66
  15. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/da/LC_MESSAGES/django.po +280 -153
  17. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/de/LC_MESSAGES/django.po +80 -64
  19. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/el/LC_MESSAGES/django.po +82 -66
  21. umap/locale/en/LC_MESSAGES/django.po +73 -61
  22. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/es/LC_MESSAGES/django.po +75 -63
  24. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/et/LC_MESSAGES/django.po +280 -153
  26. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/eu/LC_MESSAGES/django.po +82 -66
  28. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/fa_IR/LC_MESSAGES/django.po +80 -64
  30. umap/locale/fi/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/fi/LC_MESSAGES/django.po +278 -151
  32. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/fr/LC_MESSAGES/django.po +75 -63
  34. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/gl/LC_MESSAGES/django.po +280 -153
  36. umap/locale/he/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/he/LC_MESSAGES/django.po +281 -154
  38. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/hu/LC_MESSAGES/django.po +80 -64
  40. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  41. umap/locale/is/LC_MESSAGES/django.po +280 -153
  42. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  43. umap/locale/it/LC_MESSAGES/django.po +82 -66
  44. umap/locale/ja/LC_MESSAGES/django.mo +0 -0
  45. umap/locale/ja/LC_MESSAGES/django.po +280 -153
  46. umap/locale/ko/LC_MESSAGES/django.mo +0 -0
  47. umap/locale/ko/LC_MESSAGES/django.po +280 -153
  48. umap/locale/lt/LC_MESSAGES/django.mo +0 -0
  49. umap/locale/lt/LC_MESSAGES/django.po +280 -153
  50. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  51. umap/locale/ms/LC_MESSAGES/django.po +82 -66
  52. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  53. umap/locale/nl/LC_MESSAGES/django.po +280 -153
  54. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  55. umap/locale/pl/LC_MESSAGES/django.po +82 -66
  56. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  57. umap/locale/pt/LC_MESSAGES/django.po +75 -63
  58. umap/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  59. umap/locale/pt_BR/LC_MESSAGES/django.po +280 -153
  60. umap/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  61. umap/locale/pt_PT/LC_MESSAGES/django.po +280 -153
  62. umap/locale/ru/LC_MESSAGES/django.mo +0 -0
  63. umap/locale/ru/LC_MESSAGES/django.po +280 -153
  64. umap/locale/sk_SK/LC_MESSAGES/django.mo +0 -0
  65. umap/locale/sk_SK/LC_MESSAGES/django.po +280 -153
  66. umap/locale/sl/LC_MESSAGES/django.mo +0 -0
  67. umap/locale/sl/LC_MESSAGES/django.po +280 -153
  68. umap/locale/sr/LC_MESSAGES/django.mo +0 -0
  69. umap/locale/sr/LC_MESSAGES/django.po +280 -153
  70. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  71. umap/locale/sv/LC_MESSAGES/django.po +81 -65
  72. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  73. umap/locale/th_TH/LC_MESSAGES/django.po +257 -185
  74. umap/locale/tr/LC_MESSAGES/django.mo +0 -0
  75. umap/locale/tr/LC_MESSAGES/django.po +280 -153
  76. umap/locale/uk_UA/LC_MESSAGES/django.mo +0 -0
  77. umap/locale/uk_UA/LC_MESSAGES/django.po +280 -153
  78. umap/locale/vi/LC_MESSAGES/django.mo +0 -0
  79. umap/locale/vi/LC_MESSAGES/django.po +278 -151
  80. umap/locale/zh/LC_MESSAGES/django.mo +0 -0
  81. umap/locale/zh/LC_MESSAGES/django.po +278 -151
  82. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  83. umap/locale/zh_TW/LC_MESSAGES/django.po +97 -81
  84. umap/management/commands/empty_trash.py +35 -0
  85. umap/management/commands/migrate_to_S3.py +29 -0
  86. umap/migrations/0023_alter_datalayer_uuid.py +19 -0
  87. umap/migrations/0024_alter_map_share_status.py +30 -0
  88. umap/migrations/0025_alter_datalayer_geojson.py +24 -0
  89. umap/models.py +68 -116
  90. umap/settings/base.py +23 -3
  91. umap/settings/local_s3.py +45 -0
  92. umap/static/umap/base.css +3 -603
  93. umap/static/umap/content.css +5 -3
  94. umap/static/umap/css/bar.css +202 -0
  95. umap/static/umap/css/form.css +620 -0
  96. umap/static/umap/css/icon.css +21 -1
  97. umap/static/umap/css/popup.css +125 -0
  98. umap/static/umap/img/16-white.svg +16 -4
  99. umap/static/umap/img/16.svg +1 -1
  100. umap/static/umap/img/source/16-white.svg +46 -45
  101. umap/static/umap/img/source/16.svg +1 -753
  102. umap/static/umap/js/components/fragment.js +3 -1
  103. umap/static/umap/js/modules/browser.js +20 -19
  104. umap/static/umap/js/modules/caption.js +21 -22
  105. umap/static/umap/js/modules/data/features.js +120 -78
  106. umap/static/umap/js/modules/data/layer.js +195 -153
  107. umap/static/umap/js/modules/facets.js +9 -9
  108. umap/static/umap/js/modules/formatter.js +5 -5
  109. umap/static/umap/js/modules/global.js +4 -52
  110. umap/static/umap/js/modules/help.js +18 -21
  111. umap/static/umap/js/modules/importer.js +133 -56
  112. umap/static/umap/js/modules/importers/cadastrefr.js +4 -0
  113. umap/static/umap/js/modules/importers/geodatamine.js +3 -3
  114. umap/static/umap/js/modules/importers/overpass.js +5 -0
  115. umap/static/umap/js/modules/permissions.js +85 -87
  116. umap/static/umap/js/modules/rendering/icon.js +2 -1
  117. umap/static/umap/js/modules/rendering/layers/base.js +15 -15
  118. umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
  119. umap/static/umap/js/modules/rendering/layers/cluster.js +1 -1
  120. umap/static/umap/js/modules/rendering/layers/heat.js +1 -1
  121. umap/static/umap/js/modules/rendering/map.js +390 -0
  122. umap/static/umap/js/modules/rendering/popup.js +19 -19
  123. umap/static/umap/js/modules/rendering/template.js +88 -21
  124. umap/static/umap/js/modules/rendering/ui.js +63 -14
  125. umap/static/umap/js/modules/request.js +2 -2
  126. umap/static/umap/js/modules/rules.js +22 -25
  127. umap/static/umap/js/modules/saving.js +47 -0
  128. umap/static/umap/js/modules/schema.js +6 -0
  129. umap/static/umap/js/modules/share.js +21 -24
  130. umap/static/umap/js/modules/slideshow.js +24 -20
  131. umap/static/umap/js/modules/sync/updaters.js +7 -9
  132. umap/static/umap/js/modules/tableeditor.js +20 -19
  133. umap/static/umap/js/modules/ui/bar.js +196 -0
  134. umap/static/umap/js/modules/ui/dialog.js +6 -1
  135. umap/static/umap/js/modules/ui/panel.js +10 -9
  136. umap/static/umap/js/modules/umap.js +1691 -0
  137. umap/static/umap/js/modules/urls.js +2 -2
  138. umap/static/umap/js/modules/utils.js +22 -6
  139. umap/static/umap/js/umap.controls.js +81 -305
  140. umap/static/umap/js/umap.core.js +29 -50
  141. umap/static/umap/js/umap.forms.js +78 -27
  142. umap/static/umap/keycloak.png +0 -0
  143. umap/static/umap/locale/am_ET.js +26 -10
  144. umap/static/umap/locale/am_ET.json +26 -10
  145. umap/static/umap/locale/ar.js +26 -10
  146. umap/static/umap/locale/ar.json +26 -10
  147. umap/static/umap/locale/ast.js +26 -10
  148. umap/static/umap/locale/ast.json +26 -10
  149. umap/static/umap/locale/bg.js +26 -10
  150. umap/static/umap/locale/bg.json +26 -10
  151. umap/static/umap/locale/br.js +27 -20
  152. umap/static/umap/locale/br.json +27 -20
  153. umap/static/umap/locale/ca.js +32 -29
  154. umap/static/umap/locale/ca.json +32 -29
  155. umap/static/umap/locale/cs_CZ.js +24 -17
  156. umap/static/umap/locale/cs_CZ.json +24 -17
  157. umap/static/umap/locale/da.js +26 -10
  158. umap/static/umap/locale/da.json +26 -10
  159. umap/static/umap/locale/de.js +21 -14
  160. umap/static/umap/locale/de.json +21 -14
  161. umap/static/umap/locale/el.js +28 -12
  162. umap/static/umap/locale/el.json +28 -12
  163. umap/static/umap/locale/en.js +14 -9
  164. umap/static/umap/locale/en.json +14 -9
  165. umap/static/umap/locale/en_US.json +26 -10
  166. umap/static/umap/locale/es.js +16 -13
  167. umap/static/umap/locale/es.json +16 -13
  168. umap/static/umap/locale/et.js +26 -10
  169. umap/static/umap/locale/et.json +26 -10
  170. umap/static/umap/locale/eu.js +16 -9
  171. umap/static/umap/locale/eu.json +16 -9
  172. umap/static/umap/locale/fa_IR.js +16 -9
  173. umap/static/umap/locale/fa_IR.json +16 -9
  174. umap/static/umap/locale/fi.js +26 -10
  175. umap/static/umap/locale/fi.json +26 -10
  176. umap/static/umap/locale/fr.js +14 -9
  177. umap/static/umap/locale/fr.json +14 -9
  178. umap/static/umap/locale/gl.js +26 -10
  179. umap/static/umap/locale/gl.json +26 -10
  180. umap/static/umap/locale/he.js +26 -10
  181. umap/static/umap/locale/he.json +26 -10
  182. umap/static/umap/locale/hr.js +26 -10
  183. umap/static/umap/locale/hr.json +26 -10
  184. umap/static/umap/locale/hu.js +16 -9
  185. umap/static/umap/locale/hu.json +16 -9
  186. umap/static/umap/locale/id.js +26 -10
  187. umap/static/umap/locale/id.json +26 -10
  188. umap/static/umap/locale/is.js +26 -10
  189. umap/static/umap/locale/is.json +26 -10
  190. umap/static/umap/locale/it.js +26 -10
  191. umap/static/umap/locale/it.json +26 -10
  192. umap/static/umap/locale/ja.js +26 -10
  193. umap/static/umap/locale/ja.json +26 -10
  194. umap/static/umap/locale/ko.js +26 -10
  195. umap/static/umap/locale/ko.json +26 -10
  196. umap/static/umap/locale/lt.js +26 -10
  197. umap/static/umap/locale/lt.json +26 -10
  198. umap/static/umap/locale/ms.js +28 -12
  199. umap/static/umap/locale/ms.json +28 -12
  200. umap/static/umap/locale/nl.js +28 -12
  201. umap/static/umap/locale/nl.json +28 -12
  202. umap/static/umap/locale/no.js +26 -10
  203. umap/static/umap/locale/no.json +26 -10
  204. umap/static/umap/locale/pl.js +28 -12
  205. umap/static/umap/locale/pl.json +28 -12
  206. umap/static/umap/locale/pl_PL.json +26 -10
  207. umap/static/umap/locale/pt.js +16 -9
  208. umap/static/umap/locale/pt.json +16 -9
  209. umap/static/umap/locale/pt_BR.js +26 -10
  210. umap/static/umap/locale/pt_BR.json +26 -10
  211. umap/static/umap/locale/pt_PT.js +16 -9
  212. umap/static/umap/locale/pt_PT.json +16 -9
  213. umap/static/umap/locale/ro.js +26 -10
  214. umap/static/umap/locale/ro.json +26 -10
  215. umap/static/umap/locale/ru.js +26 -10
  216. umap/static/umap/locale/ru.json +26 -10
  217. umap/static/umap/locale/si.js +7 -7
  218. umap/static/umap/locale/si.json +7 -7
  219. umap/static/umap/locale/sk_SK.js +26 -10
  220. umap/static/umap/locale/sk_SK.json +26 -10
  221. umap/static/umap/locale/sl.js +26 -10
  222. umap/static/umap/locale/sl.json +26 -10
  223. umap/static/umap/locale/sr.js +26 -10
  224. umap/static/umap/locale/sr.json +26 -10
  225. umap/static/umap/locale/sv.js +27 -11
  226. umap/static/umap/locale/sv.json +27 -11
  227. umap/static/umap/locale/th_TH.js +28 -12
  228. umap/static/umap/locale/th_TH.json +28 -12
  229. umap/static/umap/locale/tr.js +26 -10
  230. umap/static/umap/locale/tr.json +26 -10
  231. umap/static/umap/locale/uk_UA.js +26 -10
  232. umap/static/umap/locale/uk_UA.json +26 -10
  233. umap/static/umap/locale/vi.js +26 -10
  234. umap/static/umap/locale/vi.json +26 -10
  235. umap/static/umap/locale/vi_VN.json +26 -10
  236. umap/static/umap/locale/zh.js +26 -10
  237. umap/static/umap/locale/zh.json +26 -10
  238. umap/static/umap/locale/zh_CN.json +26 -10
  239. umap/static/umap/locale/zh_TW.Big5.json +26 -10
  240. umap/static/umap/locale/zh_TW.js +34 -27
  241. umap/static/umap/locale/zh_TW.json +34 -27
  242. umap/static/umap/map.css +39 -530
  243. umap/static/umap/unittests/URLs.js +15 -15
  244. umap/static/umap/unittests/utils.js +23 -1
  245. umap/static/umap/vars.css +2 -1
  246. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +5 -1
  247. umap/storage/__init__.py +3 -0
  248. umap/storage/fs.py +101 -0
  249. umap/storage/s3.py +61 -0
  250. umap/templates/base.html +2 -0
  251. umap/templates/registration/login.html +7 -6
  252. umap/templates/umap/components/alerts/alert.html +4 -0
  253. umap/templates/umap/css.html +6 -0
  254. umap/templates/umap/js.html +3 -2
  255. umap/templates/umap/map_init.html +6 -5
  256. umap/templates/umap/user_dashboard.html +20 -19
  257. umap/tests/base.py +11 -1
  258. umap/tests/fixtures/empty_tile.png +0 -0
  259. umap/tests/fixtures/test_upload_simple_marker.json +19 -0
  260. umap/tests/integration/conftest.py +4 -1
  261. umap/tests/integration/test_anonymous_owned_map.py +18 -10
  262. umap/tests/integration/test_browser.py +16 -1
  263. umap/tests/integration/test_dashboard.py +1 -1
  264. umap/tests/integration/test_edit_datalayer.py +29 -7
  265. umap/tests/integration/test_import.py +28 -4
  266. umap/tests/integration/test_optimistic_merge.py +31 -8
  267. umap/tests/integration/test_owned_map.py +22 -16
  268. umap/tests/integration/test_popup.py +44 -0
  269. umap/tests/integration/test_save.py +35 -0
  270. umap/tests/integration/test_view_marker.py +12 -0
  271. umap/tests/integration/test_view_polyline.py +257 -0
  272. umap/tests/integration/test_websocket_sync.py +81 -9
  273. umap/tests/test_dashboard.py +82 -0
  274. umap/tests/test_datalayer.py +6 -7
  275. umap/tests/test_datalayer_s3.py +135 -0
  276. umap/tests/test_datalayer_views.py +28 -10
  277. umap/tests/test_empty_trash.py +34 -0
  278. umap/tests/test_map.py +12 -3
  279. umap/tests/test_map_views.py +69 -37
  280. umap/tests/test_statics.py +1 -1
  281. umap/tests/test_team_views.py +35 -1
  282. umap/tests/test_views.py +31 -52
  283. umap/urls.py +3 -3
  284. umap/views.py +126 -90
  285. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/METADATA +16 -14
  286. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/RECORD +290 -269
  287. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/WHEEL +1 -1
  288. umap/management/commands/purge_purgatory.py +0 -28
  289. umap/static/umap/js/umap.js +0 -1903
  290. umap/tests/test_purge_purgatory.py +0 -25
  291. /umap/{storage.py → storage/staticfiles.py} +0 -0
  292. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/entry_points.txt +0 -0
  293. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -147,7 +147,7 @@ describe('Utils', () => {
147
147
  )
148
148
  })
149
149
 
150
- it('should handle iframe with height with px', () => {
150
+ it('should handle double iframe', () => {
151
151
  assert.equal(
152
152
  Utils.toHTML(
153
153
  'A double iframe: {{{https://osm.org/pouet}}}{{{https://osm.org/boudin}}}'
@@ -156,6 +156,15 @@ describe('Utils', () => {
156
156
  )
157
157
  })
158
158
 
159
+ it('should handle iframe with query string and space', () => {
160
+ assert.equal(
161
+ Utils.toHTML(
162
+ 'An iframe with query string: {{{https://osm.org/pouet.html?name=foobar&description=baz baz}}}'
163
+ ),
164
+ 'An iframe with query string: <div><iframe height="300px" width="100%" src="https://osm.org/pouet.html?name=foobar&amp;description=baz baz" frameborder="0"></iframe></div>'
165
+ )
166
+ })
167
+
159
168
  it('http link with http link as parameter as variable', () => {
160
169
  assert.equal(
161
170
  Utils.toHTML('A phrase with a [[http://iframeurl.com?to=http://another.com]].'),
@@ -263,6 +272,19 @@ describe('Utils', () => {
263
272
  )
264
273
  })
265
274
 
275
+ it('should process variables in http links', () => {
276
+ assert.equal(
277
+ Utils.greedyTemplate(
278
+ 'A phrase with a {{{https://osm.org/pouet?name={name}&description={description}}}}.',
279
+ {
280
+ name: 'foobar',
281
+ description: 'bazbaz',
282
+ }
283
+ ),
284
+ 'A phrase with a {{{https://osm.org/pouet?name=foobar&description=bazbaz}}}.'
285
+ )
286
+ })
287
+
266
288
  it('should not accept dash', () => {
267
289
  assert.equal(
268
290
  Utils.greedyTemplate('A phrase with a {var-iable}.', { 'var-iable': 'value' }),
umap/static/umap/vars.css CHANGED
@@ -2,9 +2,11 @@
2
2
  /* Colors. */
3
3
  --color-waterMint: #B9F5D2;
4
4
  --color-darkBlue: #263B58;
5
+ --color-lighterGray: #f6f6f6;
5
6
  --color-lightGray: #ddd;
6
7
  --color-mediumGray: #3e4444;
7
8
  --color-darkGray: #323737;
9
+ --color-darkerGray: #2a2e30;
8
10
  --color-light: white;
9
11
  --color-dark: black;
10
12
  --color-limeGreen: #b9f5d2;
@@ -46,7 +48,6 @@
46
48
  --zindex-autocomplete: 470;
47
49
  --zindex-dialog: 460;
48
50
  --zindex-contextmenu: 455;
49
- --zindex-icon-active: 450;
50
51
  --zindex-tooltip: 445;
51
52
  --zindex-panels: 440;
52
53
  --zindex-controls: 430;
@@ -63,7 +63,11 @@ L.FormBuilder = L.Evented.extend({
63
63
  const path = field.split('.')
64
64
  let value = this.obj
65
65
  for (const sub of path) {
66
- value = value[sub]
66
+ try {
67
+ value = value[sub]
68
+ } catch {
69
+ console.log(field)
70
+ }
67
71
  }
68
72
  return value
69
73
  },
@@ -0,0 +1,3 @@
1
+ # Retrocompat
2
+
3
+ from .staticfiles import UmapManifestStaticFilesStorage # noqa: F401
umap/storage/fs.py ADDED
@@ -0,0 +1,101 @@
1
+ import operator
2
+ import os
3
+ import time
4
+ from pathlib import Path
5
+
6
+ from django.conf import settings
7
+ from django.core.files.storage import FileSystemStorage
8
+
9
+
10
+ class FSDataStorage(FileSystemStorage):
11
+ def get_reference_version(self, instance):
12
+ return self._extract_version_ref(instance.geojson.name)
13
+
14
+ def make_filename(self, instance):
15
+ root = self._base_path(instance)
16
+ name = "%s_%s.geojson" % (instance.pk, int(time.time() * 1000))
17
+ return root / name
18
+
19
+ def list_versions(self, instance):
20
+ root = self._base_path(instance)
21
+ names = self.listdir(root)[1]
22
+ names = [name for name in names if self._is_valid_version(name, instance)]
23
+ versions = [self._version_metadata(name, instance) for name in names]
24
+ versions.sort(reverse=True, key=operator.itemgetter("at"))
25
+ return versions
26
+
27
+ def get_version(self, ref, instance):
28
+ with self.open(self.get_version_path(ref, instance), "r") as f:
29
+ return f.read()
30
+
31
+ def get_version_path(self, ref, instance):
32
+ base_path = Path(settings.MEDIA_ROOT) / self._base_path(instance)
33
+ fullpath = base_path / f"{instance.pk}_{ref}.geojson"
34
+ if instance.old_id and not fullpath.exists():
35
+ fullpath = base_path / f"{instance.old_id}_{ref}.geojson"
36
+ if not fullpath.exists():
37
+ raise ValueError(f"Invalid version reference: {ref}")
38
+ return fullpath
39
+
40
+ def onDatalayerSave(self, instance):
41
+ self._purge_gzip(instance)
42
+ self._purge_old_versions(instance, keep=settings.UMAP_KEEP_VERSIONS)
43
+
44
+ def onDatalayerDelete(self, instance):
45
+ self._purge_gzip(instance)
46
+ self._purge_old_versions(instance, keep=None)
47
+
48
+ def _extract_version_ref(self, path):
49
+ version = path.split(".")[0]
50
+ if "_" in version:
51
+ return version.split("_")[-1]
52
+ return version
53
+
54
+ def _base_path(self, instance):
55
+ path = ["datalayer", str(instance.map.pk)[-1]]
56
+ if len(str(instance.map.pk)) > 1:
57
+ path.append(str(instance.map.pk)[-2])
58
+ path.append(str(instance.map.pk))
59
+ return Path(os.path.join(*path))
60
+
61
+ def _is_valid_version(self, name, instance):
62
+ valid_prefixes = [name.startswith("%s_" % instance.pk)]
63
+ if instance.old_id:
64
+ valid_prefixes.append(name.startswith("%s_" % instance.old_id))
65
+ return any(valid_prefixes) and name.endswith(".geojson")
66
+
67
+ def _version_metadata(self, name, instance):
68
+ ref = self._extract_version_ref(name)
69
+ return {
70
+ "name": name,
71
+ "ref": ref,
72
+ "at": ref,
73
+ "size": self.size(self._base_path(instance) / name),
74
+ }
75
+
76
+ def _purge_old_versions(self, instance, keep=None):
77
+ root = self._base_path(instance)
78
+ versions = self.list_versions(instance)
79
+ if keep is not None:
80
+ versions = versions[keep:]
81
+ for version in versions:
82
+ name = version["name"]
83
+ # Should not be in the list, but ensure to not delete the file
84
+ # currently used in database
85
+ if keep is not None and instance.geojson.name.endswith(name):
86
+ continue
87
+ try:
88
+ self.delete(root / name)
89
+ except FileNotFoundError:
90
+ pass
91
+
92
+ def _purge_gzip(self, instance):
93
+ root = self._base_path(instance)
94
+ names = self.listdir(root)[1]
95
+ prefixes = [f"{instance.pk}_"]
96
+ if instance.old_id:
97
+ prefixes.append(f"{instance.old_id}_")
98
+ prefixes = tuple(prefixes)
99
+ for name in names:
100
+ if name.startswith(prefixes) and name.endswith(".gz"):
101
+ self.delete(root / name)
umap/storage/s3.py ADDED
@@ -0,0 +1,61 @@
1
+ from gzip import GzipFile
2
+
3
+ from django.core.exceptions import ImproperlyConfigured
4
+
5
+ try:
6
+ from botocore.exceptions import ClientError
7
+ from storages.backends.s3 import S3Storage
8
+ except ImportError:
9
+ raise ImproperlyConfigured(
10
+ "You need to install s3 dependencies: pip install umap-project[s3]"
11
+ )
12
+
13
+
14
+ class S3DataStorage(S3Storage):
15
+ gzip = True
16
+
17
+ def get_reference_version(self, instance):
18
+ metadata = self.connection.meta.client.head_object(
19
+ Bucket=self.bucket_name, Key=instance.geojson.name
20
+ )
21
+ # Do not fail if bucket does not handle versioning
22
+ return metadata.get("VersionId", metadata["ETag"])
23
+
24
+ def make_filename(self, instance):
25
+ return f"{str(instance.pk)}.geojson"
26
+
27
+ def list_versions(self, instance):
28
+ response = self.connection.meta.client.list_object_versions(
29
+ Bucket=self.bucket_name, Prefix=instance.geojson.name
30
+ )
31
+ return [
32
+ {
33
+ "ref": version["VersionId"],
34
+ "at": version["LastModified"].timestamp() * 1000,
35
+ "size": version["Size"],
36
+ }
37
+ for version in response["Versions"]
38
+ ]
39
+
40
+ def get_version(self, ref, instance):
41
+ try:
42
+ data = self.connection.meta.client.get_object(
43
+ Bucket=self.bucket_name,
44
+ Key=instance.geojson.name,
45
+ VersionId=ref,
46
+ )
47
+ except ClientError:
48
+ raise ValueError(f"Invalid version reference: {ref}")
49
+ return GzipFile(mode="r", fileobj=data["Body"]).read()
50
+
51
+ def get_version_path(self, ref, instance):
52
+ return self.url(instance.geojson.name, parameters={"VersionId": ref})
53
+
54
+ def onDatalayerSave(self, instance):
55
+ pass
56
+
57
+ def onDatalayerDelete(self, instance):
58
+ return self.connection.meta.client.delete_object(
59
+ Bucket=self.bucket_name,
60
+ Key=instance.geojson.name,
61
+ )
umap/templates/base.html CHANGED
@@ -18,6 +18,7 @@
18
18
  <meta name="viewport"
19
19
  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
20
20
  {# See https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs #}
21
+ {% autoescape off %}
21
22
  <link rel="icon"
22
23
  href="{% static 'umap/favicons/favicon.ico' %}"
23
24
  sizes="32x32">
@@ -28,6 +29,7 @@
28
29
  href="{% static 'umap/favicons/apple-touch-icon.png' %}">
29
30
  <!-- 180×180 -->
30
31
  <link rel="manifest" href="/manifest.webmanifest">
32
+ {% endautoescape %}
31
33
  </head>
32
34
  <body class="{% block body_class %}{% endblock body_class %}">
33
35
  {% block header %}
@@ -19,10 +19,11 @@
19
19
  <header class="umap-nav">
20
20
  {% include "umap/branding.html" %}
21
21
  </header>
22
+ <h2>{% trans "To save and easily find your maps, identify yourself." %}</h2>
22
23
  {% if ENABLE_ACCOUNT_LOGIN %}
23
- <h2>
24
- {% trans "Please log in with your account" %}
25
- </h2>
24
+ <h3>
25
+ {% trans "Please log in with your account:" %}
26
+ </h3>
26
27
  <div>
27
28
  {% if form.non_field_errors %}
28
29
  <ul class="form-errors">
@@ -47,9 +48,9 @@
47
48
  </div>
48
49
  {% endif %}
49
50
  {% if backends.backends|length %}
50
- <h2>
51
- {% trans "Please choose a provider" %}
52
- </h2>
51
+ <h3>
52
+ {% trans "Please choose a provider:" %}
53
+ </h3>
53
54
  <div>
54
55
  <ul class="login-grid block-grid">
55
56
  {% for name in backends.backends %}
@@ -1,8 +1,10 @@
1
1
  {% load i18n static %}
2
2
 
3
+ {% autoescape off %}
3
4
  <style type="text/css">
4
5
  @import "{% static 'umap/js/components/alerts/alert.css' %}";
5
6
  </style>
7
+ {% endautoescape %}
6
8
  <template id="umap-alert-template">
7
9
  <div role="dialog" class="dark window umap-alert">
8
10
  <div>
@@ -97,6 +99,7 @@
97
99
  </div>
98
100
  </template>
99
101
  <umap-alert-conflict></umap-alert-conflict>
102
+ {% autoescape off %}
100
103
  <script type="module">
101
104
  import { register } from '{% static 'umap/js/components/base.js' %}'
102
105
  import {
@@ -108,3 +111,4 @@
108
111
  register(uMapAlertCreation, 'umap-alert-creation')
109
112
  register(uMapAlertConflict, 'umap-alert-conflict')
110
113
  </script>
114
+ {% endautoescape %}
@@ -1,5 +1,7 @@
1
1
  {% load static %}
2
2
 
3
+ {% autoescape off %}
4
+
3
5
  <link rel="stylesheet"
4
6
  href="{% static 'umap/vendors/leaflet/leaflet.css' %}" />
5
7
  <link rel="stylesheet"
@@ -24,6 +26,8 @@
24
26
  <link rel="stylesheet" href="{% static 'umap/font.css' %}" />
25
27
  <link rel="stylesheet" href="{% static 'umap/css/icon.css' %}" />
26
28
  <link rel="stylesheet" href="{% static 'umap/base.css' %}" />
29
+ <link rel="stylesheet" href="{% static 'umap/css/popup.css' %}" />
30
+ <link rel="stylesheet" href="{% static 'umap/css/form.css' %}" />
27
31
  <link rel="stylesheet" href="{% static 'umap/content.css' %}" />
28
32
  <link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
29
33
  <link rel="stylesheet" href="{% static 'umap/map.css' %}" />
@@ -35,4 +39,6 @@
35
39
  <link rel="stylesheet" href="{% static 'umap/css/dialog.css' %}" />
36
40
  <link rel="stylesheet" href="{% static 'umap/css/importers.css' %}" />
37
41
  <link rel="stylesheet" href="{% static 'umap/css/tableeditor.css' %}" />
42
+ <link rel="stylesheet" href="{% static 'umap/css/bar.css' %}" />
38
43
  <link rel="stylesheet" href="{% static 'umap/theme.css' %}" />
44
+ {% endautoescape %}
@@ -1,5 +1,6 @@
1
1
  {% load static %}
2
2
 
3
+ {% autoescape off %}
3
4
  <script type="module"
4
5
  src="{% static 'umap/vendors/leaflet/leaflet-src.esm.js' %}"
5
6
  defer></script>
@@ -41,5 +42,5 @@
41
42
  <script src="{% static 'umap/js/umap.core.js' %}" defer></script>
42
43
  <script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
43
44
  <script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
44
- <script src="{% static 'umap/js/umap.js' %}" defer></script>
45
- <script src="{% static 'umap/js/components/fragment.js' %}" defer></script>
45
+ <script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
46
+ {% endautoescape %}
@@ -1,12 +1,13 @@
1
- {% load umap_tags %}
1
+ {% load umap_tags static %}
2
2
 
3
3
  {% include "umap/messages.html" %}
4
4
  <div id="map">
5
5
  </div>
6
6
  <!-- djlint:off -->
7
- <script defer type="text/javascript">
8
- window.addEventListener('DOMContentLoaded', (event) => {
9
- U.MAP = new U.Map("map", {{ map_settings|notag|safe }})
10
- })
7
+ <script defer type="module">
8
+ {% autoescape off %}
9
+ import Umap from '{% static "umap/js/modules/umap.js" %}'
10
+ {% endautoescape %}
11
+ U.MAP = new Umap("map", {{ map_settings|notag|safe }})
11
12
  </script>
12
13
  <!-- djlint:on -->
@@ -1,6 +1,6 @@
1
1
  {% extends "umap/content.html" %}
2
2
 
3
- {% load i18n %}
3
+ {% load i18n static %}
4
4
 
5
5
  {% block head_title %}
6
6
  {{ SITE_NAME }} - {% trans "My Dashboard" %}
@@ -45,23 +45,24 @@
45
45
  {% endblock maincontent %}
46
46
  {% block bottom_js %}
47
47
  {{ block.super }}
48
- <script type="text/javascript">
49
- !(function () {
50
- const CACHE = {}
51
- for (const mapOpener of document.querySelectorAll("button.map-opener")) {
52
- mapOpener.addEventListener('click', (event) => {
53
- const button = event.target.closest('button')
54
- button.nextElementSibling.showModal()
55
- const mapId = button.dataset.mapId
56
- if (!document.querySelector(`#${mapId}_target`).children.length) {
57
- const previewSettings = JSON.parse(document.getElementById(mapId).textContent)
58
- const map = new U.Map(`${mapId}_target`, previewSettings)
59
- CACHE[mapId] = map
60
- } else {
61
- CACHE[mapId].invalidateSize()
62
- }
63
- })
64
- }
65
- })()
48
+ <script type="module">
49
+ {% autoescape off %}
50
+ import Umap from '{% static "umap/js/modules/umap.js" %}'
51
+ {% endautoescape %}
52
+ const CACHE = {}
53
+ for (const mapOpener of document.querySelectorAll("button.map-opener")) {
54
+ mapOpener.addEventListener('click', (event) => {
55
+ const button = event.target.closest('button')
56
+ button.nextElementSibling.showModal()
57
+ const mapId = button.dataset.mapId
58
+ if (!document.querySelector(`#${mapId}_target`).children.length) {
59
+ const previewSettings = JSON.parse(document.getElementById(mapId).textContent)
60
+ const map = new Umap(`${mapId}_target`, previewSettings)
61
+ CACHE[mapId] = map
62
+ } else {
63
+ CACHE[mapId].invalidateSize()
64
+ }
65
+ })
66
+ }
66
67
  </script>
67
68
  {% endblock bottom_js %}
umap/tests/base.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import copy
2
2
  import json
3
+ import uuid
4
+ from pathlib import Path
3
5
 
4
6
  import factory
5
7
  from django.contrib.auth import get_user_model
@@ -40,7 +42,7 @@ class LicenceFactory(factory.django.DjangoModelFactory):
40
42
 
41
43
  class TileLayerFactory(factory.django.DjangoModelFactory):
42
44
  name = "Test zoom layer"
43
- url_template = "http://{s}.test.org/{z}/{x}/{y}.png"
45
+ url_template = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
44
46
  attribution = "Test layer attribution"
45
47
 
46
48
  class Meta:
@@ -100,6 +102,7 @@ class MapFactory(factory.django.DjangoModelFactory):
100
102
 
101
103
  licence = factory.SubFactory(LicenceFactory)
102
104
  owner = factory.SubFactory(UserFactory)
105
+ share_status = Map.PUBLIC
103
106
 
104
107
  @classmethod
105
108
  def _adjust_kwargs(cls, **kwargs):
@@ -113,6 +116,7 @@ class MapFactory(factory.django.DjangoModelFactory):
113
116
 
114
117
 
115
118
  class DataLayerFactory(factory.django.DjangoModelFactory):
119
+ uuid = factory.LazyFunction(lambda: uuid.uuid4())
116
120
  map = factory.SubFactory(MapFactory)
117
121
  name = "test datalayer"
118
122
  description = "test description"
@@ -150,3 +154,9 @@ def login_required(response):
150
154
  redirect_url = reverse("login")
151
155
  assert j["login_required"] == redirect_url
152
156
  return True
157
+
158
+
159
+ def mock_tiles(route):
160
+ print("Intercepted route", route.request.url)
161
+ path = Path(__file__).parent / "fixtures/empty_tile.png"
162
+ route.fulfill(path=path)
Binary file
@@ -0,0 +1,19 @@
1
+ {
2
+ "crs": null,
3
+ "type": "FeatureCollection",
4
+ "features": [
5
+ {
6
+ "type": "Feature",
7
+ "geometry": {
8
+ "coordinates": [
9
+ -1.09314,
10
+ 50.791718
11
+ ],
12
+ "type": "Point"
13
+ },
14
+ "properties": {
15
+ "name": "Portsmouth"
16
+ }
17
+ }
18
+ ]
19
+ }
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import re
2
3
  import subprocess
3
4
  import time
4
5
  from pathlib import Path
@@ -6,6 +7,8 @@ from pathlib import Path
6
7
  import pytest
7
8
  from playwright.sync_api import expect
8
9
 
10
+ from ..base import mock_tiles
11
+
9
12
 
10
13
  @pytest.fixture(scope="session")
11
14
  def browser_context_args(browser_context_args):
@@ -23,7 +26,7 @@ def set_timeout(context):
23
26
  @pytest.fixture(autouse=True)
24
27
  def mock_osm_tiles(page):
25
28
  if not bool(os.environ.get("PWDEBUG", False)):
26
- page.route("*/**/osmfr/**", lambda route: route.fulfill())
29
+ page.route(re.compile(r".*tile\..*"), mock_tiles)
27
30
 
28
31
 
29
32
  @pytest.fixture
@@ -76,8 +76,6 @@ def test_owner_permissions_form(map, datalayer, live_server, owner_session):
76
76
  edit_permissions = owner_session.get_by_title("Update permissions and editors")
77
77
  expect(edit_permissions).to_be_visible()
78
78
  edit_permissions.click()
79
- select = owner_session.locator(".umap-field-share_status select")
80
- expect(select).to_be_hidden()
81
79
  owner_field = owner_session.locator(".umap-field-owner")
82
80
  expect(owner_field).to_be_hidden()
83
81
  editors_field = owner_session.locator(".umap-field-editors input")
@@ -92,8 +90,15 @@ def test_owner_permissions_form(map, datalayer, live_server, owner_session):
92
90
  ".datalayer-permissions select[name='edit_status'] option:checked"
93
91
  )
94
92
  expect(option).to_have_text("Inherit")
95
- # Those fields should not be present in anonymous maps
96
- expect(owner_session.locator(".umap-field-share_status select")).to_be_hidden()
93
+ expect(owner_session.locator(".umap-field-share_status select")).to_be_visible()
94
+ options = [
95
+ int(option.get_attribute("value"))
96
+ for option in owner_session.locator(
97
+ ".umap-field-share_status select option"
98
+ ).all()
99
+ ]
100
+ assert options == [Map.DRAFT, Map.PUBLIC]
101
+ # This field should not be present in anonymous maps
97
102
  expect(owner_session.locator(".umap-field-owner")).to_be_hidden()
98
103
 
99
104
 
@@ -135,15 +140,15 @@ def test_can_change_perms_after_create(tilelayer, live_server, page):
135
140
  page.get_by_title("Manage layers").click()
136
141
  page.get_by_title("Add a layer").click()
137
142
  page.locator("input[name=name]").fill("Layer 1")
138
- save = page.get_by_role("button", name="Save")
139
- expect(save).to_be_visible()
143
+ expect(
144
+ page.get_by_role("button", name="Visibility: Draft (private)")
145
+ ).to_be_visible()
146
+ expect(page.get_by_role("button", name="Save", exact=True)).to_be_hidden()
140
147
  with page.expect_response(re.compile(r".*/datalayer/create/.*")):
141
- save.click()
148
+ page.get_by_role("button", name="Save draft", exact=True).click()
142
149
  edit_permissions = page.get_by_title("Update permissions and editors")
143
150
  expect(edit_permissions).to_be_visible()
144
151
  edit_permissions.click()
145
- select = page.locator(".umap-field-share_status select")
146
- expect(select).to_be_hidden()
147
152
  owner_field = page.locator(".umap-field-owner")
148
153
  expect(owner_field).to_be_hidden()
149
154
  editors_field = page.locator(".umap-field-editors input")
@@ -157,6 +162,9 @@ def test_can_change_perms_after_create(tilelayer, live_server, page):
157
162
  )
158
163
  expect(option).to_have_text("Inherit")
159
164
  expect(page.get_by_label("Secret edit link:")).to_be_visible()
165
+ page.locator('select[name="share_status"]').select_option("1")
166
+ expect(page.get_by_role("button", name="Save draft", exact=True)).to_be_hidden()
167
+ expect(page.get_by_role("button", name="Save", exact=True)).to_be_visible()
160
168
 
161
169
 
162
170
  def test_alert_message_after_create(
@@ -232,7 +240,7 @@ def test_anonymous_owner_can_delete_the_map(anonymap, live_server, owner_session
232
240
  owner_session.get_by_role("button", name="Delete").click()
233
241
  with owner_session.expect_response(re.compile(r".*/update/delete/.*")):
234
242
  owner_session.get_by_role("button", name="OK").click()
235
- assert not Map.objects.count()
243
+ assert Map.objects.get(pk=anonymap.pk).share_status == Map.DELETED
236
244
 
237
245
 
238
246
  def test_non_owner_cannot_see_delete_button(anonymap, live_server, page):
@@ -15,7 +15,12 @@ DATALAYER_DATA = {
15
15
  "features": [
16
16
  {
17
17
  "type": "Feature",
18
- "properties": {"name": "one point in france", "foo": "point", "bar": "one"},
18
+ "properties": {
19
+ "name": "one point in france",
20
+ "foo": "point",
21
+ "bar": "one",
22
+ "label": "this is label one",
23
+ },
19
24
  "geometry": {"type": "Point", "coordinates": [3.339844, 46.920255]},
20
25
  },
21
26
  {
@@ -24,6 +29,7 @@ DATALAYER_DATA = {
24
29
  "name": "one polygon in greenland",
25
30
  "foo": "polygon",
26
31
  "bar": "two",
32
+ "label": "this is label two",
27
33
  },
28
34
  "geometry": {
29
35
  "type": "Polygon",
@@ -44,6 +50,7 @@ DATALAYER_DATA = {
44
50
  "name": "one line in new zeland",
45
51
  "foo": "line",
46
52
  "bar": "three",
53
+ "label": "this is label three",
47
54
  },
48
55
  "geometry": {
49
56
  "type": "LineString",
@@ -476,3 +483,11 @@ def test_main_toolbox_toggle_all_layers(live_server, map, page):
476
483
  # Should hidden again all layers
477
484
  expect(page.locator(".datalayer.off")).to_have_count(3)
478
485
  expect(markers).to_have_count(0)
486
+
487
+
488
+ def test_honour_the_label_fields_settings(live_server, map, page, bootstrap, settings):
489
+ settings.UMAP_LABEL_KEYS = ["label", "name"]
490
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
491
+ expect(page.locator(".panel").get_by_text("this is label one")).to_be_visible()
492
+ expect(page.locator(".panel").get_by_text("this is label two")).to_be_visible()
493
+ expect(page.locator(".panel").get_by_text("this is label three")).to_be_visible()
@@ -22,7 +22,7 @@ def test_owner_can_delete_map_after_confirmation(map, live_server, login):
22
22
  with page.expect_navigation():
23
23
  delete_button.click()
24
24
  assert dialog_shown
25
- assert Map.objects.all().count() == 0
25
+ assert Map.objects.get(pk=map.pk).share_status == Map.DELETED
26
26
 
27
27
 
28
28
  def test_dashboard_map_preview(map, live_server, datalayer, login):