umap-project 3.1.2__py3-none-any.whl → 3.3.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.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (199) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/en/LC_MESSAGES/django.po +22 -18
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +21 -17
  6. umap/management/commands/export_pictogram.py +29 -0
  7. umap/management/commands/migrate_to_S3.py +5 -1
  8. umap/management/commands/purge_old_versions.py +8 -6
  9. umap/settings/__init__.py +21 -0
  10. umap/settings/base.py +3 -0
  11. umap/static/umap/content.css +7 -2
  12. umap/static/umap/css/contextmenu.css +58 -2
  13. umap/static/umap/css/form.css +175 -45
  14. umap/static/umap/css/icon.css +97 -3
  15. umap/static/umap/css/panel.css +31 -1
  16. umap/static/umap/img/16-white.svg +21 -40
  17. umap/static/umap/img/16.svg +1 -1
  18. umap/static/umap/img/24-white.svg +9 -9
  19. umap/static/umap/img/24.svg +23 -10
  20. umap/static/umap/img/source/16-white.svg +23 -41
  21. umap/static/umap/img/source/16.svg +1 -1
  22. umap/static/umap/img/source/24-white.svg +11 -11
  23. umap/static/umap/img/source/24.svg +25 -12
  24. umap/static/umap/js/modules/browser.js +1 -1
  25. umap/static/umap/js/modules/caption.js +8 -0
  26. umap/static/umap/js/modules/data/features.js +331 -202
  27. umap/static/umap/js/modules/data/layer.js +263 -152
  28. umap/static/umap/js/modules/facets.js +2 -2
  29. umap/static/umap/js/modules/form/builder.js +11 -7
  30. umap/static/umap/js/modules/form/fields.js +66 -26
  31. umap/static/umap/js/modules/formatter.js +78 -28
  32. umap/static/umap/js/modules/importer.js +6 -1
  33. umap/static/umap/js/modules/importers/opendata.js +138 -33
  34. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  35. umap/static/umap/js/modules/managers.js +67 -0
  36. umap/static/umap/js/modules/printer.js +107 -0
  37. umap/static/umap/js/modules/rendering/controls.js +78 -2
  38. umap/static/umap/js/modules/rendering/icon.js +116 -87
  39. umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
  40. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  41. umap/static/umap/js/modules/rendering/map.js +6 -2
  42. umap/static/umap/js/modules/rendering/template.js +71 -1
  43. umap/static/umap/js/modules/rendering/ui.js +111 -34
  44. umap/static/umap/js/modules/rules.js +76 -23
  45. umap/static/umap/js/modules/schema.js +27 -0
  46. umap/static/umap/js/modules/share.js +19 -12
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/updaters.js +1 -6
  49. umap/static/umap/js/modules/tableeditor.js +13 -37
  50. umap/static/umap/js/modules/templates.js +7 -6
  51. umap/static/umap/js/modules/ui/bar.js +6 -1
  52. umap/static/umap/js/modules/ui/base.js +24 -9
  53. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  54. umap/static/umap/js/modules/ui/dialog.js +7 -4
  55. umap/static/umap/js/modules/ui/panel.js +7 -0
  56. umap/static/umap/js/modules/umap.js +84 -67
  57. umap/static/umap/js/modules/utils.js +8 -7
  58. umap/static/umap/js/umap.controls.js +22 -57
  59. umap/static/umap/locale/am_ET.js +81 -9
  60. umap/static/umap/locale/am_ET.json +81 -9
  61. umap/static/umap/locale/ar.js +81 -9
  62. umap/static/umap/locale/ar.json +81 -9
  63. umap/static/umap/locale/ast.js +81 -9
  64. umap/static/umap/locale/ast.json +81 -9
  65. umap/static/umap/locale/bg.js +81 -9
  66. umap/static/umap/locale/bg.json +81 -9
  67. umap/static/umap/locale/br.js +68 -29
  68. umap/static/umap/locale/br.json +68 -29
  69. umap/static/umap/locale/ca.js +88 -16
  70. umap/static/umap/locale/ca.json +88 -16
  71. umap/static/umap/locale/cs_CZ.js +81 -9
  72. umap/static/umap/locale/cs_CZ.json +81 -9
  73. umap/static/umap/locale/da.js +48 -9
  74. umap/static/umap/locale/da.json +48 -9
  75. umap/static/umap/locale/de.js +48 -9
  76. umap/static/umap/locale/de.json +48 -9
  77. umap/static/umap/locale/el.js +58 -13
  78. umap/static/umap/locale/el.json +58 -13
  79. umap/static/umap/locale/en.js +48 -9
  80. umap/static/umap/locale/en.json +48 -9
  81. umap/static/umap/locale/en_US.json +81 -9
  82. umap/static/umap/locale/es.js +48 -9
  83. umap/static/umap/locale/es.json +48 -9
  84. umap/static/umap/locale/et.js +81 -9
  85. umap/static/umap/locale/et.json +81 -9
  86. umap/static/umap/locale/eu.js +97 -25
  87. umap/static/umap/locale/eu.json +97 -25
  88. umap/static/umap/locale/fa_IR.js +81 -9
  89. umap/static/umap/locale/fa_IR.json +81 -9
  90. umap/static/umap/locale/fi.js +81 -9
  91. umap/static/umap/locale/fi.json +81 -9
  92. umap/static/umap/locale/fr.js +48 -9
  93. umap/static/umap/locale/fr.json +48 -9
  94. umap/static/umap/locale/gl.js +81 -9
  95. umap/static/umap/locale/gl.json +81 -9
  96. umap/static/umap/locale/he.js +81 -9
  97. umap/static/umap/locale/he.json +81 -9
  98. umap/static/umap/locale/hr.js +81 -9
  99. umap/static/umap/locale/hr.json +81 -9
  100. umap/static/umap/locale/hu.js +72 -27
  101. umap/static/umap/locale/hu.json +72 -27
  102. umap/static/umap/locale/id.js +81 -9
  103. umap/static/umap/locale/id.json +81 -9
  104. umap/static/umap/locale/is.js +81 -9
  105. umap/static/umap/locale/is.json +81 -9
  106. umap/static/umap/locale/it.js +48 -9
  107. umap/static/umap/locale/it.json +48 -9
  108. umap/static/umap/locale/ja.js +81 -9
  109. umap/static/umap/locale/ja.json +81 -9
  110. umap/static/umap/locale/ko.js +81 -9
  111. umap/static/umap/locale/ko.json +81 -9
  112. umap/static/umap/locale/lt.js +81 -9
  113. umap/static/umap/locale/lt.json +81 -9
  114. umap/static/umap/locale/ms.js +81 -9
  115. umap/static/umap/locale/ms.json +81 -9
  116. umap/static/umap/locale/nl.js +48 -9
  117. umap/static/umap/locale/nl.json +48 -9
  118. umap/static/umap/locale/no.js +81 -9
  119. umap/static/umap/locale/no.json +81 -9
  120. umap/static/umap/locale/pl.js +81 -9
  121. umap/static/umap/locale/pl.json +81 -9
  122. umap/static/umap/locale/pl_PL.json +81 -9
  123. umap/static/umap/locale/pt.js +81 -9
  124. umap/static/umap/locale/pt.json +81 -9
  125. umap/static/umap/locale/pt_BR.js +91 -19
  126. umap/static/umap/locale/pt_BR.json +91 -19
  127. umap/static/umap/locale/pt_PT.js +81 -9
  128. umap/static/umap/locale/pt_PT.json +81 -9
  129. umap/static/umap/locale/ro.js +81 -9
  130. umap/static/umap/locale/ro.json +81 -9
  131. umap/static/umap/locale/ru.js +81 -9
  132. umap/static/umap/locale/ru.json +81 -9
  133. umap/static/umap/locale/sk_SK.js +81 -9
  134. umap/static/umap/locale/sk_SK.json +81 -9
  135. umap/static/umap/locale/sl.js +81 -9
  136. umap/static/umap/locale/sl.json +81 -9
  137. umap/static/umap/locale/sr.js +81 -9
  138. umap/static/umap/locale/sr.json +81 -9
  139. umap/static/umap/locale/sv.js +81 -9
  140. umap/static/umap/locale/sv.json +81 -9
  141. umap/static/umap/locale/th_TH.js +81 -9
  142. umap/static/umap/locale/th_TH.json +81 -9
  143. umap/static/umap/locale/tr.js +81 -9
  144. umap/static/umap/locale/tr.json +81 -9
  145. umap/static/umap/locale/uk_UA.js +81 -9
  146. umap/static/umap/locale/uk_UA.json +81 -9
  147. umap/static/umap/locale/vi.js +81 -9
  148. umap/static/umap/locale/vi.json +81 -9
  149. umap/static/umap/locale/vi_VN.json +81 -9
  150. umap/static/umap/locale/zh.js +81 -9
  151. umap/static/umap/locale/zh.json +81 -9
  152. umap/static/umap/locale/zh_CN.json +81 -9
  153. umap/static/umap/locale/zh_TW.Big5.json +81 -9
  154. umap/static/umap/locale/zh_TW.js +98 -26
  155. umap/static/umap/locale/zh_TW.json +98 -26
  156. umap/static/umap/map.css +325 -102
  157. umap/static/umap/vars.css +1 -0
  158. umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
  159. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  160. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  161. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  162. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  163. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  164. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  165. umap/storage/fs.py +3 -2
  166. umap/storage/staticfiles.py +12 -0
  167. umap/templates/base.html +4 -1
  168. umap/templates/umap/css.html +0 -4
  169. umap/templates/umap/js.html +1 -3
  170. umap/tests/base.py +9 -1
  171. umap/tests/integration/test_basics.py +3 -1
  172. umap/tests/integration/test_conditional_rules.py +79 -37
  173. umap/tests/integration/test_datalayer.py +1 -1
  174. umap/tests/integration/test_draw_polygon.py +3 -5
  175. umap/tests/integration/test_draw_polyline.py +4 -6
  176. umap/tests/integration/test_draw_route.py +178 -0
  177. umap/tests/integration/test_edit_datalayer.py +1 -1
  178. umap/tests/integration/test_edit_map.py +1 -1
  179. umap/tests/integration/test_edit_marker.py +8 -8
  180. umap/tests/integration/test_edit_polygon.py +2 -2
  181. umap/tests/integration/test_export_map.py +84 -10
  182. umap/tests/integration/test_import.py +140 -0
  183. umap/tests/integration/test_map_preview.py +1 -1
  184. umap/tests/integration/test_optimistic_merge.py +72 -12
  185. umap/tests/integration/test_share.py +1 -1
  186. umap/tests/integration/test_tableeditor.py +10 -7
  187. umap/tests/integration/test_websocket_sync.py +4 -4
  188. umap/utils.py +37 -0
  189. umap/views.py +18 -2
  190. umap_project-3.3.0.dist-info/METADATA +76 -0
  191. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/RECORD +194 -188
  192. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  193. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  194. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  195. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  196. umap_project-3.1.2.dist-info/METADATA +0 -68
  197. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  198. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,13 +12,13 @@ from ..base import DataLayerFactory, MapFactory
