umap-project 3.3.2__py3-none-any.whl → 3.4.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 (242) hide show
  1. umap/__init__.py +1 -1
  2. umap/context_processors.py +4 -1
  3. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/cs_CZ/LC_MESSAGES/django.po +43 -33
  5. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/da/LC_MESSAGES/django.po +43 -33
  7. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/de/LC_MESSAGES/django.po +64 -53
  9. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/el/LC_MESSAGES/django.po +35 -29
  11. umap/locale/en/LC_MESSAGES/django.po +47 -41
  12. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/es/LC_MESSAGES/django.po +43 -33
  14. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/et/LC_MESSAGES/django.po +58 -54
  16. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/eu/LC_MESSAGES/django.po +43 -33
  18. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/fa_IR/LC_MESSAGES/django.po +43 -33
  20. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/fr/LC_MESSAGES/django.po +36 -30
  22. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/gl/LC_MESSAGES/django.po +43 -33
  24. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/hu/LC_MESSAGES/django.po +35 -29
  26. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/is/LC_MESSAGES/django.po +43 -33
  28. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/it/LC_MESSAGES/django.po +43 -33
  30. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/nl/LC_MESSAGES/django.po +35 -29
  32. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/pl/LC_MESSAGES/django.po +114 -103
  34. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/pt/LC_MESSAGES/django.po +43 -33
  36. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/th_TH/LC_MESSAGES/django.po +310 -109
  38. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/zh_TW/LC_MESSAGES/django.po +80 -70
  40. umap/management/commands/switch_user.py +2 -2
  41. umap/migrations/0018_datalayer_uuid.py +1 -1
  42. umap/models.py +7 -3
  43. umap/settings/local.py.sample +1 -1
  44. umap/static/umap/base.css +89 -32
  45. umap/static/umap/content.css +129 -33
  46. umap/static/umap/css/bar.css +82 -20
  47. umap/static/umap/css/browser.css +163 -0
  48. umap/static/umap/css/contextmenu.css +15 -0
  49. umap/static/umap/css/dialog.css +36 -16
  50. umap/static/umap/css/form.css +123 -33
  51. umap/static/umap/css/icon.css +46 -3
  52. umap/static/umap/css/panel.css +7 -3
  53. umap/static/umap/css/popup.css +34 -8
  54. umap/static/umap/css/tooltip.css +8 -4
  55. umap/static/umap/img/16-white.svg +26 -8
  56. umap/static/umap/img/16.svg +1 -1
  57. umap/static/umap/img/source/16-white.svg +36 -18
  58. umap/static/umap/img/source/16.svg +1 -1
  59. umap/static/umap/js/components/alerts/alert.css +69 -31
  60. umap/static/umap/js/components/alerts/alert.js +20 -2
  61. umap/static/umap/js/components/base.js +1 -1
  62. umap/static/umap/js/modules/browser.js +69 -61
  63. umap/static/umap/js/modules/caption.js +10 -7
  64. umap/static/umap/js/modules/data/features.js +89 -63
  65. umap/static/umap/js/modules/data/fields.js +446 -0
  66. umap/static/umap/js/modules/data/layer.js +116 -196
  67. umap/static/umap/js/modules/domutils.js +109 -0
  68. umap/static/umap/js/modules/filters.js +780 -0
  69. umap/static/umap/js/modules/form/builder.js +8 -5
  70. umap/static/umap/js/modules/form/fields.js +111 -221
  71. umap/static/umap/js/modules/formatter.js +24 -1
  72. umap/static/umap/js/modules/help.js +4 -3
  73. umap/static/umap/js/modules/i18n.js +1 -1
  74. umap/static/umap/js/modules/importer.js +1 -1
  75. umap/static/umap/js/modules/importers/opendata.js +15 -0
  76. umap/static/umap/js/modules/importers/openrouteservice.js +6 -1
  77. umap/static/umap/js/modules/managers.js +2 -1
  78. umap/static/umap/js/modules/permissions.js +39 -31
  79. umap/static/umap/js/modules/rendering/controls.js +11 -9
  80. umap/static/umap/js/modules/rendering/icon.js +3 -8
  81. umap/static/umap/js/modules/rendering/layers/base.js +3 -3
  82. umap/static/umap/js/modules/rendering/layers/classified.js +18 -11
  83. umap/static/umap/js/modules/rendering/layers/cluster.js +23 -11
  84. umap/static/umap/js/modules/rendering/layers/heat.js +27 -21
  85. umap/static/umap/js/modules/rendering/map.js +1 -0
  86. umap/static/umap/js/modules/rendering/template.js +50 -23
  87. umap/static/umap/js/modules/rendering/ui.js +33 -25
  88. umap/static/umap/js/modules/rules.js +38 -44
  89. umap/static/umap/js/modules/schema.js +3 -6
  90. umap/static/umap/js/modules/share.js +5 -4
  91. umap/static/umap/js/modules/tableeditor.js +50 -38
  92. umap/static/umap/js/modules/templates.js +2 -3
  93. umap/static/umap/js/modules/ui/bar.js +55 -23
  94. umap/static/umap/js/modules/ui/dialog.js +38 -27
  95. umap/static/umap/js/modules/ui/panel.js +23 -8
  96. umap/static/umap/js/modules/ui/tooltip.js +6 -5
  97. umap/static/umap/js/modules/umap.js +158 -51
  98. umap/static/umap/js/modules/utils.js +24 -2
  99. umap/static/umap/js/umap.core.js +1 -110
  100. umap/static/umap/locale/am_ET.js +52 -17
  101. umap/static/umap/locale/am_ET.json +52 -17
  102. umap/static/umap/locale/ar.js +52 -17
  103. umap/static/umap/locale/ar.json +52 -17
  104. umap/static/umap/locale/ast.js +52 -17
  105. umap/static/umap/locale/ast.json +52 -17
  106. umap/static/umap/locale/bg.js +52 -17
  107. umap/static/umap/locale/bg.json +52 -17
  108. umap/static/umap/locale/br.js +48 -22
  109. umap/static/umap/locale/br.json +48 -22
  110. umap/static/umap/locale/ca.js +52 -17
  111. umap/static/umap/locale/ca.json +52 -17
  112. umap/static/umap/locale/cs_CZ.js +52 -17
  113. umap/static/umap/locale/cs_CZ.json +52 -17
  114. umap/static/umap/locale/da.js +54 -17
  115. umap/static/umap/locale/da.json +54 -17
  116. umap/static/umap/locale/de.js +102 -67
  117. umap/static/umap/locale/de.json +102 -67
  118. umap/static/umap/locale/el.js +52 -17
  119. umap/static/umap/locale/el.json +52 -17
  120. umap/static/umap/locale/en.js +54 -16
  121. umap/static/umap/locale/en.json +54 -16
  122. umap/static/umap/locale/en_US.json +52 -17
  123. umap/static/umap/locale/es.js +54 -17
  124. umap/static/umap/locale/es.json +54 -17
  125. umap/static/umap/locale/et.js +91 -56
  126. umap/static/umap/locale/et.json +91 -56
  127. umap/static/umap/locale/eu.js +84 -49
  128. umap/static/umap/locale/eu.json +84 -49
  129. umap/static/umap/locale/fa_IR.js +52 -17
  130. umap/static/umap/locale/fa_IR.json +52 -17
  131. umap/static/umap/locale/fi.js +52 -17
  132. umap/static/umap/locale/fi.json +52 -17
  133. umap/static/umap/locale/fr.js +54 -17
  134. umap/static/umap/locale/fr.json +54 -17
  135. umap/static/umap/locale/gl.js +52 -17
  136. umap/static/umap/locale/gl.json +52 -17
  137. umap/static/umap/locale/he.js +52 -17
  138. umap/static/umap/locale/he.json +52 -17
  139. umap/static/umap/locale/hr.js +52 -17
  140. umap/static/umap/locale/hr.json +52 -17
  141. umap/static/umap/locale/hu.js +59 -24
  142. umap/static/umap/locale/hu.json +59 -24
  143. umap/static/umap/locale/id.js +52 -17
  144. umap/static/umap/locale/id.json +52 -17
  145. umap/static/umap/locale/is.js +52 -17
  146. umap/static/umap/locale/is.json +52 -17
  147. umap/static/umap/locale/it.js +52 -17
  148. umap/static/umap/locale/it.json +52 -17
  149. umap/static/umap/locale/ja.js +52 -17
  150. umap/static/umap/locale/ja.json +52 -17
  151. umap/static/umap/locale/ko.js +52 -17
  152. umap/static/umap/locale/ko.json +52 -17
  153. umap/static/umap/locale/lt.js +52 -17
  154. umap/static/umap/locale/lt.json +52 -17
  155. umap/static/umap/locale/ms.js +52 -17
  156. umap/static/umap/locale/ms.json +52 -17
  157. umap/static/umap/locale/nl.js +52 -17
  158. umap/static/umap/locale/nl.json +52 -17
  159. umap/static/umap/locale/no.js +52 -17
  160. umap/static/umap/locale/no.json +52 -17
  161. umap/static/umap/locale/pl.js +53 -17
  162. umap/static/umap/locale/pl.json +53 -17
  163. umap/static/umap/locale/pl_PL.json +52 -17
  164. umap/static/umap/locale/pt.js +52 -17
  165. umap/static/umap/locale/pt.json +52 -17
  166. umap/static/umap/locale/pt_BR.js +52 -17
  167. umap/static/umap/locale/pt_BR.json +52 -17
  168. umap/static/umap/locale/pt_PT.js +52 -17
  169. umap/static/umap/locale/pt_PT.json +52 -17
  170. umap/static/umap/locale/ro.js +52 -17
  171. umap/static/umap/locale/ro.json +52 -17
  172. umap/static/umap/locale/ru.js +52 -17
  173. umap/static/umap/locale/ru.json +52 -17
  174. umap/static/umap/locale/si.js +1 -1
  175. umap/static/umap/locale/si.json +1 -1
  176. umap/static/umap/locale/sk_SK.js +52 -17
  177. umap/static/umap/locale/sk_SK.json +52 -17
  178. umap/static/umap/locale/sl.js +52 -17
  179. umap/static/umap/locale/sl.json +52 -17
  180. umap/static/umap/locale/sr.js +52 -17
  181. umap/static/umap/locale/sr.json +52 -17
  182. umap/static/umap/locale/sv.js +52 -17
  183. umap/static/umap/locale/sv.json +52 -17
  184. umap/static/umap/locale/th_TH.js +52 -17
  185. umap/static/umap/locale/th_TH.json +52 -17
  186. umap/static/umap/locale/tr.js +52 -17
  187. umap/static/umap/locale/tr.json +52 -17
  188. umap/static/umap/locale/uk_UA.js +52 -17
  189. umap/static/umap/locale/uk_UA.json +52 -17
  190. umap/static/umap/locale/vi.js +52 -17
  191. umap/static/umap/locale/vi.json +52 -17
  192. umap/static/umap/locale/vi_VN.json +52 -17
  193. umap/static/umap/locale/zh.js +52 -17
  194. umap/static/umap/locale/zh.json +52 -17
  195. umap/static/umap/locale/zh_CN.json +52 -17
  196. umap/static/umap/locale/zh_TW.Big5.json +52 -17
  197. umap/static/umap/locale/zh_TW.js +53 -17
  198. umap/static/umap/locale/zh_TW.json +53 -17
  199. umap/static/umap/map.css +63 -226
  200. umap/static/umap/unittests/utils.js +18 -0
  201. umap/static/umap/vars.css +23 -5
  202. umap/templates/umap/components/alerts/alert.html +32 -29
  203. umap/templates/umap/css.html +2 -1
  204. umap/templates/umap/login_popup_end.html +18 -9
  205. umap/templates/umap/user_map_table.html +7 -2
  206. umap/tests/integration/conftest.py +10 -6
  207. umap/tests/integration/test_anonymous_owned_map.py +90 -37
  208. umap/tests/integration/test_basics.py +25 -1
  209. umap/tests/integration/test_browser.py +37 -0
  210. umap/tests/integration/test_cluster.py +110 -0
  211. umap/tests/integration/test_conditional_rules.py +107 -52
  212. umap/tests/integration/test_datalayer.py +9 -16
  213. umap/tests/integration/test_draw_polygon.py +6 -0
  214. umap/tests/integration/test_draw_polyline.py +11 -0
  215. umap/tests/integration/test_edit_marker.py +12 -1
  216. umap/tests/integration/test_export_map.py +19 -0
  217. umap/tests/integration/test_fields.py +541 -0
  218. umap/tests/integration/test_filters.py +616 -0
  219. umap/tests/integration/test_iframe.py +1 -1
  220. umap/tests/integration/test_import.py +38 -42
  221. umap/tests/integration/test_map_preview.py +1 -1
  222. umap/tests/integration/test_picto.py +1 -1
  223. umap/tests/integration/test_popup.py +31 -0
  224. umap/tests/integration/test_remote_data.py +60 -4
  225. umap/tests/integration/test_save.py +1 -1
  226. umap/tests/integration/test_share.py +4 -4
  227. umap/tests/integration/test_tableeditor.py +31 -7
  228. umap/tests/integration/test_websocket_sync.py +71 -20
  229. umap/tests/test_dashboard.py +11 -1
  230. umap/tests/test_statics.py +2 -2
  231. umap/tests/test_utils.py +19 -2
  232. umap/tests/test_views.py +1 -1
  233. umap/urls.py +1 -0
  234. umap/utils.py +8 -1
  235. umap/views.py +5 -0
  236. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/METADATA +15 -15
  237. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/RECORD +240 -236
  238. umap/static/umap/js/modules/facets.js +0 -164
  239. umap/tests/integration/test_facets_browser.py +0 -279
  240. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/WHEEL +0 -0
  241. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/entry_points.txt +0 -0
  242. {umap_project-3.3.2.dist-info → umap_project-3.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -25,7 +25,9 @@ def setup_function():
25
25
 
26
26
 
27
27
  @pytest.mark.xdist_group(name="websockets")
28
- def test_websocket_connection_can_sync_markers(new_page, asgi_live_server, tilelayer):
28
+ def test_websocket_connection_can_sync_markers(
29
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
30
+ ):
29
31
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
30
32
  map.settings["properties"]["syncEnabled"] = True
31
33
  map.save()
@@ -34,8 +36,10 @@ def test_websocket_connection_can_sync_markers(new_page, asgi_live_server, tilel
34
36
  # Create two tabs
35
37
  peerA = new_page("Page A")
36
38
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
39
+ wait_for_loaded(peerA)
37
40
  peerB = new_page("Page B")
38
41
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
42
+ wait_for_loaded(peerB)
39
43
 
40
44
  a_marker_pane = peerA.locator(".leaflet-marker-pane > div")
41
45
  b_marker_pane = peerB.locator(".leaflet-marker-pane > div")
@@ -49,6 +53,7 @@ def test_websocket_connection_can_sync_markers(new_page, asgi_live_server, tilel
49
53
 
50
54
  a_map_el = peerA.locator("#map")
51
55
  a_map_el.click(position={"x": 220, "y": 220})
56
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
52
57
  expect(a_marker_pane).to_have_count(1)
53
58
  expect(b_marker_pane).to_have_count(1)
54
59
  # Peer B should not be in state dirty
@@ -91,17 +96,21 @@ def test_websocket_connection_can_sync_markers(new_page, asgi_live_server, tilel
91
96
 
92
97
 
93
98
  @pytest.mark.xdist_group(name="websockets")
94
- def test_websocket_connection_can_sync_polygons(context, asgi_live_server, tilelayer):
99
+ def test_websocket_connection_can_sync_polygons(
100
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
101
+ ):
95
102
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
96
103
  map.settings["properties"]["syncEnabled"] = True
97
104
  map.save()
98
105
  DataLayerFactory(map=map, data={})
99
106
 
100
107
  # Create two tabs
101
- peerA = context.new_page()
108
+ peerA = new_page("Page A")
102
109
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
103
- peerB = context.new_page()
110
+ wait_for_loaded(peerA)
111
+ peerB = new_page("Page B")
104
112
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
113
+ wait_for_loaded(peerB)
105
114
 
106
115
  b_map_el = peerB.locator("#map")
107
116
 
@@ -171,7 +180,7 @@ def test_websocket_connection_can_sync_polygons(context, asgi_live_server, tilel
171
180
 
172
181
  @pytest.mark.xdist_group(name="websockets")
173
182
  def test_websocket_connection_can_sync_map_properties(
174
- new_page, asgi_live_server, tilelayer
183
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
175
184
  ):
176
185
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
177
186
  map.settings["properties"]["syncEnabled"] = True
@@ -181,8 +190,10 @@ def test_websocket_connection_can_sync_map_properties(
181
190
  # Create two tabs
182
191
  peerA = new_page()
183
192
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
193
+ wait_for_loaded(peerA)
184
194
  peerB = new_page()
185
195
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
196
+ wait_for_loaded(peerB)
186
197
 
187
198
  # Name change is synced
188
199
  peerA.get_by_role("button", name="Edit map name and caption").click()
@@ -205,7 +216,7 @@ def test_websocket_connection_can_sync_map_properties(
205
216
 
206
217
  @pytest.mark.xdist_group(name="websockets")
207
218
  def test_websocket_connection_can_sync_datalayer_properties(
208
- new_page, asgi_live_server, tilelayer
219
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
209
220
  ):
210
221
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
211
222
  map.settings["properties"]["syncEnabled"] = True
@@ -215,8 +226,10 @@ def test_websocket_connection_can_sync_datalayer_properties(
215
226
  # Create two tabs
216
227
  peerA = new_page()
217
228
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
229
+ wait_for_loaded(peerA)
218
230
  peerB = new_page()
219
231
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
232
+ wait_for_loaded(peerB)
220
233
 
221
234
  # Layer addition, name and type are synced
222
235
  peerA.get_by_role("button", name="Manage layers").click()
@@ -227,14 +240,16 @@ def test_websocket_connection_can_sync_datalayer_properties(
227
240
  peerA.locator("body").press("Escape")
228
241
 
229
242
  peerB.get_by_role("button", name="Manage layers").click()
230
- peerB.locator(".panel.right").get_by_role("button", name="Edit").first.click()
243
+ peerB.locator(".panel.right").get_by_role(
244
+ "button", name="Edit", exact=True
245
+ ).first.click()
231
246
  expect(peerB.locator('input[name="name"]')).to_have_value("synced layer!")
232
247
  expect(peerB.get_by_role("combobox")).to_have_value("Choropleth")
233
248
 
234
249
 
235
250
  @pytest.mark.xdist_group(name="websockets")
236
251
  def test_websocket_connection_can_sync_cloned_polygons(
237
- context, asgi_live_server, tilelayer
252
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
238
253
  ):
239
254
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
240
255
  map.settings["properties"]["syncEnabled"] = True
@@ -242,10 +257,12 @@ def test_websocket_connection_can_sync_cloned_polygons(
242
257
  DataLayerFactory(map=map, data={})
243
258
 
244
259
  # Create two tabs
245
- peerA = context.new_page()
260
+ peerA = new_page("Page A")
246
261
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
247
- peerB = context.new_page()
262
+ wait_for_loaded(peerA)
263
+ peerB = new_page("Page B")
248
264
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
265
+ wait_for_loaded(peerB)
249
266
 
250
267
  b_map_el = peerB.locator("#map")
251
268
 
@@ -295,7 +312,7 @@ def test_websocket_connection_can_sync_cloned_polygons(
295
312
 
296
313
  @pytest.mark.xdist_group(name="websockets")
297
314
  def test_websocket_connection_can_sync_late_joining_peer(
298
- new_page, asgi_live_server, tilelayer
315
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
299
316
  ):
300
317
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
301
318
  map.settings["properties"]["syncEnabled"] = True
@@ -305,6 +322,7 @@ def test_websocket_connection_can_sync_late_joining_peer(
305
322
  # Create first peer (A) and have it join immediately
306
323
  peerA = new_page("Page A")
307
324
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
325
+ wait_for_loaded(peerA)
308
326
 
309
327
  # Add a marker from peer A
310
328
  a_create_marker = peerA.get_by_title("Draw a marker")
@@ -313,6 +331,7 @@ def test_websocket_connection_can_sync_late_joining_peer(
313
331
 
314
332
  a_map_el = peerA.locator("#map")
315
333
  a_map_el.click(position={"x": 220, "y": 220})
334
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
316
335
  peerA.locator("body").type("First marker")
317
336
  peerA.locator("body").press("Escape")
318
337
  peerA.wait_for_timeout(300)
@@ -331,6 +350,7 @@ def test_websocket_connection_can_sync_late_joining_peer(
331
350
  # Now create peer B and have it join
332
351
  peerB = new_page("Page B")
333
352
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
353
+ wait_for_loaded(peerB)
334
354
 
335
355
  # Check if peer B has received all the updates
336
356
  b_marker_pane = peerB.locator(".leaflet-marker-pane > div")
@@ -355,7 +375,7 @@ def test_websocket_connection_can_sync_late_joining_peer(
355
375
 
356
376
 
357
377
  @pytest.mark.xdist_group(name="websockets")
358
- def test_should_sync_datalayers(new_page, asgi_live_server, tilelayer):
378
+ def test_should_sync_datalayers(new_page, asgi_live_server, tilelayer, wait_for_loaded):
359
379
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
360
380
  map.settings["properties"]["syncEnabled"] = True
361
381
  map.save()
@@ -365,8 +385,10 @@ def test_should_sync_datalayers(new_page, asgi_live_server, tilelayer):
365
385
  # Create two tabs
366
386
  peerA = new_page("Page A")
367
387
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
388
+ wait_for_loaded(peerA)
368
389
  peerB = new_page("Page B")
369
390
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
391
+ wait_for_loaded(peerB)
370
392
 
371
393
  # Create a new layer from peerA
372
394
  peerA.get_by_role("button", name="Manage layers").click()
@@ -396,7 +418,7 @@ def test_should_sync_datalayers(new_page, asgi_live_server, tilelayer):
396
418
  peerA.locator("#map").click()
397
419
 
398
420
  # Make sure this new marker is in Layer 2 for peerB
399
- # Show features for this layer in the brower.
421
+ # Show features for this layer in the browser.
400
422
  peerB.locator("summary").filter(has_text="Layer 2").click()
401
423
  expect(peerB.locator("li").filter(has_text="Layer 2")).to_be_visible()
402
424
  peerB.locator(".panel.left").get_by_role("button", name="Show/hide layer").nth(
@@ -431,7 +453,9 @@ def test_should_sync_datalayers(new_page, asgi_live_server, tilelayer):
431
453
 
432
454
 
433
455
  @pytest.mark.xdist_group(name="websockets")
434
- def test_should_sync_datalayers_delete(new_page, asgi_live_server, tilelayer):
456
+ def test_should_sync_datalayers_delete(
457
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
458
+ ):
435
459
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
436
460
  map.settings["properties"]["syncEnabled"] = True
437
461
  map.save()
@@ -471,8 +495,10 @@ def test_should_sync_datalayers_delete(new_page, asgi_live_server, tilelayer):
471
495
  # Create two tabs
472
496
  peerA = new_page("Page A")
473
497
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
498
+ wait_for_loaded(peerA)
474
499
  peerB = new_page("Page B")
475
500
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
501
+ wait_for_loaded(peerB)
476
502
 
477
503
  peerA.get_by_role("button", name="Open browser").click()
478
504
  expect(peerA.locator(".panel").get_by_text("datalayer 1")).to_be_visible()
@@ -494,10 +520,13 @@ def test_should_sync_datalayers_delete(new_page, asgi_live_server, tilelayer):
494
520
 
495
521
 
496
522
  @pytest.mark.xdist_group(name="websockets")
497
- def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user):
523
+ def test_create_and_sync_map(
524
+ new_page, asgi_live_server, tilelayer, login, user, wait_for_loaded
525
+ ):
498
526
  # Create a syncable map with peerA
499
527
  peerA = login(user, prefix="Page A")
500
528
  peerA.goto(f"{asgi_live_server.url}/en/map/new/")
529
+ wait_for_loaded(peerA)
501
530
  peerA.get_by_role("button", name="Map advanced properties").click()
502
531
  expect(peerA.get_by_text("Real-time collaboration", exact=True)).to_be_hidden()
503
532
  with peerA.expect_response(re.compile("./map/create/.*")):
@@ -517,6 +546,7 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
517
546
  # Open map and go to edit mode with peer B
518
547
  peerB = new_page("Page B")
519
548
  peerB.goto(peerA.url)
549
+ wait_for_loaded(peerB)
520
550
  peerB.get_by_role("button", name="Edit").click()
521
551
 
522
552
  # Create a marker from peerA
@@ -527,8 +557,10 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
527
557
 
528
558
  # Add a marker from peer A
529
559
  peerA.get_by_role("button", name="Edit").click()
560
+ peerA.wait_for_timeout(300) # Time for the animation to finish
530
561
  peerA.get_by_title("Draw a marker").click()
531
562
  peerA.locator("#map").click(position={"x": 220, "y": 220})
563
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
532
564
  expect(markersA).to_have_count(1)
533
565
  expect(markersB).to_have_count(1)
534
566
 
@@ -554,6 +586,7 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
554
586
  # Add a marker from peer B
555
587
  peerB.get_by_title("Draw a marker").click()
556
588
  peerB.locator("#map").click(position={"x": 200, "y": 200})
589
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
557
590
  expect(markersB).to_have_count(2)
558
591
  expect(markersA).to_have_count(1)
559
592
  with peerB.expect_response(re.compile("./datalayer/update/.*")):
@@ -566,7 +599,9 @@ def test_create_and_sync_map(new_page, asgi_live_server, tilelayer, login, user)
566
599
 
567
600
 
568
601
  @pytest.mark.xdist_group(name="websockets")
569
- def test_saved_datalayer_are_not_duplicated(new_page, asgi_live_server, tilelayer):
602
+ def test_saved_datalayer_are_not_duplicated(
603
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
604
+ ):
570
605
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
571
606
  map.settings["properties"]["syncEnabled"] = True
572
607
  map.save()
@@ -574,6 +609,7 @@ def test_saved_datalayer_are_not_duplicated(new_page, asgi_live_server, tilelaye
574
609
  # Create one tab
575
610
  peerA = new_page("Page A")
576
611
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
612
+ wait_for_loaded(peerA)
577
613
  # Create a new datalayer
578
614
  peerA.get_by_title("Manage layers").click()
579
615
  peerA.get_by_title("Add a layer").click()
@@ -586,6 +622,7 @@ def test_saved_datalayer_are_not_duplicated(new_page, asgi_live_server, tilelaye
586
622
  # Now load the map from another tab
587
623
  peerB = new_page("Page B")
588
624
  peerB.goto(peerA.url)
625
+ wait_for_loaded(peerB)
589
626
  peerB.get_by_role("button", name="Open browser").click()
590
627
  expect(peerB.get_by_text("Layer 1")).to_be_visible()
591
628
  peerB.get_by_role("button", name="Edit").click()
@@ -594,7 +631,9 @@ def test_saved_datalayer_are_not_duplicated(new_page, asgi_live_server, tilelaye
594
631
 
595
632
 
596
633
  @pytest.mark.xdist_group(name="websockets")
597
- def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
634
+ def test_should_sync_saved_status(
635
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
636
+ ):
598
637
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
599
638
  map.settings["properties"]["syncEnabled"] = True
600
639
  map.save()
@@ -602,8 +641,10 @@ def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
602
641
  # Create two tabs
603
642
  peerA = new_page("Page A")
604
643
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
644
+ wait_for_loaded(peerA)
605
645
  peerB = new_page("Page B")
606
646
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
647
+ wait_for_loaded(peerB)
607
648
 
608
649
  # Create a new marker from peerA
609
650
  peerA.get_by_title("Draw a marker").click()
@@ -637,7 +678,9 @@ def test_should_sync_saved_status(new_page, asgi_live_server, tilelayer):
637
678
 
638
679
 
639
680
  @pytest.mark.xdist_group(name="websockets")
640
- def test_should_sync_line_on_escape(new_page, asgi_live_server, tilelayer):
681
+ def test_should_sync_line_on_escape(
682
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
683
+ ):
641
684
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
642
685
  map.settings["properties"]["syncEnabled"] = True
643
686
  map.save()
@@ -645,8 +688,10 @@ def test_should_sync_line_on_escape(new_page, asgi_live_server, tilelayer):
645
688
  # Create two tabs
646
689
  peerA = new_page("Page A")
647
690
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
691
+ wait_for_loaded(peerA)
648
692
  peerB = new_page("Page B")
649
693
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
694
+ wait_for_loaded(peerB)
650
695
 
651
696
  # Create a new marker from peerA
652
697
  peerA.get_by_title("Draw a polyline").click()
@@ -660,7 +705,7 @@ def test_should_sync_line_on_escape(new_page, asgi_live_server, tilelayer):
660
705
 
661
706
  @pytest.mark.xdist_group(name="websockets")
662
707
  def test_should_sync_datalayer_clear(
663
- new_page, asgi_live_server, tilelayer, map, datalayer
708
+ new_page, asgi_live_server, tilelayer, map, datalayer, wait_for_loaded
664
709
  ):
665
710
  map.settings["properties"]["syncEnabled"] = True
666
711
  map.edit_status = Map.ANONYMOUS
@@ -669,8 +714,10 @@ def test_should_sync_datalayer_clear(
669
714
  # Create two tabs
670
715
  peerA = new_page("Page A")
671
716
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
717
+ wait_for_loaded(peerA)
672
718
  peerB = new_page("Page B")
673
719
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
720
+ wait_for_loaded(peerB)
674
721
  expect(peerA.locator(".leaflet-marker-icon")).to_have_count(1)
675
722
  expect(peerB.locator(".leaflet-marker-icon")).to_have_count(1)
676
723
 
@@ -689,7 +736,9 @@ def test_should_sync_datalayer_clear(
689
736
 
690
737
 
691
738
  @pytest.mark.xdist_group(name="websockets")
692
- def test_should_save_remote_dirty_datalayers(new_page, asgi_live_server, tilelayer):
739
+ def test_should_save_remote_dirty_datalayers(
740
+ new_page, asgi_live_server, tilelayer, wait_for_loaded
741
+ ):
693
742
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
694
743
  map.settings["properties"]["syncEnabled"] = True
695
744
  map.save()
@@ -699,8 +748,10 @@ def test_should_save_remote_dirty_datalayers(new_page, asgi_live_server, tilelay
699
748
  # Create two tabs
700
749
  peerA = new_page("Page A")
701
750
  peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
751
+ wait_for_loaded(peerA)
702
752
  peerB = new_page("Page B")
703
753
  peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
754
+ wait_for_loaded(peerB)
704
755
 
705
756
  # Create a new layer from peerA
706
757
  peerA.get_by_role("button", name="Manage layers").click()
@@ -68,7 +68,7 @@ def test_user_dashboard_display_user_team_maps(client, map, team, user, share_st
68
68
 
69
69
  def test_user_dashboard_display_user_maps_distinct(client, map):
70
70
  # cf https://github.com/umap-project/umap/issues/1325
71
- anonymap = MapFactory(name="Map witout owner should not appear")
71
+ anonymap = MapFactory(name="Map without owner should not appear")
72
72
  user1 = UserFactory(username="user1")
73
73
  user2 = UserFactory(username="user2")
74
74
  map.editors.add(user1)
@@ -99,3 +99,13 @@ def test_user_dashboard_search_empty(client, map):
99
99
  body = response.content.decode()
100
100
  assert map.name not in body
101
101
  assert "No map found." in body
102
+
103
+
104
+ def test_user_dashboard_filter_by_tag(client, map):
105
+ new_map = MapFactory(name="A map about bicycle", owner=map.owner, tags=["cycling"])
106
+ client.login(username=map.owner.username, password="123123")
107
+ response = client.get(f"{reverse('user_dashboard')}?tags=cycling")
108
+ assert response.status_code == 200
109
+ body = response.content.decode()
110
+ assert map.name not in body
111
+ assert new_map.name in body
@@ -32,9 +32,9 @@ def test_collectstatic_ran_successfully_with_hashes(settings, staticfiles):
32
32
  assert "hash" in json_manifest.keys()
33
33
  assert "umap/base.css" in json_manifest["paths"]
34
34
  # Hash + the dot ("umap/base.<hash>.css").
35
- md5_hash_lenght = 12 + 1
35
+ md5_hash_length = 12 + 1
36
36
  # The value of the manifest must contain the hash (length).
37
37
  assert (
38
38
  len(json_manifest["paths"]["umap/base.css"])
39
- == len("umap/base.css") + md5_hash_lenght
39
+ == len("umap/base.css") + md5_hash_length
40
40
  )
umap/tests/test_utils.py CHANGED
@@ -1,9 +1,13 @@
1
+ import stat
1
2
  from pathlib import Path
2
3
 
3
- from umap.utils import gzip_file
4
+ import pytest
4
5
 
6
+ from umap.utils import gzip_file, normalize_string
5
7
 
6
- def test_gzip_file():
8
+
9
+ def test_gzip_file(settings):
10
+ settings.FILE_UPLOAD_PERMISSIONS = 0o666
7
11
  # Let's use any old file so we can check that the date of the gzip file is set.
8
12
  src = Path(__file__).parent / "settings.py"
9
13
  dest = Path("/tmp/test_settings.py.gz")
@@ -12,3 +16,16 @@ def test_gzip_file():
12
16
  dest_stat = dest.stat()
13
17
  dest.unlink()
14
18
  assert src_stat.st_mtime == dest_stat.st_mtime
19
+ assert stat.filemode(dest_stat.st_mode) == "-rw-rw-rw-"
20
+
21
+
22
+ @pytest.mark.parametrize(
23
+ "input,output",
24
+ (
25
+ ("Vélo", "velo"),
26
+ ("Éducation", "education"),
27
+ ("stävänger", "stavanger"),
28
+ ),
29
+ )
30
+ def test_normalize_string(input, output):
31
+ assert normalize_string(input) == output
umap/tests/test_views.py CHANGED
@@ -73,7 +73,7 @@ def test_POST_raises():
73
73
  validate_url(request)
74
74
 
75
75
 
76
- def test_unkown_domain_raises():
76
+ def test_unknown_domain_raises():
77
77
  request = get("http://xlkjdkjsdlkjfd.com")
78
78
  with pytest.raises(AssertionError):
79
79
  validate_url(request)
umap/urls.py CHANGED
@@ -124,6 +124,7 @@ i18n_urls += decorated_patterns(
124
124
  path("me/teams", views.UserTeams.as_view(), name="user_teams"),
125
125
  path("me/templates", views.UserTemplates.as_view(), name="user_templates"),
126
126
  path("team/create/", views.TeamNew.as_view(), name="team_new"),
127
+ path("whoami", views.WhoAmI.as_view(), name="whoami"),
127
128
  )
128
129
 
129
130
  if settings.UMAP_ALLOW_EDIT_PROFILE:
umap/utils.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import gzip
2
2
  import json
3
3
  import os
4
+ import unicodedata
4
5
  from pathlib import Path
5
6
 
6
7
  from django.conf import settings
@@ -79,7 +80,7 @@ def get_uri_template(urlname, args=None, prefix="", module=None):
79
80
  result, params = possibility[0]
80
81
  return _convert(result, params)
81
82
  else:
82
- # If there are optionnal arguments passed, use them to try to find
83
+ # If there are optional arguments passed, use them to try to find
83
84
  # the correct pattern.
84
85
  # First, we need to build a list with all the arguments
85
86
  seen_params = []
@@ -149,6 +150,7 @@ def gzip_file(from_path, to_path):
149
150
  with gzip.open(to_path, "wb") as f_out:
150
151
  f_out.writelines(f_in)
151
152
  os.utime(to_path, ns=(stat.st_mtime_ns, stat.st_mtime_ns))
153
+ os.chmod(to_path, settings.FILE_UPLOAD_PERMISSIONS)
152
154
 
153
155
 
154
156
  def is_ajax(request):
@@ -222,3 +224,8 @@ def collect_pictograms():
222
224
  }
223
225
 
224
226
  return pictograms
227
+
228
+
229
+ def normalize_string(s):
230
+ n = unicodedata.normalize("NFKD", str(s))
231
+ return "".join([c for c in n if not unicodedata.combining(c)]).lower()
umap/views.py CHANGED
@@ -585,6 +585,11 @@ class SessionMixin:
585
585
  }
586
586
 
587
587
 
588
+ class WhoAmI(SessionMixin, View):
589
+ def get(self, request, *args, **kwargs):
590
+ return simple_json_response(user=self.get_user_data())
591
+
592
+
588
593
  class FormLessEditMixin:
589
594
  http_method_names = [
590
595
  "post",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: umap-project
3
- Version: 3.3.2
3
+ Version: 3.4.0
4
4
  Summary: Create maps with OpenStreetMap layers in a minute and embed them in your site.
5
5
  Author-email: Yohan Boniface <yb@enix.org>
6
6
  Maintainer-email: David Larlet <david@larlet.fr>
@@ -19,42 +19,42 @@ Requires-Python: >=3.10
19
19
  Requires-Dist: django-agnocomplete==2.2.0
20
20
  Requires-Dist: django-environ==0.12.0
21
21
  Requires-Dist: django-probes==1.7.0
22
- Requires-Dist: django==5.2.5
23
- Requires-Dist: pillow==11.3.0
24
- Requires-Dist: psycopg==3.2.9
22
+ Requires-Dist: django==5.2.7
23
+ Requires-Dist: pillow==12.0.0
24
+ Requires-Dist: psycopg==3.2.12
25
25
  Requires-Dist: rcssmin==1.2.1
26
26
  Requires-Dist: requests==2.32.5
27
- Requires-Dist: rjsmin==1.2.4
27
+ Requires-Dist: rjsmin==1.2.5
28
28
  Requires-Dist: social-auth-app-django==5.4.3
29
29
  Requires-Dist: social-auth-core==4.5.6
30
30
  Provides-Extra: dev
31
31
  Requires-Dist: djlint==1.36.4; extra == 'dev'
32
- Requires-Dist: hatch==1.14.1; extra == 'dev'
33
- Requires-Dist: isort==6.0.1; extra == 'dev'
34
- Requires-Dist: mkdocs-material==9.6.18; extra == 'dev'
32
+ Requires-Dist: hatch==1.14.2; extra == 'dev'
33
+ Requires-Dist: isort==7.0.0; extra == 'dev'
34
+ Requires-Dist: mkdocs-material==9.6.23; extra == 'dev'
35
35
  Requires-Dist: mkdocs-static-i18n==1.3.0; extra == 'dev'
36
36
  Requires-Dist: mkdocs==1.6.1; extra == 'dev'
37
37
  Requires-Dist: pymdown-extensions==10.16.1; extra == 'dev'
38
- Requires-Dist: ruff==0.12.10; extra == 'dev'
38
+ Requires-Dist: ruff==0.14.0; extra == 'dev'
39
39
  Requires-Dist: vermin==1.6.0; extra == 'dev'
40
40
  Provides-Extra: docker
41
- Requires-Dist: uvicorn==0.35.0; extra == 'docker'
41
+ Requires-Dist: uvicorn==0.38.0; extra == 'docker'
42
42
  Provides-Extra: s3
43
43
  Requires-Dist: django-storages[s3]==1.14.6; extra == 's3'
44
44
  Provides-Extra: sync
45
- Requires-Dist: pydantic==2.11.7; extra == 'sync'
45
+ Requires-Dist: pydantic==2.12.3; extra == 'sync'
46
46
  Requires-Dist: redis==6.4.0; extra == 'sync'
47
47
  Requires-Dist: websockets==15.0.1; extra == 'sync'
48
48
  Provides-Extra: test
49
49
  Requires-Dist: daphne==4.2.1; extra == 'test'
50
50
  Requires-Dist: factory-boy==3.3.3; extra == 'test'
51
- Requires-Dist: moto[s3]==5.1.10; extra == 'test'
51
+ Requires-Dist: moto[s3]==5.1.14; extra == 'test'
52
52
  Requires-Dist: playwright>=1.39; extra == 'test'
53
53
  Requires-Dist: pytest-django==4.11.1; extra == 'test'
54
- Requires-Dist: pytest-playwright==0.7.0; extra == 'test'
55
- Requires-Dist: pytest-rerunfailures==15.1; extra == 'test'
54
+ Requires-Dist: pytest-playwright==0.7.1; extra == 'test'
55
+ Requires-Dist: pytest-rerunfailures==16.1; extra == 'test'
56
56
  Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
57
- Requires-Dist: pytest==8.4.1; extra == 'test'
57
+ Requires-Dist: pytest==8.4.2; extra == 'test'
58
58
  Description-Content-Type: text/markdown
59
59
 
60
60
  [![Matrix](https://img.shields.io/matrix/umap:matrix.org?server_fqdn=matrix.org&logo=matrix)](https://matrix.to/#/#umap:matrix.org)