umap-project 2.4.1__py3-none-any.whl → 2.5.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 (199) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/el/LC_MESSAGES/django.po +145 -90
  4. umap/locale/en/LC_MESSAGES/django.po +13 -13
  5. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/eu/LC_MESSAGES/django.po +145 -89
  7. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/hu/LC_MESSAGES/django.po +100 -50
  9. umap/static/umap/base.css +5 -2
  10. umap/static/umap/content.css +2 -2
  11. umap/static/umap/css/contextmenu.css +11 -0
  12. umap/static/umap/css/dialog.css +25 -4
  13. umap/static/umap/css/importers.css +2 -0
  14. umap/static/umap/css/panel.css +6 -4
  15. umap/static/umap/css/slideshow.css +69 -0
  16. umap/static/umap/css/tableeditor.css +69 -0
  17. umap/static/umap/css/tooltip.css +3 -3
  18. umap/static/umap/img/16-white.svg +4 -0
  19. umap/static/umap/img/source/16-white.svg +5 -1
  20. umap/static/umap/js/components/alerts/alert.css +11 -11
  21. umap/static/umap/js/components/alerts/alert.js +1 -1
  22. umap/static/umap/js/modules/autocomplete.js +27 -5
  23. umap/static/umap/js/modules/browser.js +20 -14
  24. umap/static/umap/js/modules/caption.js +4 -4
  25. umap/static/umap/js/modules/dompurify.js +2 -3
  26. umap/static/umap/js/modules/facets.js +53 -17
  27. umap/static/umap/js/modules/formatter.js +153 -0
  28. umap/static/umap/js/modules/global.js +25 -16
  29. umap/static/umap/js/modules/help.js +26 -26
  30. umap/static/umap/js/modules/importer.js +10 -10
  31. umap/static/umap/js/modules/importers/communesfr.js +3 -1
  32. umap/static/umap/js/modules/importers/datasets.js +8 -6
  33. umap/static/umap/js/modules/importers/geodatamine.js +14 -14
  34. umap/static/umap/js/modules/importers/overpass.js +19 -15
  35. umap/static/umap/js/modules/orderable.js +2 -2
  36. umap/static/umap/js/modules/request.js +1 -1
  37. umap/static/umap/js/modules/rules.js +26 -11
  38. umap/static/umap/js/modules/schema.js +16 -12
  39. umap/static/umap/js/{umap.share.js → modules/share.js} +58 -103
  40. umap/static/umap/js/modules/slideshow.js +141 -0
  41. umap/static/umap/js/modules/sync/engine.js +3 -3
  42. umap/static/umap/js/modules/sync/updaters.js +10 -11
  43. umap/static/umap/js/modules/sync/websocket.js +1 -1
  44. umap/static/umap/js/modules/tableeditor.js +329 -0
  45. umap/static/umap/js/modules/ui/base.js +93 -0
  46. umap/static/umap/js/modules/ui/contextmenu.js +50 -0
  47. umap/static/umap/js/modules/ui/dialog.js +169 -31
  48. umap/static/umap/js/modules/ui/panel.js +7 -5
  49. umap/static/umap/js/modules/ui/tooltip.js +7 -77
  50. umap/static/umap/js/modules/urls.js +1 -2
  51. umap/static/umap/js/modules/utils.js +36 -16
  52. umap/static/umap/js/umap.controls.js +27 -29
  53. umap/static/umap/js/umap.core.js +19 -15
  54. umap/static/umap/js/umap.datalayer.permissions.js +15 -18
  55. umap/static/umap/js/umap.features.js +113 -131
  56. umap/static/umap/js/umap.forms.js +203 -228
  57. umap/static/umap/js/umap.icon.js +17 -22
  58. umap/static/umap/js/umap.js +117 -107
  59. umap/static/umap/js/umap.layer.js +374 -324
  60. umap/static/umap/js/umap.permissions.js +7 -10
  61. umap/static/umap/js/umap.popup.js +20 -20
  62. umap/static/umap/locale/am_ET.js +22 -5
  63. umap/static/umap/locale/am_ET.json +22 -5
  64. umap/static/umap/locale/ar.js +22 -5
  65. umap/static/umap/locale/ar.json +22 -5
  66. umap/static/umap/locale/ast.js +22 -5
  67. umap/static/umap/locale/ast.json +22 -5
  68. umap/static/umap/locale/bg.js +22 -5
  69. umap/static/umap/locale/bg.json +22 -5
  70. umap/static/umap/locale/br.js +22 -5
  71. umap/static/umap/locale/br.json +22 -5
  72. umap/static/umap/locale/ca.js +56 -39
  73. umap/static/umap/locale/ca.json +56 -39
  74. umap/static/umap/locale/cs_CZ.js +22 -5
  75. umap/static/umap/locale/cs_CZ.json +22 -5
  76. umap/static/umap/locale/da.js +22 -5
  77. umap/static/umap/locale/da.json +22 -5
  78. umap/static/umap/locale/de.js +22 -5
  79. umap/static/umap/locale/de.json +22 -5
  80. umap/static/umap/locale/el.js +27 -10
  81. umap/static/umap/locale/el.json +27 -10
  82. umap/static/umap/locale/en.js +22 -6
  83. umap/static/umap/locale/en.json +22 -6
  84. umap/static/umap/locale/en_US.json +22 -5
  85. umap/static/umap/locale/es.js +22 -6
  86. umap/static/umap/locale/es.json +22 -6
  87. umap/static/umap/locale/et.js +22 -5
  88. umap/static/umap/locale/et.json +22 -5
  89. umap/static/umap/locale/eu.js +167 -150
  90. umap/static/umap/locale/eu.json +167 -150
  91. umap/static/umap/locale/fa_IR.js +22 -5
  92. umap/static/umap/locale/fa_IR.json +22 -5
  93. umap/static/umap/locale/fi.js +22 -5
  94. umap/static/umap/locale/fi.json +22 -5
  95. umap/static/umap/locale/fr.js +22 -6
  96. umap/static/umap/locale/fr.json +22 -6
  97. umap/static/umap/locale/gl.js +22 -5
  98. umap/static/umap/locale/gl.json +22 -5
  99. umap/static/umap/locale/he.js +22 -5
  100. umap/static/umap/locale/he.json +22 -5
  101. umap/static/umap/locale/hr.js +22 -5
  102. umap/static/umap/locale/hr.json +22 -5
  103. umap/static/umap/locale/hu.js +89 -72
  104. umap/static/umap/locale/hu.json +89 -72
  105. umap/static/umap/locale/id.js +22 -5
  106. umap/static/umap/locale/id.json +22 -5
  107. umap/static/umap/locale/is.js +22 -5
  108. umap/static/umap/locale/is.json +22 -5
  109. umap/static/umap/locale/it.js +22 -5
  110. umap/static/umap/locale/it.json +22 -5
  111. umap/static/umap/locale/ja.js +22 -5
  112. umap/static/umap/locale/ja.json +22 -5
  113. umap/static/umap/locale/ko.js +22 -5
  114. umap/static/umap/locale/ko.json +22 -5
  115. umap/static/umap/locale/lt.js +22 -5
  116. umap/static/umap/locale/lt.json +22 -5
  117. umap/static/umap/locale/ms.js +22 -5
  118. umap/static/umap/locale/ms.json +22 -5
  119. umap/static/umap/locale/nl.js +22 -5
  120. umap/static/umap/locale/nl.json +22 -5
  121. umap/static/umap/locale/no.js +22 -5
  122. umap/static/umap/locale/no.json +22 -5
  123. umap/static/umap/locale/pl.js +22 -5
  124. umap/static/umap/locale/pl.json +22 -5
  125. umap/static/umap/locale/pl_PL.json +22 -5
  126. umap/static/umap/locale/pt.js +22 -6
  127. umap/static/umap/locale/pt.json +22 -6
  128. umap/static/umap/locale/pt_BR.js +22 -5
  129. umap/static/umap/locale/pt_BR.json +22 -5
  130. umap/static/umap/locale/pt_PT.js +22 -5
  131. umap/static/umap/locale/pt_PT.json +22 -5
  132. umap/static/umap/locale/ro.js +22 -5
  133. umap/static/umap/locale/ro.json +22 -5
  134. umap/static/umap/locale/ru.js +22 -5
  135. umap/static/umap/locale/ru.json +22 -5
  136. umap/static/umap/locale/sk_SK.js +22 -5
  137. umap/static/umap/locale/sk_SK.json +22 -5
  138. umap/static/umap/locale/sl.js +22 -5
  139. umap/static/umap/locale/sl.json +22 -5
  140. umap/static/umap/locale/sr.js +22 -5
  141. umap/static/umap/locale/sr.json +22 -5
  142. umap/static/umap/locale/sv.js +22 -5
  143. umap/static/umap/locale/sv.json +22 -5
  144. umap/static/umap/locale/th_TH.js +22 -5
  145. umap/static/umap/locale/th_TH.json +22 -5
  146. umap/static/umap/locale/tr.js +22 -5
  147. umap/static/umap/locale/tr.json +22 -5
  148. umap/static/umap/locale/uk_UA.js +22 -5
  149. umap/static/umap/locale/uk_UA.json +22 -5
  150. umap/static/umap/locale/vi.js +22 -5
  151. umap/static/umap/locale/vi.json +22 -5
  152. umap/static/umap/locale/vi_VN.json +22 -5
  153. umap/static/umap/locale/zh.js +22 -5
  154. umap/static/umap/locale/zh.json +22 -5
  155. umap/static/umap/locale/zh_CN.json +22 -5
  156. umap/static/umap/locale/zh_TW.Big5.json +22 -5
  157. umap/static/umap/locale/zh_TW.js +22 -5
  158. umap/static/umap/locale/zh_TW.json +22 -5
  159. umap/static/umap/map.css +9 -153
  160. umap/static/umap/vars.css +15 -0
  161. umap/static/umap/vendors/dompurify/purify.es.js +5 -59
  162. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  163. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +410 -428
  164. umap/static/umap/vendors/geojson-to-gpx/index.js +155 -0
  165. umap/static/umap/vendors/osmtogeojson/osmtogeojson.js +1 -2
  166. umap/static/umap/vendors/togeojson/togeojson.es.js +1109 -0
  167. umap/static/umap/vendors/togeojson/{togeojson.umd.js.map → togeojson.es.mjs.map} +1 -1
  168. umap/static/umap/vendors/tokml/tokml.es.js +895 -0
  169. umap/static/umap/vendors/tokml/tokml.es.mjs.map +1 -0
  170. umap/storage.py +6 -2
  171. umap/templates/umap/components/alerts/alert.html +3 -3
  172. umap/templates/umap/css.html +3 -0
  173. umap/templates/umap/js.html +0 -6
  174. umap/tests/fixtures/categorized_highway.geojson +1 -0
  175. umap/tests/fixtures/test_import_osm_relation.json +130 -0
  176. umap/tests/integration/conftest.py +8 -1
  177. umap/tests/integration/test_browser.py +3 -2
  178. umap/tests/integration/test_categorized_layer.py +141 -0
  179. umap/tests/integration/test_conditional_rules.py +21 -0
  180. umap/tests/integration/test_datalayer.py +9 -4
  181. umap/tests/integration/test_edit_datalayer.py +1 -0
  182. umap/tests/integration/test_edit_polygon.py +1 -1
  183. umap/tests/integration/test_export_map.py +2 -3
  184. umap/tests/integration/test_import.py +22 -0
  185. umap/tests/integration/test_map_preview.py +36 -2
  186. umap/tests/integration/test_tableeditor.py +158 -4
  187. umap/tests/integration/test_websocket_sync.py +2 -2
  188. umap/tests/test_views.py +2 -2
  189. umap/views.py +3 -2
  190. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/METADATA +8 -8
  191. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/RECORD +194 -184
  192. umap/static/umap/js/umap.slideshow.js +0 -165
  193. umap/static/umap/js/umap.tableeditor.js +0 -118
  194. umap/static/umap/vendors/togeojson/togeojson.umd.js +0 -2
  195. umap/static/umap/vendors/togpx/togpx.js +0 -547
  196. umap/static/umap/vendors/tokml/tokml.js +0 -343
  197. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/WHEEL +0 -0
  198. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -67,6 +67,7 @@ def test_cancel_deleting_datalayer_should_restore(
67
67
  expect(page.get_by_text("test datalayer")).to_be_hidden()
68
68
  page.once("dialog", lambda dialog: dialog.accept())
69
69
  page.get_by_role("button", name="Cancel edits").click()
70
+ page.locator("dialog").get_by_role("button", name="OK").click()
70
71
  expect(markers).to_have_count(1)
71
72
  expect(page.locator(".umap-browser").get_by_text("test datalayer")).to_be_visible()
72
73
 
@@ -117,8 +117,8 @@ def test_should_reset_style_on_cancel(live_server, openmap, page, bootstrap):
117
117
  expect(page.locator(".leaflet-overlay-pane path[fill='GoldenRod']")).to_have_count(
118
118
  1
119
119
  )
120
- page.once("dialog", lambda dialog: dialog.accept())
121
120
  page.get_by_role("button", name="Cancel edits").click()
121
+ page.locator("dialog").get_by_role("button", name="OK").click()
122
122
  expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)