12
12
  DATALAYER_UPDATE = re.compile(r".*/datalayer/update/.*")
13
13
 
14
14
 
15
- def test_created_markers_are_merged(context, live_server, tilelayer):
15
+ def test_created_markers_are_merged(new_page, live_server, tilelayer):
16
16
  # Let's create a new map with an empty datalayer
17
17
  map = MapFactory(name="server-side merge")
18
18
  datalayer = DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
19
19
 
20
20
  # Now navigate to this map and create marker
21
- page_one = context.new_page()
21
+ page_one = new_page("page 1")
22
22
  page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
23
23
 
24
24
  save_p1 = page_one.get_by_role("button", name="Save")
@@ -52,10 +52,20 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
52
52
  "id": str(datalayer.pk),
53
53
  "rank": 0,
54
54
  "remoteData": {},
55
+ "fields": [
56
+ {
57
+ "key": "name",
58
+ "type": "String",
59
+ },
60
+ {
61
+ "key": "description",
62
+ "type": "Text",
63
+ },
64
+ ],
55
65
  }
56
66
 
57
67
  # Now navigate to this map from another tab
58
- page_two = context.new_page()
68
+ page_two = new_page("page 2")
59
69
 
60
70
  page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
61
71
 
@@ -91,6 +101,16 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
91
101
  "id": str(datalayer.pk),
