umap-project 3.4.0b3__py3-none-any.whl → 3.6.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 (222) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/br/LC_MESSAGES/django.po +71 -57
  4. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/da/LC_MESSAGES/django.po +18 -14
  6. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/de/LC_MESSAGES/django.po +20 -16
  8. umap/locale/en/LC_MESSAGES/django.po +18 -14
  9. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/es/LC_MESSAGES/django.po +20 -16
  11. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/fr/LC_MESSAGES/django.po +18 -14
  13. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/hu/LC_MESSAGES/django.po +20 -16
  15. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/pl/LC_MESSAGES/django.po +101 -95
  17. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/zh_TW/LC_MESSAGES/django.po +20 -16
  19. umap/management/commands/clean_tilelayer.py +0 -1
  20. umap/management/commands/search_maps.py +95 -0
  21. umap/settings/__init__.py +9 -1
  22. umap/settings/base.py +7 -6
  23. umap/static/umap/content.css +0 -3
  24. umap/static/umap/css/bar.css +9 -6
  25. umap/static/umap/css/form.css +25 -9
  26. umap/static/umap/css/icon.css +8 -0
  27. umap/static/umap/css/popup.css +1 -0
  28. umap/static/umap/img/16-white.svg +5 -2
  29. umap/static/umap/img/16.svg +1 -1
  30. umap/static/umap/img/source/16-white.svg +7 -4
  31. umap/static/umap/img/source/16.svg +1 -1
  32. umap/static/umap/js/components/copiable.js +47 -0
  33. umap/static/umap/js/modules/autocomplete.js +32 -67
  34. umap/static/umap/js/modules/browser.js +31 -14
  35. umap/static/umap/js/modules/data/features.js +34 -36
  36. umap/static/umap/js/modules/data/fields.js +199 -23
  37. umap/static/umap/js/modules/data/layer.js +85 -96
  38. umap/static/umap/js/modules/domutils.js +25 -1
  39. umap/static/umap/js/modules/filters.js +24 -50
  40. umap/static/umap/js/modules/form/builder.js +17 -16
  41. umap/static/umap/js/modules/form/fields.js +20 -20
  42. umap/static/umap/js/modules/formatter.js +9 -1
  43. umap/static/umap/js/modules/help.js +12 -13
  44. umap/static/umap/js/modules/importer.js +17 -26
  45. umap/static/umap/js/modules/importers/banfr.js +0 -1
  46. umap/static/umap/js/modules/importers/cadastrefr.js +19 -19
  47. umap/static/umap/js/modules/importers/communesfr.js +7 -8
  48. umap/static/umap/js/modules/importers/datasets.js +14 -14
  49. umap/static/umap/js/modules/importers/geodatamine.js +20 -22
  50. umap/static/umap/js/modules/importers/opendata.js +10 -0
  51. umap/static/umap/js/modules/importers/overpass.js +19 -18
  52. umap/static/umap/js/modules/managers.js +1 -1
  53. umap/static/umap/js/modules/permissions.js +15 -5
  54. umap/static/umap/js/modules/rendering/controls.js +203 -10
  55. umap/static/umap/js/modules/rendering/icon.js +5 -9
  56. umap/static/umap/js/modules/rendering/layers/base.js +1 -1
  57. umap/static/umap/js/modules/rendering/layers/classified.js +16 -11
  58. umap/static/umap/js/modules/rendering/layers/heat.js +1 -0
  59. umap/static/umap/js/modules/rendering/map.js +67 -57
  60. umap/static/umap/js/modules/rendering/popup.js +6 -3
  61. umap/static/umap/js/modules/rendering/template.js +40 -40
  62. umap/static/umap/js/modules/rendering/ui.js +1 -2
  63. umap/static/umap/js/modules/rules.js +34 -41
  64. umap/static/umap/js/modules/schema.js +0 -7
  65. umap/static/umap/js/modules/share.js +36 -69
  66. umap/static/umap/js/modules/slideshow.js +3 -3
  67. umap/static/umap/js/modules/tableeditor.js +0 -1
  68. umap/static/umap/js/modules/ui/bar.js +53 -33
  69. umap/static/umap/js/modules/ui/hash.js +36 -0
  70. umap/static/umap/js/modules/ui/loader.js +26 -0
  71. umap/static/umap/js/modules/ui/panel.js +33 -21
  72. umap/static/umap/js/modules/ui/tooltip.js +1 -1
  73. umap/static/umap/js/modules/umap.js +81 -80
  74. umap/static/umap/js/modules/utils.js +13 -3
  75. umap/static/umap/js/umap.controls.js +16 -179
  76. umap/static/umap/locale/am_ET.js +7 -8
  77. umap/static/umap/locale/am_ET.json +7 -8
  78. umap/static/umap/locale/ar.js +7 -8
  79. umap/static/umap/locale/ar.json +7 -8
  80. umap/static/umap/locale/ast.js +7 -8
  81. umap/static/umap/locale/ast.json +7 -8
  82. umap/static/umap/locale/bg.js +7 -8
  83. umap/static/umap/locale/bg.json +7 -8
  84. umap/static/umap/locale/br.js +44 -36
  85. umap/static/umap/locale/br.json +44 -36
  86. umap/static/umap/locale/ca.js +7 -8
  87. umap/static/umap/locale/ca.json +7 -8
  88. umap/static/umap/locale/cs_CZ.js +7 -8
  89. umap/static/umap/locale/cs_CZ.json +7 -8
  90. umap/static/umap/locale/da.js +8 -9
  91. umap/static/umap/locale/da.json +8 -9
  92. umap/static/umap/locale/de.js +62 -63
  93. umap/static/umap/locale/de.json +62 -63
  94. umap/static/umap/locale/el.js +7 -8
  95. umap/static/umap/locale/el.json +7 -8
  96. umap/static/umap/locale/en.js +7 -8
  97. umap/static/umap/locale/en.json +7 -8
  98. umap/static/umap/locale/en_US.json +7 -8
  99. umap/static/umap/locale/es.js +19 -20
  100. umap/static/umap/locale/es.json +19 -20
  101. umap/static/umap/locale/et.js +7 -8
  102. umap/static/umap/locale/et.json +7 -8
  103. umap/static/umap/locale/eu.js +23 -24
  104. umap/static/umap/locale/eu.json +23 -24
  105. umap/static/umap/locale/fa_IR.js +7 -8
  106. umap/static/umap/locale/fa_IR.json +7 -8
  107. umap/static/umap/locale/fi.js +7 -8
  108. umap/static/umap/locale/fi.json +7 -8
  109. umap/static/umap/locale/fr.js +11 -12
  110. umap/static/umap/locale/fr.json +11 -12
  111. umap/static/umap/locale/gl.js +147 -148
  112. umap/static/umap/locale/gl.json +147 -148
  113. umap/static/umap/locale/he.js +7 -8
  114. umap/static/umap/locale/he.json +7 -8
  115. umap/static/umap/locale/hr.js +7 -8
  116. umap/static/umap/locale/hr.json +7 -8
  117. umap/static/umap/locale/hu.js +8 -9
  118. umap/static/umap/locale/hu.json +8 -9
  119. umap/static/umap/locale/id.js +7 -8
  120. umap/static/umap/locale/id.json +7 -8
  121. umap/static/umap/locale/is.js +7 -8
  122. umap/static/umap/locale/is.json +7 -8
  123. umap/static/umap/locale/it.js +7 -8
  124. umap/static/umap/locale/it.json +7 -8
  125. umap/static/umap/locale/ja.js +7 -8
  126. umap/static/umap/locale/ja.json +7 -8
  127. umap/static/umap/locale/ko.js +7 -8
  128. umap/static/umap/locale/ko.json +7 -8
  129. umap/static/umap/locale/lt.js +7 -8
  130. umap/static/umap/locale/lt.json +7 -8
  131. umap/static/umap/locale/ms.js +7 -8
  132. umap/static/umap/locale/ms.json +7 -8
  133. umap/static/umap/locale/nl.js +7 -8
  134. umap/static/umap/locale/nl.json +7 -8
  135. umap/static/umap/locale/no.js +7 -8
  136. umap/static/umap/locale/no.json +7 -8
  137. umap/static/umap/locale/pl.js +53 -54
  138. umap/static/umap/locale/pl.json +53 -54
  139. umap/static/umap/locale/pl_PL.json +7 -8
  140. umap/static/umap/locale/pt.js +7 -8
  141. umap/static/umap/locale/pt.json +7 -8
  142. umap/static/umap/locale/pt_BR.js +7 -8
  143. umap/static/umap/locale/pt_BR.json +7 -8
  144. umap/static/umap/locale/pt_PT.js +7 -8
  145. umap/static/umap/locale/pt_PT.json +7 -8
  146. umap/static/umap/locale/ro.js +7 -8
  147. umap/static/umap/locale/ro.json +7 -8
  148. umap/static/umap/locale/ru.js +7 -8
  149. umap/static/umap/locale/ru.json +7 -8
  150. umap/static/umap/locale/sk_SK.js +7 -8
  151. umap/static/umap/locale/sk_SK.json +7 -8
  152. umap/static/umap/locale/sl.js +7 -8
  153. umap/static/umap/locale/sl.json +7 -8
  154. umap/static/umap/locale/sr.js +7 -8
  155. umap/static/umap/locale/sr.json +7 -8
  156. umap/static/umap/locale/sv.js +7 -8
  157. umap/static/umap/locale/sv.json +7 -8
  158. umap/static/umap/locale/th_TH.js +7 -8
  159. umap/static/umap/locale/th_TH.json +7 -8
  160. umap/static/umap/locale/tr.js +7 -8
  161. umap/static/umap/locale/tr.json +7 -8
  162. umap/static/umap/locale/uk_UA.js +7 -8
  163. umap/static/umap/locale/uk_UA.json +7 -8
  164. umap/static/umap/locale/vi.js +7 -8
  165. umap/static/umap/locale/vi.json +7 -8
  166. umap/static/umap/locale/vi_VN.json +7 -8
  167. umap/static/umap/locale/zh.js +7 -8
  168. umap/static/umap/locale/zh.json +7 -8
  169. umap/static/umap/locale/zh_CN.json +7 -8
  170. umap/static/umap/locale/zh_TW.Big5.json +7 -8
  171. umap/static/umap/locale/zh_TW.js +20 -21
  172. umap/static/umap/locale/zh_TW.json +20 -21
  173. umap/static/umap/map.css +6 -21
  174. umap/static/umap/unittests/utils.js +7 -7
  175. umap/static/umap/vendors/locatecontrol/L.Control.Locate.esm.js +942 -0
  176. umap/static/umap/vendors/photon/leaflet.photon.esm.js +472 -0
  177. umap/sync/app.py +4 -1
  178. umap/templates/umap/content_footer.html +1 -0
  179. umap/templates/umap/css.html +0 -4
  180. umap/templates/umap/js.html +1 -8
  181. umap/templates/umap/team_form.html +2 -1
  182. umap/tests/integration/conftest.py +3 -2
  183. umap/tests/integration/test_anonymous_owned_map.py +1 -1
  184. umap/tests/integration/test_conditional_rules.py +106 -51
  185. umap/tests/integration/test_draw_polygon.py +4 -0
  186. umap/tests/integration/test_draw_polyline.py +11 -0
  187. umap/tests/integration/test_edit_datalayer.py +1 -1
  188. umap/tests/integration/test_edit_map.py +2 -0
  189. umap/tests/integration/test_fields.py +19 -0
  190. umap/tests/integration/test_filters.py +24 -0
  191. umap/tests/integration/test_iframe.py +1 -1
  192. umap/tests/integration/test_import.py +26 -0
  193. umap/tests/integration/test_map.py +3 -3
  194. umap/tests/integration/test_optimistic_merge.py +7 -1
  195. umap/tests/integration/test_owned_map.py +2 -2
  196. umap/tests/integration/test_popup.py +31 -0
  197. umap/tests/integration/test_remote_data.py +5 -5
  198. umap/tests/integration/test_search.py +41 -0
  199. umap/tests/integration/test_share.py +2 -2
  200. umap/tests/integration/test_team.py +1 -1
  201. umap/tests/integration/test_websocket_sync.py +6 -1
  202. umap/tests/test_search_maps_command.py +44 -0
  203. umap/tests/test_utils.py +4 -1
  204. umap/utils.py +10 -3
  205. umap/views.py +17 -4
  206. {umap_project-3.4.0b3.dist-info → umap_project-3.6.0.dist-info}/METADATA +29 -23
  207. {umap_project-3.4.0b3.dist-info → umap_project-3.6.0.dist-info}/RECORD +210 -214
  208. {umap_project-3.4.0b3.dist-info → umap_project-3.6.0.dist-info}/WHEEL +1 -1
  209. umap/static/umap/js/umap.core.js +0 -93
  210. umap/static/umap/vendors/editinosm/Leaflet.EditInOSM.css +0 -46
  211. umap/static/umap/vendors/editinosm/Leaflet.EditInOSM.js +0 -240
  212. umap/static/umap/vendors/editinosm/edit-in-osm.png +0 -0
  213. umap/static/umap/vendors/hash/leaflet-hash.js +0 -162
  214. umap/static/umap/vendors/loading/Control.Loading.css +0 -26
  215. umap/static/umap/vendors/loading/Control.Loading.js +0 -351
  216. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.css +0 -1
  217. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.css.map +0 -1
  218. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +0 -4
  219. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +0 -1
  220. umap/static/umap/vendors/photon/leaflet.photon.js +0 -487
  221. {umap_project-3.4.0b3.dist-info → umap_project-3.6.0.dist-info}/entry_points.txt +0 -0
  222. {umap_project-3.4.0b3.dist-info → umap_project-3.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -147,16 +147,16 @@ def test_default_view_latest_with_polygon(map, live_server, page):
147
147
  expect(layers).to_have_count(1)
148
148
 
149
149
 
150
- def test_default_view_locate(browser, live_server, map):
150
+ def test_default_view_locate(browser, live_server, map, new_page):
151
151
  context = browser.new_context(
152
152
  geolocation={"longitude": 8.52967, "latitude": 39.16267},
153
153
  permissions=["geolocation"],
154
154
  )
155
155
  map.settings["properties"]["defaultView"] = "locate"
156
156
  map.save()
157
- page = context.new_page()
157
+ page = new_page(custom_context=context)
158
158
  page.goto(f"{live_server.url}{map.get_absolute_url()}")
159
- expect(page).to_have_url(re.compile(r".*#18/39\.16267/8\.52967"))
159
+ expect(page).to_have_url(re.compile(r".*#18/39\.162670/8\.529670"))
160
160
 
161
161
 
162
162
  def test_remote_layer_should_not_be_used_as_datalayer_for_created_features(
@@ -3,6 +3,7 @@ import re
3
3
  from pathlib import Path
4
4
  from time import sleep
5
5
 
6
+ import pytest
6
7
  from playwright.sync_api import expect
7
8
 
8
9
  from umap.models import DataLayer
@@ -12,7 +13,12 @@ from ..base import DataLayerFactory, MapFactory
12
13
  DATALAYER_UPDATE = re.compile(r".*/datalayer/update/.*")
13
14
 
14
15
 
15
- def test_created_markers_are_merged(new_page, live_server, tilelayer):
16
+ # Test with in memory upload AND streamed upload
17
+ @pytest.mark.parametrize("upload_limit", (2621440, 0))
18
+ def test_created_markers_are_merged(
19
+ new_page, live_server, tilelayer, settings, upload_limit
20
+ ):
21
+ settings.FILE_UPLOAD_MAX_MEMORY_SIZE = upload_limit
16
22
  # Let's create a new map with an empty datalayer
17
23
  map = MapFactory(name="server-side merge")
18
24
  datalayer = DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
@@ -183,7 +183,7 @@ def test_can_change_perms_after_create(tilelayer, live_server, login, user):
183
183
  page.goto(f"{live_server.url}/en/map/new")
184
184
  # Create a layer
185
185
  page.get_by_title("Manage layers").click()
186
- page.get_by_title("Add a layer").click()
186
+ page.get_by_role("button", name="Add a layer").click()
187
187
  page.locator("input[name=name]").fill("Layer 1")
188
188
  expect(
189
189
  page.get_by_role("button", name="Visibility: Draft (private)")
@@ -217,7 +217,7 @@ def test_can_change_owner(map, live_server, login, user):
217
217
  page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
218
218
  edit_permissions = page.get_by_title("Update permissions and editors")
219
219
  edit_permissions.click()
220
- close = page.locator(".umap-field-owner .close")
220
+ close = page.locator(".umap-field-owner .icon-close")
221
221
  close.click()
222
222
  input = page.locator("input.edit-owner")
223
223
  with page.expect_response(re.compile(r".*/agnocomplete/.*")):
@@ -42,3 +42,34 @@ def test_openstreetmap_popup(live_server, map, page):
42
42
  "src",
43
43
  "https://api.panoramax.xyz/api/pictures/d811b398-d930-4cf8-95a2-0c29c34d9fca/sd.jpg",
44
44
  )
45
+
46
+
47
+ def test_table_popup(live_server, map, page):
48
+ data = {
49
+ "type": "FeatureCollection",
50
+ "features": [
51
+ {
52
+ "type": "Feature",
53
+ "geometry": {"type": "Point", "coordinates": [2.49, 48.79]},
54
+ "properties": {
55
+ "url": "https://restaurant.org",
56
+ "formatted": "with **bold**",
57
+ },
58
+ "id": "AzMjk",
59
+ },
60
+ ],
61
+ "_umap_options": {
62
+ "popupTemplate": "Table",
63
+ "fields": [
64
+ {"key": "url", "type": "String"},
65
+ {"key": "formatted", "type": "Text"},
66
+ ],
67
+ },
68
+ }
69
+ DataLayerFactory(map=map, data=data)
70
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#18/48.79/2.49")
71
+ page.locator(".leaflet-marker-icon").click()
72
+ expect(page.get_by_role("link", name="https://restaurant.org")).to_be_visible()
73
+ expect(page.get_by_role("cell", name="with bold").locator("strong")).to_have_text(
74
+ "bold"
75
+ )
@@ -16,7 +16,7 @@ def intercept_remote_data(page):
16
16
  "features": [
17
17
  {
18
18
  "type": "Feature",
19
- "properties": {"name": "Point 2"},
19
+ "properties": {"name": "Point 2", "foobar": "bla"},
20
20
  "geometry": {
21
21
  "type": "Point",
22
22
  "coordinates": [4.3375, 11.2707],
@@ -29,7 +29,7 @@ def intercept_remote_data(page):
29
29
  "features": [
30
30
  {
31
31
  "type": "Feature",
32
- "properties": {"name": "Point 1"},
32
+ "properties": {"name": "Point 1", "foobar": "baz"},
33
33
  "geometry": {
34
34
  "type": "Point",
35
35
  "coordinates": [4.3375, 12.2707],
@@ -44,7 +44,6 @@ def intercept_remote_data(page):
44
44
 
45
45
  # Intercept the route to the proxy
46
46
  page.route("https://remote.org/data.json", handle)
47
- page.on("request", lambda *a, **k: print(a, k))
48
47
 
49
48
 
50
49
  def test_dynamic_remote_data(page, live_server, tilelayer, map):
@@ -104,6 +103,7 @@ def test_create_remote_data_layer(page, live_server, tilelayer, settings):
104
103
  expect(page.locator(".leaflet-marker-icon")).to_have_count(1)
105
104
  with page.expect_response(re.compile(".*/datalayer/create/.*")):
106
105
  page.get_by_role("button", name="Save draft", exact=True).click()
106
+ assert DataLayer.objects.count() == 1
107
107
  datalayer = DataLayer.objects.last()
108
108
  data = json.loads(Path(datalayer.geojson.path).read_text())
109
109
  assert data == {
@@ -117,8 +117,8 @@ def test_create_remote_data_layer(page, live_server, tilelayer, settings):
117
117
  "type": "String",
118
118
  },
119
119
  {
120
- "key": "description",
121
- "type": "Text",
120
+ "key": "foobar",
121
+ "type": "String",
122
122
  },
123
123
  ],
124
124
  "id": str(datalayer.pk),
@@ -0,0 +1,41 @@
1
+ from playwright.sync_api import expect
2
+
3
+
4
+ def test_reverse_search(live_server, page, tilelayer):
5
+ photon_response = {
6
+ "type": "FeatureCollection",
7
+ "features": [
8
+ {
9
+ "type": "Feature",
10
+ "properties": {
11
+ "osm_type": "N",
12
+ "osm_id": 5055853416,
13
+ "osm_key": "place",
14
+ "osm_value": "locality",
15
+ "type": "locality",
16
+ "postcode": "10200",
17
+ "countrycode": "FR",
18
+ "name": "Le Haut Sentier",
19
+ "country": "France",
20
+ "city": "Thors",
21
+ "state": "Grand Est",
22
+ "county": "Aube",
23
+ },
24
+ "geometry": {"type": "Point", "coordinates": [4.7995256, 48.2985251]},
25
+ }
26
+ ],
27
+ }
28
+ page.goto(f"{live_server.url}/en/map/new")
29
+
30
+ def handle_search(route):
31
+ route.fulfill(json=photon_response)
32
+
33
+ # Intercept the route
34
+ page.route(
35
+ "https://photon.komoot.io/reverse/?limit=1&lat=48.3&lon=4.8",
36
+ handle_search,
37
+ )
38
+ page.get_by_role("button", name="Search location").click()
39
+ page.get_by_role("searchbox", name="Type a place name or").fill("48.3 4.8")
40
+ expect(page.get_by_text("48.3 4.8")).to_be_visible()
41
+ expect(page.get_by_text("Le Haut Sentier")).to_be_visible()
@@ -8,7 +8,7 @@ pytestmark = pytest.mark.django_db
8
8
 
9
9
  def test_iframe_code_can_contain_datalayers(map, live_server, datalayer, page):
10
10
  page.goto(f"{live_server.url}{map.get_absolute_url()}?share")
11
- textarea = page.locator(".umap-share-iframe")
11
+ textarea = page.get_by_label("Iframe")
12
12
  expect(textarea).to_be_visible()
13
13
  expect(textarea).to_have_text(re.compile('src="'))
14
14
  expect(textarea).to_have_text(re.compile('href="'))
@@ -28,7 +28,7 @@ def test_iframe_code_can_contain_datalayers(map, live_server, datalayer, page):
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
30
  page.locator(".icon-container").click()
31
- textarea = page.locator(".umap-share-iframe")
31
+ textarea = page.get_by_label("Iframe")
32
32
  expect(textarea).to_be_visible()
33
33
  expect(textarea).not_to_have_text(re.compile("feature=Here"))
34
34
  # Open options
@@ -39,7 +39,7 @@ def test_can_remove_user_from_team(live_server, map, user, user2, team, login):
39
39
  page.get_by_role("link", name="My teams").click()
40
40
  with page.expect_navigation():
41
41
  page.get_by_role("link", name="Edit").click()
42
- page.locator("li").filter(has_text="Averell").locator(".close").click()
42
+ page.locator("li").filter(has_text="Averell").locator(".icon-close").click()
43
43
  page.get_by_role("button", name="Save").click()
44
44
  assert Team.objects.count() == 1
45
45
  modified = Team.objects.first()
@@ -53,6 +53,7 @@ def test_websocket_connection_can_sync_markers(
53
53
 
54
54
  a_map_el = peerA.locator("#map")
55
55
  a_map_el.click(position={"x": 220, "y": 220})
56
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
56
57
  expect(a_marker_pane).to_have_count(1)
57
58
  expect(b_marker_pane).to_have_count(1)
58
59
  # Peer B should not be in state dirty
@@ -330,6 +331,7 @@ def test_websocket_connection_can_sync_late_joining_peer(
330
331
 
331
332
  a_map_el = peerA.locator("#map")
332
333
  a_map_el.click(position={"x": 220, "y": 220})
334
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
333
335
  peerA.locator("body").type("First marker")
334
336
  peerA.locator("body").press("Escape")
335
337
  peerA.wait_for_timeout(300)
@@ -555,8 +557,10 @@ def test_create_and_sync_map(
555
557
 
556
558
  # Add a marker from peer A
557
559
  peerA.get_by_role("button", name="Edit").click()
560
+ peerA.wait_for_timeout(300) # Time for the animation to finish
558
561
  peerA.get_by_title("Draw a marker").click()
559
562
  peerA.locator("#map").click(position={"x": 220, "y": 220})
563
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
560
564
  expect(markersA).to_have_count(1)
561
565
  expect(markersB).to_have_count(1)
562
566
 
@@ -582,6 +586,7 @@ def test_create_and_sync_map(
582
586
  # Add a marker from peer B
583
587
  peerB.get_by_title("Draw a marker").click()
584
588
  peerB.locator("#map").click(position={"x": 200, "y": 200})
589
+ peerA.wait_for_timeout(300) # Time for the panel animation to finish
585
590
  expect(markersB).to_have_count(2)
586
591
  expect(markersA).to_have_count(1)
587
592
  with peerB.expect_response(re.compile("./datalayer/update/.*")):
@@ -607,7 +612,7 @@ def test_saved_datalayer_are_not_duplicated(
607
612
  wait_for_loaded(peerA)
608
613
  # Create a new datalayer
609
614
  peerA.get_by_title("Manage layers").click()
610
- peerA.get_by_title("Add a layer").click()
615
+ peerA.get_by_role("button", name="Add a layer").click()
611
616
  peerA.locator("#map").click(position={"x": 220, "y": 220})
612
617
  # Save layer to the server, so now the datalayer exist on the server AND
613
618
  # is still in the live operations of peer A
@@ -0,0 +1,44 @@
1
+ import pytest
2
+ from django.core.management import call_command
3
+
4
+ from umap.models import Map
5
+
6
+ from .base import MapFactory
7
+
8
+ pytestmark = pytest.mark.django_db
9
+
10
+
11
+ def test_search_and_delete_maps(map, team):
12
+ target = MapFactory(name="find me")
13
+ assert Map.objects.filter(share_status=Map.DELETED).count() == 0
14
+
15
+ call_command("search_maps", "find", "--delete", "--dry-run", "--no-input")
16
+ assert Map.objects.filter(share_status=Map.DELETED).count() == 0
17
+
18
+ call_command("search_maps", "find", "--delete", "--no-input")
19
+ assert Map.objects.filter(share_status=Map.DELETED).count() == 1
20
+
21
+ assert not Map.public.filter(pk=target.pk)
22
+
23
+ call_command("search_maps", "find", "--restore", "--no-input")
24
+ assert Map.objects.filter(share_status=Map.DELETED).count() == 0
25
+
26
+ assert Map.objects.get(pk=target.pk).share_status == Map.DRAFT
27
+
28
+
29
+ def test_search_and_block_maps(map, team):
30
+ target = MapFactory(name="find me")
31
+ assert Map.objects.filter(share_status=Map.BLOCKED).count() == 0
32
+
33
+ call_command("search_maps", "find", "--block", "--dry-run", "--no-input")
34
+ assert Map.objects.filter(share_status=Map.BLOCKED).count() == 0
35
+
36
+ call_command("search_maps", "find", "--block", "--no-input")
37
+ assert Map.objects.filter(share_status=Map.BLOCKED).count() == 1
38
+
39
+ assert not Map.public.filter(pk=target.pk)
40
+
41
+ call_command("search_maps", "find", "--restore", "--no-input")
42
+ assert Map.objects.filter(share_status=Map.BLOCKED).count() == 0
43
+
44
+ assert Map.objects.get(pk=target.pk).share_status == Map.DRAFT
umap/tests/test_utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ import stat
1
2
  from pathlib import Path
2
3
 
3
4
  import pytest
@@ -5,7 +6,8 @@ import pytest
5
6
  from umap.utils import gzip_file, normalize_string
6
7
 
7
8
 
8
- def test_gzip_file():
9
+ def test_gzip_file(settings):
10
+ settings.FILE_UPLOAD_PERMISSIONS = 0o666
9
11
  # Let's use any old file so we can check that the date of the gzip file is set.
10
12
  src = Path(__file__).parent / "settings.py"
11
13
  dest = Path("/tmp/test_settings.py.gz")
@@ -14,6 +16,7 @@ def test_gzip_file():
14
16
  dest_stat = dest.stat()
15
17
  dest.unlink()
16
18
  assert src_stat.st_mtime == dest_stat.st_mtime
19
+ assert stat.filemode(dest_stat.st_mode) == "-rw-rw-rw-"
17
20
 
18
21
 
19
22
  @pytest.mark.parametrize(
umap/utils.py CHANGED
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  from django.conf import settings
8
8
  from django.contrib.staticfiles import finders
9
9
  from django.core.serializers.json import DjangoJSONEncoder
10
- from django.urls import URLPattern, URLResolver, get_resolver
10
+ from django.urls import URLPattern, URLResolver, get_resolver, reverse
11
11
 
12
12
 
13
13
  def _get_url_names(module):
@@ -29,14 +29,20 @@ def _urls_for_js():
29
29
  """
30
30
  Return templated URLs prepared for javascript.
31
31
  """
32
- urls = {}
32
+ urls = {
33
+ "agnocomplete": f"{reverse('agnocomplete:agnocomplete', kwargs={'klass': 'AutocompleteUser'})}?q={{q}}"
34
+ }
33
35
  modules = ["umap.urls"]
34
36
  if settings.REALTIME_ENABLED:
35
37
  modules.append("umap.sync.app")
36
38
  for module in modules:
37
39
  names = _get_url_names(module)
40
+ prefix = settings.FORCE_SCRIPT_NAME or ""
38
41
  urls.update(
39
- dict(zip(names, [get_uri_template(url, module=module) for url in names]))
42
+ zip(
43
+ names,
44
+ [get_uri_template(url, prefix=prefix, module=module) for url in names],
45
+ )
40
46
  )
41
47
  urls.update(getattr(settings, "UMAP_EXTRA_URLS", {}))
42
48
  return urls
@@ -150,6 +156,7 @@ def gzip_file(from_path, to_path):
150
156
  with gzip.open(to_path, "wb") as f_out:
151
157
  f_out.writelines(f_in)
152
158
  os.utime(to_path, ns=(stat.st_mtime_ns, stat.st_mtime_ns))
159
+ os.chmod(to_path, settings.FILE_UPLOAD_PERMISSIONS)
153
160
 
154
161
 
155
162
  def is_ajax(request):
umap/views.py CHANGED
@@ -6,7 +6,6 @@ import socket
6
6
  import zipfile
7
7
  from datetime import datetime, timedelta
8
8
  from http.client import InvalidURL
9
- from io import BytesIO
10
9
  from pathlib import Path
11
10
  from smtplib import SMTPException
12
11
  from urllib.error import HTTPError, URLError
@@ -22,6 +21,7 @@ from django.contrib.postgres.search import SearchQuery, SearchVector
22
21
  from django.contrib.sessions.models import Session
23
22
  from django.contrib.staticfiles.storage import staticfiles_storage
24
23
  from django.core.exceptions import PermissionDenied
24
+ from django.core.files.uploadedfile import InMemoryUploadedFile
25
25
  from django.core.mail import send_mail
26
26
  from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
27
27
  from django.core.signing import BadSignature, Signer, TimestampSigner
@@ -610,7 +610,8 @@ class MapDetailMixin(SessionMixin):
610
610
 
611
611
  def set_preconnect(self, properties, context):
612
612
  # Try to extract the tilelayer domain, in order to but a preconnect meta.
613
- url_template = properties.get("tilelayer", {}).get("url_template")
613
+ tilelayer = properties.get("tilelayer") or {}
614
+ url_template = tilelayer.get("url_template")
614
615
  # Not explicit tilelayer set, take the first of the list, which will be
615
616
  # used by frontend too.
616
617
  if not url_template:
@@ -1335,8 +1336,20 @@ class DataLayerUpdate(FormLessEditMixin, UpdateView):
1335
1336
  return HttpResponse(status=412)
1336
1337
 
1337
1338
  # Replace the uploaded file by the merged version.
1338
- self.request.FILES["geojson"].file = BytesIO(
1339
- json_dumps(merged).encode("utf-8")
1339
+ # The geojson here can be either a NamedTemporaryFile or an
1340
+ # InMemoryUploadedFile, depending on whether is bigger than the
1341
+ # FILE_UPLOAD_MAX_MEMORY_SIZE setting (2.5Mo by default).
1342
+ # Now that we loaded all in RAM, let's use an InMemoryUploadedFile.
1343
+ orig = self.request.FILES["geojson"]
1344
+ file = io.BytesIO(json_dumps(merged).encode("utf-8"))
1345
+ file_size = file.getbuffer().nbytes
1346
+ self.request.FILES["geojson"] = InMemoryUploadedFile(
1347
+ file=file,
1348
+ field_name="geojson",
1349
+ name=orig.name,
1350
+ content_type="application/geo+json",
1351
+ size=file_size,
1352
+ charset="utf-8",
1340
1353
  )
1341
1354
 
1342
1355
  # Mark the data to be reloaded by form_valid
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: umap-project
3
- Version: 3.4.0b3
3
+ Version: 3.6.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>
@@ -18,43 +18,49 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Requires-Python: >=3.10
19
19
  Requires-Dist: django-agnocomplete==2.2.0
20
20
  Requires-Dist: django-environ==0.12.0
21
- Requires-Dist: django-probes==1.7.0
22
- Requires-Dist: django==5.2.7
23
- Requires-Dist: pillow==11.3.0
24
- Requires-Dist: psycopg==3.2.11
25
- Requires-Dist: rcssmin==1.2.1
21
+ Requires-Dist: django-probes==1.8.0
22
+ Requires-Dist: django<6.0,>=5.1
23
+ Requires-Dist: pillow==12.1.0
24
+ Requires-Dist: psycopg==3.3.2
25
+ Requires-Dist: rcssmin==1.2.2
26
26
  Requires-Dist: requests==2.32.5
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
27
+ Requires-Dist: rjsmin==1.2.5
28
+ Requires-Dist: social-auth-app-django==5.7.0
29
+ Requires-Dist: social-auth-core==4.8.3
30
+ Provides-Extra: deploy
31
+ Requires-Dist: django==5.2.10; extra == 'deploy'
30
32
  Provides-Extra: dev
33
+ Requires-Dist: django==5.2.9; extra == 'dev'
31
34
  Requires-Dist: djlint==1.36.4; extra == 'dev'
32
- Requires-Dist: hatch==1.14.2; extra == 'dev'
33
- Requires-Dist: isort==6.0.1; extra == 'dev'
34
- Requires-Dist: mkdocs-material==9.6.21; extra == 'dev'
35
+ Requires-Dist: hatch==1.15.1; extra == 'dev'
36
+ Requires-Dist: isort==7.0.0; extra == 'dev'
37
+ Requires-Dist: mkdocs-material==9.7.1; extra == 'dev'
35
38
  Requires-Dist: mkdocs-static-i18n==1.3.0; extra == 'dev'
36
39
  Requires-Dist: mkdocs==1.6.1; extra == 'dev'
37
- Requires-Dist: pymdown-extensions==10.16.1; extra == 'dev'
38
- Requires-Dist: ruff==0.14.0; extra == 'dev'
39
- Requires-Dist: vermin==1.6.0; extra == 'dev'
40
- Provides-Extra: docker
41
- Requires-Dist: uvicorn==0.37.0; extra == 'docker'
40
+ Requires-Dist: pymdown-extensions==10.20.1; extra == 'dev'
41
+ Requires-Dist: ruff==0.14.14; extra == 'dev'
42
+ Requires-Dist: vermin==1.8.0; extra == 'dev'
42
43
  Provides-Extra: s3
43
44
  Requires-Dist: django-storages[s3]==1.14.6; extra == 's3'
44
45
  Provides-Extra: sync
45
- Requires-Dist: pydantic==2.12.3; extra == 'sync'
46
- Requires-Dist: redis==6.4.0; extra == 'sync'
47
- Requires-Dist: websockets==15.0.1; extra == 'sync'
46
+ Requires-Dist: pydantic==2.12.5; extra == 'sync'
47
+ Requires-Dist: redis==7.1.0; extra == 'sync'
48
+ Requires-Dist: uvicorn==0.40.0; extra == 'sync'
49
+ Requires-Dist: websockets==16.0; extra == 'sync'
48
50
  Provides-Extra: test
49
51
  Requires-Dist: daphne==4.2.1; extra == 'test'
50
52
  Requires-Dist: factory-boy==3.3.3; extra == 'test'
51
- Requires-Dist: moto[s3]==5.1.14; extra == 'test'
53
+ Requires-Dist: moto[s3]==5.1.20; extra == 'test'
52
54
  Requires-Dist: playwright>=1.39; extra == 'test'
53
55
  Requires-Dist: pytest-django==4.11.1; extra == 'test'
54
- Requires-Dist: pytest-playwright==0.7.1; extra == 'test'
56
+ Requires-Dist: pytest-playwright==0.7.2; extra == 'test'
55
57
  Requires-Dist: pytest-rerunfailures==16.1; extra == 'test'
56
58
  Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
57
- Requires-Dist: pytest==8.4.2; extra == 'test'
59
+ Requires-Dist: pytest==9.0.2; extra == 'test'
60
+ Provides-Extra: yunohost
61
+ Requires-Dist: django-yunohost-integration==0.10.9; extra == 'yunohost'
62
+ Requires-Dist: django==5.1.15; extra == 'yunohost'
63
+ Requires-Dist: uvicorn==0.40.0; extra == 'yunohost'
58
64
  Description-Content-Type: text/markdown
59
65
 
60
66
  [![Matrix](https://img.shields.io/matrix/umap:matrix.org?server_fqdn=matrix.org&logo=matrix)](https://matrix.to/#/#umap:matrix.org)