123
123
 
124
124
 
@@ -218,8 +218,7 @@ def test_gpx_export(map, live_server, bootstrap, page):
218
218
  download.save_as(path)
219
219
  assert (
220
220
  path.read_text()
221
- == """<gpx xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" version="1.1" creator="togpx"><metadata/><wpt lat="52.57635" lon="-0.274658"><name>test</name><desc>name=test
222
- description=Some description</desc></wpt><trk><name>name poly</name><desc>name=name poly</desc><trkseg><trkpt lat="53.585984" lon="11.25"/><trkpt lat="52.975108" lon="10.151367"/><trkpt lat="52.167194" lon="12.689209"/><trkpt lat="53.199452" lon="14.084473"/><trkpt lat="53.618579" lon="12.634277"/><trkpt lat="53.585984" lon="11.25"/><trkpt lat="53.585984" lon="11.25"/></trkseg></trk><trk><name>test</name><desc>name=test</desc><trkseg><trkpt lat="54.476422" lon="-0.571289"/><trkpt lat="54.610255" lon="0.439453"/><trkpt lat="53.448807" lon="1.724854"/><trkpt lat="53.988395" lon="4.163818"/><trkpt lat="53.533778" lon="5.306396"/><trkpt lat="53.709714" lon="6.591797"/><trkpt lat="53.350551" lon="7.042236"/></trkseg></trk></gpx>"""
221
+ == """<?xml version="1.0" encoding="UTF-8"?><gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="@dwayneparton/geojson-to-gpx"><wpt lat="52.57635" lon="-0.274658"><name>test</name><desc>Some description</desc></wpt><trk><name>test</name><trkseg><trkpt lat="54.476422" lon="-0.571289"/><trkpt lat="54.610255" lon="0.439453"/><trkpt lat="53.448807" lon="1.724854"/><trkpt lat="53.988395" lon="4.163818"/><trkpt lat="53.533778" lon="5.306396"/><trkpt lat="53.709714" lon="6.591797"/><trkpt lat="53.350551" lon="7.042236"/></trkseg></trk></gpx>"""
223
222
  )