92
102
  "rank": 0,
93
103
  "remoteData": {},
104
+ "fields": [
105
+ {
106
+ "key": "name",
107
+ "type": "String",
108
+ },
109
+ {
110
+ "key": "description",
111
+ "type": "Text",
112
+ },
113
+ ],
94
114
  }
95
115
 
96
116
  # Now create another marker in the first tab
@@ -111,6 +131,16 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
111
131
  "id": str(datalayer.pk),
112
132
  "rank": 0,
113
133
  "remoteData": {},
134
+ "fields": [
135
+ {
136
+ "key": "name",
137
+ "type": "String",
138
+ },
139
+ {
140
+ "key": "description",
141
+ "type": "Text",
142
+ },
143
+ ],
114
144
  }
115
145
 
116
146
  # And again
@@ -131,6 +161,16 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
131
161
  "id": str(datalayer.pk),
132
162
  "rank": 0,
133
163
  "remoteData": {},
164
+ "fields": [
165
+ {
166
+ "key": "name",
167
+ "type": "String",
168
+ },
169
+ {
170
+ "key": "description",
171
+ "type": "Text",
172
+ },
173
+ ],
134
174
  }
135
175
  expect(marker_pane_p1).to_have_count(4)
136
176
 
@@ -153,20 +193,30 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
153
193
  "id": str(datalayer.pk),
154
194
  "rank": 0,
155
195
  "remoteData": {},
196
+ "fields": [
197
+ {
198
+ "key": "name",
199
+ "type": "String",
200
+ },
201
+ {
202
+ "key": "description",
203
+ "type": "Text",
204
+ },
205
+ ],
156
206
  }
157
207
  expect(marker_pane_p2).to_have_count(5)
158
208
 
159
209
 
160
- def test_empty_datalayers_can_be_merged(context, live_server, tilelayer):
210
+ def test_empty_datalayers_can_be_merged(new_page, live_server, tilelayer):
161
211
  # Let's create a new map with an empty datalayer
162
212
  map = MapFactory(name="server-side merge")
163
213
  DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
164
214
 
165
215
  # Open two tabs at the same time, on the same empty map
166
- page_one = context.new_page()
216
+ page_one = new_page("page 1")
167
217
  page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
168
218
 
169
- page_two = context.new_page()
219
+ page_two = new_page("page 2")
170
220
  page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
171
221
 
172
222
  save_p1 = page_one.get_by_role("button", name="Save")
@@ -213,15 +263,15 @@ def test_empty_datalayers_can_be_merged(context, live_server, tilelayer):
213
263
  expect(marker_pane_p2).to_have_count(2)
214
264
 
215
265
 
216
- def test_same_second_edit_doesnt_conflict(context, live_server, tilelayer):
266
+ def test_same_second_edit_doesnt_conflict(new_page, live_server, tilelayer):
217
267
  # Let's create a new map with an empty datalayer
218
268
  map = MapFactory(name="server-side merge")
219
269
  datalayer = DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
220
270
 
221
271
  # Open the created map on two pages.
222
- page_one = context.new_page()
272
+ page_one = new_page("page 1")
223
273
  page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
224
- page_two = context.new_page()
274
+ page_two = new_page("page 2")
225
275
  page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
226
276
 
227
277
  save_p1 = page_one.get_by_role("button", name="Save")
@@ -280,14 +330,24 @@ def test_same_second_edit_doesnt_conflict(context, live_server, tilelayer):
280
330
  "id": str(datalayer.pk),
281
331
  "rank": 0,
282
332
  "remoteData": {},
333
+ "fields": [
334
+ {
335
+ "key": "name",
336
+ "type": "String",
337
+ },
338
+ {
339
+ "key": "description",
340
+ "type": "Text",
341
+ },
342
+ ],
283
343
  }
284
344
 
285
345
 
286
- def test_should_display_alert_on_conflict(context, live_server, datalayer, openmap):
346
+ def test_should_display_alert_on_conflict(new_page, live_server, datalayer, openmap):
287
347
  # Open the map on two pages.
288
- page_one = context.new_page()
348
+ page_one = new_page("page 1")
289
349
  page_one.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
290
- page_two = context.new_page()
350
+ page_two = new_page("page 2")
291
351
  page_two.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
292
352
 
293
353
  # Change name on page one and save
@@ -27,7 +27,7 @@ def test_iframe_code_can_contain_datalayers(map, live_server, datalayer, page):
27
27
 
28
28
  def test_iframe_code_can_contain_feature(map, live_server, datalayer, page):
29
29
  page.goto(f"{live_server.url}{map.get_absolute_url()}?share")
30
- page.locator(".icon_container").click()
30
+ page.locator(".icon-container").click()
31
31
  textarea = page.locator(".umap-share-iframe")
32
32
  expect(textarea).to_be_visible()
33
33
  expect(textarea).not_to_have_text(re.compile("feature=Here"))
@@ -69,7 +69,9 @@ def test_table_editor(live_server, openmap, datalayer, page):
69
69
  page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
70
70
  page.get_by_role("button", name="Manage layers").click()
71
71
  page.locator(".panel").get_by_title("Edit properties in a table").click()
72
- page.get_by_text("Add a new property").click()
72
+ page.locator("td[data-property=description]").dblclick()
73
+ page.locator('textarea[name="description"]').fill("nice new description")
74
+ page.get_by_text("Add a new field").click()
73
75
  page.locator("dialog").locator("input").fill("newprop")