224
223
 
225
224
 
@@ -235,7 +234,7 @@ def test_kml_export(map, live_server, bootstrap, page):
235
234
  download.save_as(path)
236
235
  assert (
237
236
  path.read_text()
238
- == """<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2"><Document><Placemark><name>name poly</name><ExtendedData><Data name="name"><value>name poly</value></Data></ExtendedData><Polygon><outerBoundaryIs><LinearRing><coordinates>11.25,53.585984 10.151367,52.975108 12.689209,52.167194 14.084473,53.199452 12.634277,53.618579 11.25,53.585984 11.25,53.585984</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark><Placemark><name>test</name><description>Some description</description><ExtendedData><Data name="_umap_options"><value>[object Object]</value></Data><Data name="name"><value>test</value></Data><Data name="description"><value>Some description</value></Data></ExtendedData><Point><coordinates>-0.274658,52.57635</coordinates></Point></Placemark><Placemark><name>test</name><ExtendedData><Data name="_umap_options"><value>[object Object]</value></Data><Data name="name"><value>test</value></Data></ExtendedData><LineString><coordinates>-0.571289,54.476422 0.439453,54.610255 1.724854,53.448807 4.163818,53.988395 5.306396,53.533778 6.591797,53.709714 7.042236,53.350551</coordinates></LineString></Placemark></Document></kml>"""
237
+ == """<kml xmlns="http://www.opengis.net/kml/2.2"><Document>\n<Placemark id="gyNzM">\n<name>name poly</name><ExtendedData></ExtendedData>\n <Polygon>\n<outerBoundaryIs>\n <LinearRing><coordinates>11.25,53.585984\n10.151367,52.975108\n12.689209,52.167194\n14.084473,53.199452\n12.634277,53.618579\n11.25,53.585984\n11.25,53.585984</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark>\n<Placemark id="QwNjg">\n<name>test</name><description>Some description</description><ExtendedData>\n <Data name="_umap_options"><value>{"color":"OliveDrab"}</value></Data></ExtendedData>\n <Point><coordinates>-0.274658,52.57635</coordinates></Point></Placemark>\n<Placemark id="YwMTM">\n<name>test</name><ExtendedData>\n <Data name="_umap_options"><value>{"fill":false,"opacity":0.6}</value></Data></ExtendedData>\n <LineString><coordinates>-0.571289,54.476422\n0.439453,54.610255\n1.724854,53.448807\n4.163818,53.988395\n5.306396,53.533778\n6.591797,53.709714\n7.042236,53.350551</coordinates></LineString></Placemark></Document></kml>"""
239
238
  )