74
76
  page.locator("dialog").get_by_role("button", name="OK").click()
75
77
  page.locator("td").nth(2).dblclick()
@@ -83,6 +85,7 @@ def test_table_editor(live_server, openmap, datalayer, page):
83
85
  page.get_by_role("button", name="Save").click()
84
86
  saved = DataLayer.objects.last()
85
87
  data = json.loads(Path(saved.geojson.path).read_text())
88
+ assert data["features"][0]["properties"]["description"] == "nice new description"
86
89
  assert data["features"][0]["properties"]["newprop"] == "newvalue"
87
90
  assert "name" not in data["features"][0]["properties"]
88
91
 
@@ -91,7 +94,7 @@ def test_cannot_add_existing_property_name(live_server, openmap, datalayer, page
91
94
  page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
92
95
  page.get_by_role("button", name="Manage layers").click()
93
96
  page.locator(".panel").get_by_title("Edit properties in a table").click()
94
- page.get_by_text("Add a new property").click()
97
+ page.get_by_text("Add a new field").click()
95
98
  page.locator("dialog").locator("input").fill("name")
96
99
  page.get_by_role("button", name="OK").click()
97
100
  expect(page.get_by_role("dialog")).to_contain_text(
@@ -104,7 +107,7 @@ def test_cannot_add_property_with_a_dot(live_server, openmap, datalayer, page):
104
107
  page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
105
108
  page.get_by_role("button", name="Manage layers").click()
106
109
  page.locator(".panel").get_by_title("Edit properties in a table").click()
107
- page.get_by_text("Add a new property").click()
110
+ page.get_by_text("Add a new field").click()
108
111
  page.locator("dialog").locator("input").fill("foo.bar")
109
112
  page.get_by_role("button", name="OK").click()
110
113
  expect(page.get_by_role("dialog")).to_contain_text(
@@ -127,13 +130,13 @@ def test_rename_property(live_server, openmap, page):
127
130
  expect(page.locator("table th button[data-property=mytype]")).to_have_count(0)
128
131
 
129
132
  page.locator(".panel.full").get_by_role("button", name="Close").click()
130
- page.locator(".leaflet-marker-icon").first.click()
133
+ page.locator(".leaflet-marker-icon").first.click(button="right")
131
134
  page.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
132
135
  expect(page.locator(".panel.right .umap-field-mynewtype")).to_be_visible()
133
136
  expect(page.locator(".panel.right .umap-field-mytype")).to_be_hidden()
134
137
  page.locator(".edit-undo").click()
135
138
  page.locator(".panel.right").get_by_role("button", name="Close").click()
136
- page.locator(".leaflet-marker-icon").first.click()
139
+ page.locator(".leaflet-marker-icon").first.click(button="right")
137
140
  page.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
138
141
  expect(page.locator(".panel.right .umap-field-mynewtype")).to_be_hidden()
139
142
  expect(page.locator(".panel.right .umap-field-mytype")).to_be_visible()
@@ -151,12 +154,12 @@ def test_delete_property(live_server, openmap, page):
151
154
  expect(page.locator("table th button[data-property=mytype]")).to_have_count(0)
152
155
 
153
156
  page.locator(".panel.full").get_by_role("button", name="Close").click()
154
- page.locator(".leaflet-marker-icon").first.click()
157
+ page.locator(".leaflet-marker-icon").first.click(button="right")
155
158
  page.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
156
159
  expect(page.locator(".panel.right .umap-field-mytype")).to_be_hidden()
157
160
  page.locator(".edit-undo").click()
158
161
  page.locator(".panel.right").get_by_role("button", name="Close").click()
159
- page.locator(".leaflet-marker-icon").first.click()
162
+ page.locator(".leaflet-marker-icon").first.click(button="right")
160
163
  page.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
161
164
  expect(page.locator(".panel.right .umap-field-mytype")).to_be_visible()
162
165
 
@@ -58,7 +58,7 @@ def test_websocket_connection_can_sync_markers(new_page, asgi_live_server, tilel
58
58
  peerA.locator("body").press("Escape")
59
59
  peerA.wait_for_timeout(300)
60
60
 
61
- peerB.locator(".leaflet-marker-icon").first.click()
61
+ peerB.locator(".leaflet-marker-icon").first.click(button="right")
62
62
  peerB.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
63
63
  expect(peerB.locator('input[name="name"]')).to_have_value("Synced name")
64
64
 
@@ -137,7 +137,7 @@ def test_websocket_connection_can_sync_polygons(context, asgi_live_server, tilel
137
137
  a_polygon_bbox_t1 = a_polygon.bounding_box()
138
138
  assert b_polygon_bbox_t1 == a_polygon_bbox_t1
139
139
 
140
- b_polygon.click()
140
+ b_polygon.click(button="right")
141
141
  peerB.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
142
142
 
143
143
  edited_vertex = peerB.locator("div:nth-child(6)").first
@@ -151,7 +151,7 @@ def test_websocket_connection_can_sync_polygons(context, asgi_live_server, tilel
151
151
  assert b_polygon_bbox_t2 == a_polygon_bbox_t2
152
152
 
153
153
  # Move the polygon on peer B and check it moved also on peer A
154
- b_polygon.click()
154
+ b_polygon.click(button="right")
155
155
  peerB.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
156
156
 
157
157
  b_polygon.drag_to(b_map_el, target_position={"x": 400, "y": 400})
@@ -340,7 +340,7 @@ def test_websocket_connection_can_sync_late_joining_peer(
340
340
  expect(b_polygons).to_have_count(1)
341
341
 
342
342
  # Verify marker properties
343
- peerB.locator(".leaflet-marker-icon").first.click()
343
+ peerB.locator(".leaflet-marker-icon").first.click(button="right")
344
344
  peerB.get_by_role("button", name="Toggle edit mode (⇧+Click)").click()
345
345
  expect(peerB.locator('input[name="name"]')).to_have_value("First marker")
346
346
 
umap/utils.py CHANGED
@@ -1,8 +1,10 @@
1
1
  import gzip
2
2
  import json
3
3
  import os
4
+ from pathlib import Path
4
5
 
5
6
  from django.conf import settings
7
+ from django.contrib.staticfiles import finders
6
8
  from django.core.serializers.json import DjangoJSONEncoder
7
9
  from django.urls import URLPattern, URLResolver, get_resolver
8
10
 
@@ -185,3 +187,38 @@ def merge_features(reference: list, latest: list, incoming: list):
185
187
  def json_dumps(obj, **kwargs):
186
188
  """Utility using the Django JSON Encoder when dumping objects"""
187
189
  return json.dumps(obj, cls=DjangoJSONEncoder, **kwargs)
190
+
191
+
192
+ def collect_pictograms():
193
+ pictograms = {}
194
+
195
+ for name, definition in settings.UMAP_PICTOGRAMS_COLLECTIONS.items():
196
+ root = Path(definition["path"])
197
+ subfolder = "pictograms"
198
+ if not root.is_absolute():
199
+ found_path = finders.find(root)
200
+ if not found_path:
201
+ print(f"Cannot find {root} in STATIFILES_DIRS")
202
+ continue
203
+ root = Path(found_path.removesuffix(definition["path"].rstrip("/")))
204
+ subfolder = definition["path"]
205
+ categories = {}
206
+ for path in (root / subfolder).iterdir():
207
+ if path.is_dir():
208
+ categories[path.name] = []
209
+ for subpath in path.iterdir():
210
+ if subpath.is_dir() or subpath.name.startswith("."):
211
+ continue
212
+ src = subpath.relative_to(root)
213
+ categories[path.name].append(
214
+ {
215
+ "name": subpath.stem,
216
+ "src": f"{settings.STATIC_URL}{src}",
217
+ }
218
+ )
219
+ pictograms[name] = {
220
+ "attribution": definition.get("attribution"),
221
+ "categories": categories,
222
+ }
223
+
224
+ return pictograms
umap/views.py CHANGED
@@ -71,6 +71,7 @@ from .models import DataLayer, Licence, Map, Pictogram, Star, Team, TileLayer
71
71
  from .utils import (
72
72
  ConflictError,
73
73
  _urls_for_js,
74
+ collect_pictograms,
74
75
  gzip_file,
75
76
  is_ajax,
76
77
  json_dumps,
@@ -635,6 +636,8 @@ class MapDetailMixin(SessionMixin):
635
636
  "defaultLabelKeys": settings.UMAP_LABEL_KEYS,
636
637
  "help_links": settings.UMAP_HELP_LINKS,
637
638
  }
639
+ if settings.OPENROUTESERVICE_APIKEY:
640
+ properties["ORSAPIKey"] = settings.OPENROUTESERVICE_APIKEY
638
641
  created = bool(getattr(self, "object", None))
639
642
  if created:
640
643
  properties.update(
@@ -1389,8 +1392,21 @@ class PictogramJSONList(ListView):
1389
1392
  model = Pictogram
1390
1393
 
1391
1394
  def render_to_response(self, context, **response_kwargs):
1392
- content = [p.json for p in Pictogram.objects.all()]
1393
- return simple_json_response(pictogram_list=content)
1395
+ if settings.UMAP_PICTOGRAMS_COLLECTIONS:
1396
+ content = collect_pictograms()
1397
+ else:
1398
+ categories = {}
1399
+ for picto in Pictogram.objects.all():
1400
+ category = picto.category or _("Generic")
1401
+ categories.setdefault(category, [])
1402
+ categories[category].append(picto.json)
1403
+ content = {
1404
+ settings.SITE_NAME: {
1405
+ "attribution": settings.SITE_NAME,
1406
+ "categories": categories,
1407
+ }
1408
+ }
1409
+ return simple_json_response(data=content)
1394
1410
 
1395
1411
 
1396
1412
  # ############## #
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: umap-project
3
+ Version: 3.3.0
4
+ Summary: Create maps with OpenStreetMap layers in a minute and embed them in your site.
5
+ Author-email: Yohan Boniface <yb@enix.org>
6
+ Maintainer-email: David Larlet <david@larlet.fr>
7
+ License-File: LICENSE
8
+ Keywords: django,geodjango,leaflet,map,openstreetmap
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: django-agnocomplete==2.2.0
20
+ Requires-Dist: django-environ==0.12.0
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
25
+ Requires-Dist: rcssmin==1.2.1
26
+ Requires-Dist: requests==2.32.4
27
+ Requires-Dist: rjsmin==1.2.4
28
+ Requires-Dist: social-auth-app-django==5.4.3
29
+ Requires-Dist: social-auth-core==4.5.6
30
+ Provides-Extra: dev
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.16; extra == 'dev'
35
+ Requires-Dist: mkdocs-static-i18n==1.3.0; extra == 'dev'
36
+ Requires-Dist: mkdocs==1.6.1; extra == 'dev'
37
+ Requires-Dist: pymdown-extensions==10.16.1; extra == 'dev'
38
+ Requires-Dist: ruff==0.12.8; extra == 'dev'
39
+ Requires-Dist: vermin==1.6.0; extra == 'dev'
40
+ Provides-Extra: docker
41
+ Requires-Dist: uvicorn==0.35.0; extra == 'docker'
42
+ Provides-Extra: s3
43
+ Requires-Dist: django-storages[s3]==1.14.6; extra == 's3'
44
+ Provides-Extra: sync
45
+ Requires-Dist: pydantic==2.11.7; extra == 'sync'
46
+ Requires-Dist: redis==6.4.0; extra == 'sync'
47
+ Requires-Dist: websockets==15.0.1; extra == 'sync'
48
+ Provides-Extra: test
49
+ Requires-Dist: daphne==4.2.1; extra == 'test'
50
+ Requires-Dist: factory-boy==3.3.3; extra == 'test'
51
+ Requires-Dist: moto[s3]==5.1.9; extra == 'test'
52
+ Requires-Dist: playwright>=1.39; extra == 'test'
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'
56
+ Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
57
+ Requires-Dist: pytest==8.4.1; extra == 'test'
58
+ Description-Content-Type: text/markdown
59
+
60
+ [![Matrix](https://img.shields.io/matrix/umap:matrix.org?server_fqdn=matrix.org&logo=matrix)](https://matrix.to/#/#umap:matrix.org)
61
+ [![Forum](https://img.shields.io/discourse/users?server=https://forum.openstreetmap.fr&label=Forum&logo=discourse)](https://forum.openstreetmap.fr/c/utiliser/umap/29)
62
+ [![Mailing list](https://img.shields.io/badge/Mailing%20list-Subscribe-598dc1?labelColor=white&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAZCAYAAAAmNZ4aAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5AICEi0F3iqxoQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAATBSURBVEjHrZbPb5xHHcY/z+z6R+06cRzWJQGCSgKhxPturSIOwIFSIeCEOHHqMZeqkUoLUiH1rvddlEOFxAn4HxAXblwIoEoICRrlfScFi0PVxo0rQtYBJ3ZiZ3ceDruOYid2kqojzeH9vpr5zPc7zzwzAqjXFySpKinYrgABhA0SgBPgYU+wHZMlp6LomMdseuaZhUq1yglJc5I+a/s48BRo1Pa4pHFwD3zb1qbEJnAbWAU+BD4AroA2JN8Ar9tat30zxs6dPcGNxsIYuAaqpaTLMeZdgPn5xbF+P83YOhyCD4EPpRRmJWrArM00+KCkafA0aEvyNWDNZhVYAXXttCKFFfAV26sxdraG4GYNdAS8YfMtiaWiyP/4qCV79tn2ZL+fJiVPSZqymZaYsTkkMZJS2pDCJvgmcA3oSr33lWWtY+A5yechPGnzKni5LPNf8zG1+fnFkZT6o7bmwWtS/1KwMVADpoqifc3mnK16lrUWPi5wr+fRlPSizaiEoRICpDvAUQgTADG2bwKvgz+fZc2fnDrVDh8V2Gi0lGXNOeBXwFKM+fmUwrwdKiEEtoDDNk9sD4ixvSbpR6DnKpX+mXq9Nfr40OYBm9MS50JIrRjzPwFIZGAFu7IFmgBP3juwKNpXbf8Q+Ibk04+Zad3Wm7a/VK3e+F5RdN4bxj8peVyCYPu27RFb07sniDFftvUK6Pl6vfXyo0CzrPl92z8Gn48xf+Xtt39x11xsnrNZB1KIsd2TSBIHs6yl++Ht9yWfkfzVer314kOgp4Hv2vy8LPPf7P5v+yug67bSUDjqgmd093tnK4r8Q4mXJV5oNFrPPxjaehU4Dn4txrzcw6/mwO9KqR+GK+kCnwFV9sqmKPJV4DXwt7Os+ald0EXbt8oyf70sOzcevO8LRyQfs7VcFB0HgBBYBo7aVPcrZVm2u6BfAl+v18+ODCd8KQR+H+P+hpOSjgPrIbgLDEA27wLTDwM3Gm8crFRuXen1xt+SQi3LmjWJ3/b7Xn+Y6ELwF22t2fovQJifPzsGlcugCYmJfc7lnK3DFy68mcoyX+n3fX1rqxcvXsyvSkxnWfPk3n6+WLE1ByzZDMAphcmybF21XQVN7qHWF2xGUqq+tx17552f3VpaOpcGW5BfAcbr9daXHzTeTjOgI7bKGNtbAKEoOqtDR+kCUw9Q63dAG7bKS5daae9ieknygUajdfJ+sJ4CT4D/ebf0gzL+NEh6C3xslwN9Dbhj+28xtvv7C6+zKRFtvlCvNz+xE8yngbUQvLQDXBTnUkr+O+jYPZmetF2z019izHuP4lpFkf9H4nIIZFnW2lb9iOSnbV0sis76DjBApbJxQfKTQ+hhm8/Z4c8xdm49jk8XRbuwCcCw5GEMNAv+ww4r2SWiH0D4ne0TkpbLcvF/H/1KbH4TKEHJ9pmyzNv7gBeOQhgdmInHgVmJGYnplDwlhRFwFSzQFrBp+3oIWrXpAv+WWC6KfGVYuRPDl2mtLNt/3RM8WOnZgymFGUmzwCHgwFDtB+65Pp8AxobdkoOtChAkG3TH5l9AtP2PEFguik5/XzDAqVNvqFIR0mAa2wohkJKlwUNbkofjhZ2242GQoGSTJPopuR9j575j+H+W9nlG4uJsnAAAAABJRU5ErkJggg==)](https://lists.openstreetmap.org/listinfo/umap)
63
+
64
+ [![Liberapay](https://img.shields.io/liberapay/patrons/uMap.svg?logo=liberapay)](https://liberapay.com/uMap)
65
+ [![OpenCollective](https://img.shields.io/opencollective/all/uMap?logo=opencollective)](https://opencollective.com/uMap)
66
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/umap-project?logo=github)](https://github.com/sponsors/umap-project)
67
+
68
+ # uMap project
69
+
70
+ uMap lets you create maps with OpenStreetMap layers within minutes and embed them in your site.
71
+ *Because we think that the more OSM will be used, the more OSM will be improved.*
72
+ Built on top of Django and Leaflet.
73
+
74
+ - Have a look at [our website](https://umap-project.org) for an introduction
75
+ - See [our docs](https://docs.umap-project.org/) for technical information
76
+ - Come [chat with us on matrix](https://matrix.to/#/#umap:matrix.org), start a thread on [the forum](https://forum.openstreetmap.fr/c/utiliser/umap/29) or join [the mailing list](https://lists.openstreetmap.org/listinfo/umap)