240
239
 
241
240
 
@@ -585,3 +585,25 @@ def test_import_from_datasets(page, live_server, tilelayer, settings):
585
585
  expect(page.locator(".leaflet-marker-icon")).to_be_visible()
586
586
  page.get_by_role("button", name="Open browser").click()
587
587
  expect(page.locator("h5").get_by_text("Good data")).to_be_visible()
588
+
589
+
590
+ def test_import_osm_relation(tilelayer, live_server, page):
591
+ # Overpass query used for this data:
592
+ # [out:json][timeout:25];(relation(id:15612202)%20;);out%20geom;
593
+ page.goto(f"{live_server.url}/map/new/")
594
+ page.get_by_title("Open browser").click()
595
+ layers = page.locator(".umap-browser .datalayer")
596
+ paths = page.locator("path")
597
+ expect(paths).to_have_count(0)
598
+ expect(layers).to_have_count(0)
599
+ button = page.get_by_title("Import data")
600
+ expect(button).to_be_visible()
601
+ button.click()
602
+ textarea = page.locator(".umap-upload textarea")
603
+ file_path = Path(__file__).parent.parent / "fixtures/test_import_osm_relation.json"
604
+ textarea.fill(file_path.read_text())
605
+ page.locator('select[name="format"]').select_option("osm")
606
+ page.get_by_role("button", name="Import data", exact=True).click()
607
+ # A layer and one path has been created
608
+ expect(layers).to_have_count(1)
609
+ expect(paths).to_have_count(1)
@@ -21,6 +21,21 @@ GEOJSON = {
21
21
  }
22
22
  ],
23
23
  }
24
+ GEOJSON2 = {
25
+ "type": "FeatureCollection",
26
+ "features": [
27
+ {
28
+ "type": "Feature",
29
+ "properties": {
30
+ "name": "Montmorency Falls",
31
+ },
32
+ "geometry": {
33
+ "type": "Point",
34
+ "coordinates": [-71.14, 46.89],
35
+ },
36
+ }
37
+ ],
38
+ }
24
39
  CSV = "name,latitude,longitude\nNiagara Falls,43.08,-79.04"
25
40
 
26
41
 
@@ -43,10 +58,29 @@ def test_map_preview_can_load_remote_geojson(page, live_server, tilelayer):
43
58
  expect(markers).to_have_count(1)
44
59
 
45
60
 
61
+ def test_map_preview_can_load_mutiple_remote_geojson(page, live_server, tilelayer):
62
+ def handle(route):
63
+ if "2" in route.request.url:
64
+ route.fulfill(json=GEOJSON2)
65
+ else:
66
+ route.fulfill(json=GEOJSON)
67
+
68
+ # Intercept the route to the proxy
69
+ page.route("*/**/ajax-proxy/**", handle)
70
+
71
+ page.goto(
72
+ (
73
+ f"{live_server.url}/map/?"
74
+ "dataUrl=http://some.org/geo.json&dataUrl=http://some.org/geo2.json"
75
+ )
76
+ )
77
+ markers = page.locator(".leaflet-marker-icon")
78
+ expect(markers).to_have_count(2)
79
+
80
+
46
81
  def test_map_preview_can_load_remote_csv(page, live_server, tilelayer):
47
82
  def handle(route):
48
- csv = """name,latitude,longitude\nNiagara Falls,43.08,-79.04"""
49
- route.fulfill(body=csv)
83
+ route.fulfill(body=CSV)
50
84
 
51
85
  # Intercept the route to the proxy
52
86
  page.route("*/**/ajax-proxy/**", handle)
@@ -2,22 +2,176 @@ import json
2
2
  import re
3
3
  from pathlib import Path
4
4
 
5
+ from playwright.sync_api import expect
6
+
5
7
  from umap.models import DataLayer
6
8
 
9
+ from ..base import DataLayerFactory
10
+
11
+ DATALAYER_DATA = {
12
+ "type": "FeatureCollection",
13
+ "features": [
14
+ {
15
+ "type": "Feature",
16
+ "properties": {
17
+ "mytype": "even",
18
+ "name": "Point 2",
19
+ "mynumber": 10,
20
+ "myboolean": True,
21
+ "mydate": "2024/04/14 12:19:17",
22
+ },
23
+ "geometry": {"type": "Point", "coordinates": [0.065918, 48.385442]},
24
+ "id": "poin2", # Must be exactly 5 chars long so the frontend will keep it
25
+ },
26
+ {
27
+ "type": "Feature",
28
+ "properties": {
29
+ "mytype": "odd",
30
+ "name": "Point 1",
31
+ "mynumber": 12,
32
+ "myboolean": False,
33
+ "mydate": "2024/03/13 12:20:20",
34
+ },
35
+ "geometry": {"type": "Point", "coordinates": [3.55957, 49.767074]},
36
+ "id": "poin1",
37
+ },
38
+ {
39
+ "type": "Feature",
40
+ "properties": {
41
+ "mytype": "even",
42
+ "name": "Point 4",
43
+ "mynumber": 10,
44
+ "myboolean": "true",
45
+ "mydate": "2024/08/18 13:14:15",
46
+ },
47
+ "geometry": {"type": "Point", "coordinates": [0.856934, 45.290347]},
48
+ "id": "poin4",
49
+ },
50
+ {
51
+ "type": "Feature",
52
+ "properties": {
53
+ "mytype": "odd",
54
+ "name": "Point 3",
55
+ "mynumber": 14,
56
+ "mydate": "2024-04-14T10:19:17.000Z",
57
+ },
58
+ "geometry": {"type": "Point", "coordinates": [4.372559, 47.945786]},
59
+ "id": "poin3",
60
+ },
61
+ ],
62
+ "_umap_options": {
63
+ "name": "Calque 2",
64
+ },
65
+ }
66
+
7
67
 
8
68
  def test_table_editor(live_server, openmap, datalayer, page):
9
69
  page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
10
70
  page.get_by_role("link", name="Manage layers").click()
11
71
  page.locator(".panel").get_by_title("Edit properties in a table").click()
12
- page.once("dialog", lambda dialog: dialog.accept(prompt_text="newprop"))
13
72
  page.get_by_text("Add a new property").click()
73
+ page.locator("dialog").locator("input").fill("newprop")
74
+ page.locator("dialog").get_by_role("button", name="OK").click()
75
+ page.locator("td").nth(2).dblclick()
14
76
  page.locator('input[name="newprop"]').fill("newvalue")
15
- page.once("dialog", lambda dialog: dialog.accept())
16
- page.hover(".umap-table-editor .tcell")
17
- page.get_by_title("Delete this property on all").first.click()
77
+ page.keyboard.press("Enter")
78
+ page.locator("thead button[data-property=name]").click()
79
+ page.get_by_role("button", name="Delete this column").click()
80
+ page.locator("dialog").get_by_role("button", name="OK").click()
18
81
  with page.expect_response(re.compile(r".*/datalayer/update/.*")):
19
82
  page.get_by_role("button", name="Save").click()
20
83
  saved = DataLayer.objects.last()
21
84
  data = json.loads(Path(saved.geojson.path).read_text())
22
85
  assert data["features"][0]["properties"]["newprop"] == "newvalue"
23
86
  assert "name" not in data["features"][0]["properties"]
87
+
88
+
89
+ def test_cannot_add_existing_property_name(live_server, openmap, datalayer, page):
90
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
91
+ page.get_by_role("link", name="Manage layers").click()
92
+ page.locator(".panel").get_by_title("Edit properties in a table").click()
93
+ page.get_by_text("Add a new property").click()
94
+ page.locator("dialog").locator("input").fill("name")
95
+ page.get_by_role("button", name="OK").click()
96
+ expect(page.get_by_role("dialog")).to_contain_text(
97
+ "This name already exists: “name”"
98
+ )
99
+ expect(page.locator("table th button[data-property=name]")).to_have_count(1)
100
+
101
+
102
+ def test_cannot_add_property_with_a_dot(live_server, openmap, datalayer, page):
103
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
104
+ page.get_by_role("link", name="Manage layers").click()
105
+ page.locator(".panel").get_by_title("Edit properties in a table").click()
106
+ page.get_by_text("Add a new property").click()
107
+ page.locator("dialog").locator("input").fill("foo.bar")
108
+ page.get_by_role("button", name="OK").click()
109
+ expect(page.get_by_role("dialog")).to_contain_text(
110
+ "Name “foo.bar” should not contain a dot."
111
+ )
112
+ expect(page.locator("table th button[data-property=name]")).to_have_count(1)
113
+
114
+
115
+ def test_rename_property(live_server, openmap, datalayer, page):
116
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
117
+ page.get_by_role("link", name="Manage layers").click()
118
+ page.locator(".panel").get_by_title("Edit properties in a table").click()
119
+ expect(page.locator("table th button[data-property=name]")).to_have_count(1)
120
+ page.locator("thead button[data-property=name]").click()
121
+ page.get_by_text("Rename this column").click()
122
+ page.locator("dialog").locator("input").fill("newname")
123
+ page.get_by_role("button", name="OK").click()
124
+ expect(page.locator("table th button[data-property=newname]")).to_have_count(1)
125
+ expect(page.locator("table th button[data-property=name]")).to_have_count(0)
126
+
127
+
128
+ def test_delete_selected_rows(live_server, openmap, page):
129
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA)
130
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit#6/48.093/1.890")
131
+ page.get_by_role("link", name="Manage layers").click()
132
+ page.locator(".panel").get_by_title("Edit properties in a table").click()
133
+ expect(page.locator("tbody tr")).to_have_count(4)
134
+ expect(page.locator(".leaflet-marker-icon")).to_have_count(4)
135
+ page.locator("tr[data-feature=poin2]").get_by_role("checkbox").check()
136
+ page.get_by_role("button", name="Delete selected rows").click()
137
+ page.get_by_role("button", name="OK").click()
138
+ expect(page.locator("tbody tr")).to_have_count(3)
139
+ expect(page.locator(".leaflet-marker-icon")).to_have_count(3)
140
+
141
+
142
+ def test_delete_all_rows(live_server, openmap, page):
143
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA)
144
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit#6/48.093/1.890")
145
+ page.get_by_role("link", name="Manage layers").click()
146
+ page.locator(".panel").get_by_title("Edit properties in a table").click()
147
+ expect(page.locator("tbody tr")).to_have_count(4)
148
+ expect(page.locator(".leaflet-marker-icon")).to_have_count(4)
149
+ page.locator("thead").get_by_role("checkbox").check()
150
+ page.get_by_role("button", name="Delete selected rows").click()
151
+ page.get_by_role("button", name="OK").click()
152
+ expect(page.locator("tbody tr")).to_have_count(0)
153
+ expect(page.locator(".leaflet-marker-icon")).to_have_count(0)
154
+
155
+
156
+ def test_filter_and_delete_rows(live_server, openmap, page):
157
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA)
158
+ panel = page.locator(".panel.left.on")
159
+ table = page.locator(".panel.full table")
160
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit#6/48.093/1.890")
161
+ page.get_by_role("link", name="Manage layers").click()
162
+ page.locator(".panel").get_by_title("Edit properties in a table").click()
163
+ expect(table.locator("tbody tr")).to_have_count(4)
164
+ expect(page.locator(".leaflet-marker-icon")).to_have_count(4)
165
+ table.locator("thead button[data-property=mytype]").click()
166
+ page.get_by_role("button", name="Add filter for this column").click()
167
+ expect(panel).to_be_visible()
168
+ panel.get_by_label("even").check()
169
+ table.locator("thead").get_by_role("checkbox").check()
170
+ page.get_by_role("button", name="Delete selected rows").click()
171
+ page.get_by_role("button", name="OK").click()
172
+ expect(table.locator("tbody tr")).to_have_count(2)
173
+ expect(page.locator(".leaflet-marker-icon")).to_have_count(2)
174
+ expect(table.get_by_text("Point 1")).to_be_visible()
175
+ expect(table.get_by_text("Point 3")).to_be_visible()
176
+ expect(table.get_by_text("Point 2")).to_be_hidden()
177
+ expect(table.get_by_text("Point 4")).to_be_hidden()
@@ -69,8 +69,8 @@ def test_websocket_connection_can_sync_markers(
69
69
 
70
70
  # Delete a marker from peer A and check it's been deleted on peer B
71
71
  a_first_marker.click(button="right")
72
- peerA.on("dialog", lambda dialog: dialog.accept())
73
72
  peerA.get_by_role("link", name="Delete this feature").click()
73
+ peerA.locator("dialog").get_by_role("button", name="OK").click()
74
74
  expect(a_marker_pane).to_have_count(1)
75
75
  expect(b_marker_pane).to_have_count(1)
76
76
 
@@ -153,8 +153,8 @@ def test_websocket_connection_can_sync_polygons(
153
153
 
154
154
  # Delete a polygon from peer A and check it's been deleted on peer B
155
155
  a_polygon.click(button="right")
156
- peerA.on("dialog", lambda dialog: dialog.accept())
157
156
  peerA.get_by_role("link", name="Delete this feature").click()
157
+ peerA.locator("dialog").get_by_role("button", name="OK").click()
158
158
  expect(a_polygons).to_have_count(0)
159
159
  expect(b_polygons).to_have_count(0)
160
160
 
umap/tests/test_views.py CHANGED
@@ -122,7 +122,7 @@ def test_valid_proxy_request_with_invalid_ttl(client):
122
122
 
123
123
  def test_invalid_proxy_url_should_return_400(client):
124
124
  url = reverse("ajax-proxy")
125
- params = {"url": "http://example.org/a space is invalid"}
125
+ params = {"url": "http://example.org/a\ncarriage\r\nreturn is invalid"}
126
126
  headers = {
127
127
  "HTTP_X_REQUESTED_WITH": "XMLHttpRequest",
128
128
  "HTTP_REFERER": settings.SITE_URL,
@@ -144,7 +144,7 @@ def test_valid_proxy_request_with_x_accel_redirect(client, settings):
144
144
  assert "X-Accel-Redirect" in response.headers
145
145
  assert (
146
146
  response["X-Accel-Redirect"]
147
- == "/proxy/http%3A//example.org%3Ffoo%3Dbar%26bar%3Dfoo"
147
+ == "/proxy/http%3A%2F%2Fexample.org%3Ffoo%3Dbar%26bar%3Dfoo"
148
148
  )
149
149
  assert "X-Accel-Expires" in response.headers
150
150
  assert response["X-Accel-Expires"] == "300"
umap/views.py CHANGED
@@ -11,7 +11,7 @@ from io import BytesIO
11
11
  from pathlib import Path
12
12
  from smtplib import SMTPException
13
13
  from urllib.error import HTTPError, URLError
14
- from urllib.parse import quote, quote_plus, urlparse
14
+ from urllib.parse import quote_plus, urlparse
15
15
  from urllib.request import Request, build_opener
16
16
 
17
17
  from django.conf import settings
@@ -403,13 +403,14 @@ class AjaxProxy(View):
403
403
  ttl = None
404
404
  if getattr(settings, "UMAP_XSENDFILE_HEADER", None):
405
405
  response = HttpResponse()
406
- response[settings.UMAP_XSENDFILE_HEADER] = f"/proxy/{quote(url)}"
406
+ response[settings.UMAP_XSENDFILE_HEADER] = f"/proxy/{quote_plus(url)}"
407
407
  if ttl:
408
408
  response["X-Accel-Expires"] = ttl
409
409
  return response
410
410
 
411
411
  # You should not use this in production (use Nginx or so)
412
412
  headers = {"User-Agent": "uMapProxy +http://wiki.openstreetmap.org/wiki/UMap"}
413
+ url = url.replace(" ", "+")
413
414
  request = Request(url, headers=headers)
414
415
  opener = build_opener()
415
416
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: umap-project
3
- Version: 2.4.1
3
+ Version: 2.5.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,10 +19,10 @@ Requires-Python: >=3.10
19
19
  Requires-Dist: django-agnocomplete==2.2.0
20
20
  Requires-Dist: django-environ==0.11.2
21
21
  Requires-Dist: django-probes==1.7.0
22
- Requires-Dist: django==5.0.6
23
- Requires-Dist: pillow==10.3.0
24
- Requires-Dist: psycopg==3.1.19
25
- Requires-Dist: pydantic==2.7.4
22
+ Requires-Dist: django==5.0.7
23
+ Requires-Dist: pillow==10.4.0
24
+ Requires-Dist: psycopg==3.2.1
25
+ Requires-Dist: pydantic==2.8.2
26
26
  Requires-Dist: rcssmin==1.1.2
27
27
  Requires-Dist: requests==2.32.3
28
28
  Requires-Dist: rjsmin==1.2.2
@@ -33,11 +33,11 @@ Provides-Extra: dev
33
33
  Requires-Dist: djlint==1.34.1; extra == 'dev'
34
34
  Requires-Dist: hatch==1.12.0; extra == 'dev'
35
35
  Requires-Dist: isort==5.13.2; extra == 'dev'
36
- Requires-Dist: mkdocs-material==9.5.27; extra == 'dev'
36
+ Requires-Dist: mkdocs-material==9.5.28; extra == 'dev'
37
37
  Requires-Dist: mkdocs-static-i18n==1.2.3; extra == 'dev'
38
38
  Requires-Dist: mkdocs==1.6.0; extra == 'dev'
39
39
  Requires-Dist: pymdown-extensions==10.8.1; extra == 'dev'
40
- Requires-Dist: ruff==0.4.9; extra == 'dev'
40
+ Requires-Dist: ruff==0.5.1; extra == 'dev'
41
41
  Requires-Dist: vermin==1.6.0; extra == 'dev'
42
42
  Provides-Extra: docker
43
43
  Requires-Dist: uwsgi==2.0.26; extra == 'docker'
@@ -45,7 +45,7 @@ Provides-Extra: test
45
45
  Requires-Dist: factory-boy==3.2.1; extra == 'test'
46
46
  Requires-Dist: playwright>=1.39; extra == 'test'
47
47
  Requires-Dist: pytest-django==4.8.0; extra == 'test'
48
- Requires-Dist: pytest-playwright==0.5.0; extra == 'test'
48
+ Requires-Dist: pytest-playwright==0.5.1; extra == 'test'
49
49
  Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
50
50
  Requires-Dist: pytest==8.2.2; extra == 'test'
51
51
  Description-Content-Type: text/markdown