umap-project 2.6.2__py3-none-any.whl → 2.7.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.
- umap/__init__.py +1 -1
- umap/admin.py +64 -1
- umap/asgi.py +15 -0
- umap/context_processors.py +1 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.po +96 -92
- umap/locale/de/LC_MESSAGES/django.mo +0 -0
- umap/locale/de/LC_MESSAGES/django.po +19 -18
- umap/locale/en/LC_MESSAGES/django.po +47 -43
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +134 -128
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +51 -47
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +64 -60
- umap/management/commands/clean_tilelayer.py +152 -0
- umap/management/commands/purge_purgatory.py +28 -0
- umap/models.py +27 -2
- umap/settings/base.py +3 -1
- umap/static/umap/base.css +4 -4
- umap/static/umap/css/contextmenu.css +6 -1
- umap/static/umap/css/icon.css +7 -2
- umap/static/umap/css/importers.css +4 -0
- umap/static/umap/img/16-white.svg +9 -2
- umap/static/umap/img/16.svg +1 -181
- umap/static/umap/img/24-white.svg +1 -0
- umap/static/umap/img/24.svg +1 -0
- umap/static/umap/img/importers/cadastrefr.svg +23 -0
- umap/static/umap/img/source/16-white.svg +10 -3
- umap/static/umap/img/source/16.svg +753 -197
- umap/static/umap/img/source/24-white.svg +3 -2
- umap/static/umap/img/source/24.svg +3 -2
- umap/static/umap/js/modules/autocomplete.js +7 -3
- umap/static/umap/js/modules/browser.js +55 -2
- umap/static/umap/js/modules/caption.js +16 -5
- umap/static/umap/js/modules/data/features.js +183 -8
- umap/static/umap/js/modules/data/layer.js +57 -40
- umap/static/umap/js/modules/formatter.js +3 -2
- umap/static/umap/js/modules/global.js +2 -0
- umap/static/umap/js/modules/importer.js +3 -0
- umap/static/umap/js/modules/importers/cadastrefr.js +62 -0
- umap/static/umap/js/modules/importers/communesfr.js +15 -3
- umap/static/umap/js/modules/permissions.js +123 -93
- umap/static/umap/js/modules/rendering/layers/classified.js +2 -0
- umap/static/umap/js/modules/rendering/ui.js +60 -213
- umap/static/umap/js/modules/share.js +1 -3
- umap/static/umap/js/modules/slideshow.js +1 -1
- umap/static/umap/js/modules/sync/engine.js +371 -14
- umap/static/umap/js/modules/sync/hlc.js +106 -0
- umap/static/umap/js/modules/sync/updaters.js +18 -6
- umap/static/umap/js/modules/sync/websocket.js +1 -1
- umap/static/umap/js/modules/tableeditor.js +1 -1
- umap/static/umap/js/modules/ui/base.js +2 -2
- umap/static/umap/js/modules/ui/contextmenu.js +51 -18
- umap/static/umap/js/modules/urls.js +5 -1
- umap/static/umap/js/modules/utils.js +28 -4
- umap/static/umap/js/umap.controls.js +76 -55
- umap/static/umap/js/umap.core.js +3 -3
- umap/static/umap/js/umap.forms.js +3 -1
- umap/static/umap/js/umap.js +115 -124
- umap/static/umap/locale/am_ET.js +2 -2
- umap/static/umap/locale/am_ET.json +2 -2
- umap/static/umap/locale/ar.js +2 -2
- umap/static/umap/locale/ar.json +2 -2
- umap/static/umap/locale/ast.js +2 -2
- umap/static/umap/locale/ast.json +2 -2
- umap/static/umap/locale/bg.js +2 -2
- umap/static/umap/locale/bg.json +2 -2
- umap/static/umap/locale/br.js +13 -4
- umap/static/umap/locale/br.json +13 -4
- umap/static/umap/locale/ca.js +30 -17
- umap/static/umap/locale/ca.json +30 -17
- umap/static/umap/locale/cs_CZ.js +89 -80
- umap/static/umap/locale/cs_CZ.json +89 -80
- umap/static/umap/locale/da.js +2 -2
- umap/static/umap/locale/da.json +2 -2
- umap/static/umap/locale/de.js +17 -8
- umap/static/umap/locale/de.json +17 -8
- umap/static/umap/locale/el.js +2 -2
- umap/static/umap/locale/el.json +2 -2
- umap/static/umap/locale/en.js +15 -4
- umap/static/umap/locale/en.json +15 -4
- umap/static/umap/locale/en_US.json +2 -2
- umap/static/umap/locale/es.js +338 -325
- umap/static/umap/locale/es.json +338 -325
- umap/static/umap/locale/et.js +2 -2
- umap/static/umap/locale/et.json +2 -2
- umap/static/umap/locale/eu.js +11 -4
- umap/static/umap/locale/eu.json +11 -4
- umap/static/umap/locale/fa_IR.js +11 -4
- umap/static/umap/locale/fa_IR.json +11 -4
- umap/static/umap/locale/fi.js +2 -2
- umap/static/umap/locale/fi.json +2 -2
- umap/static/umap/locale/fr.js +15 -4
- umap/static/umap/locale/fr.json +15 -4
- umap/static/umap/locale/gl.js +2 -2
- umap/static/umap/locale/gl.json +2 -2
- umap/static/umap/locale/he.js +2 -2
- umap/static/umap/locale/he.json +2 -2
- umap/static/umap/locale/hr.js +2 -2
- umap/static/umap/locale/hr.json +2 -2
- umap/static/umap/locale/hu.js +12 -5
- umap/static/umap/locale/hu.json +12 -5
- umap/static/umap/locale/id.js +2 -2
- umap/static/umap/locale/id.json +2 -2
- umap/static/umap/locale/is.js +2 -2
- umap/static/umap/locale/is.json +2 -2
- umap/static/umap/locale/it.js +2 -2
- umap/static/umap/locale/it.json +2 -2
- umap/static/umap/locale/ja.js +2 -2
- umap/static/umap/locale/ja.json +2 -2
- umap/static/umap/locale/ko.js +2 -2
- umap/static/umap/locale/ko.json +2 -2
- umap/static/umap/locale/lt.js +2 -2
- umap/static/umap/locale/lt.json +2 -2
- umap/static/umap/locale/ms.js +2 -2
- umap/static/umap/locale/ms.json +2 -2
- umap/static/umap/locale/nl.js +2 -2
- umap/static/umap/locale/nl.json +2 -2
- umap/static/umap/locale/no.js +2 -2
- umap/static/umap/locale/no.json +2 -2
- umap/static/umap/locale/pl.js +2 -2
- umap/static/umap/locale/pl.json +2 -2
- umap/static/umap/locale/pl_PL.json +2 -2
- umap/static/umap/locale/pt.js +19 -10
- umap/static/umap/locale/pt.json +19 -10
- umap/static/umap/locale/pt_BR.js +2 -2
- umap/static/umap/locale/pt_BR.json +2 -2
- umap/static/umap/locale/pt_PT.js +13 -4
- umap/static/umap/locale/pt_PT.json +13 -4
- umap/static/umap/locale/ro.js +2 -2
- umap/static/umap/locale/ro.json +2 -2
- umap/static/umap/locale/ru.js +2 -2
- umap/static/umap/locale/ru.json +2 -2
- umap/static/umap/locale/si.js +2 -2
- umap/static/umap/locale/si.json +2 -2
- umap/static/umap/locale/sk_SK.js +2 -2
- umap/static/umap/locale/sk_SK.json +2 -2
- umap/static/umap/locale/sl.js +2 -2
- umap/static/umap/locale/sl.json +2 -2
- umap/static/umap/locale/sr.js +2 -2
- umap/static/umap/locale/sr.json +2 -2
- umap/static/umap/locale/sv.js +2 -2
- umap/static/umap/locale/sv.json +2 -2
- umap/static/umap/locale/th_TH.js +2 -2
- umap/static/umap/locale/th_TH.json +2 -2
- umap/static/umap/locale/tr.js +2 -2
- umap/static/umap/locale/tr.json +2 -2
- umap/static/umap/locale/uk_UA.js +2 -2
- umap/static/umap/locale/uk_UA.json +2 -2
- umap/static/umap/locale/vi.js +2 -2
- umap/static/umap/locale/vi.json +2 -2
- umap/static/umap/locale/vi_VN.json +2 -2
- umap/static/umap/locale/zh.js +2 -2
- umap/static/umap/locale/zh.json +2 -2
- umap/static/umap/locale/zh_CN.json +2 -2
- umap/static/umap/locale/zh_TW.Big5.json +2 -2
- umap/static/umap/locale/zh_TW.js +13 -4
- umap/static/umap/locale/zh_TW.json +13 -4
- umap/static/umap/map.css +44 -29
- umap/static/umap/unittests/hlc.js +165 -0
- umap/static/umap/unittests/sync.js +321 -15
- umap/static/umap/unittests/utils.js +47 -0
- umap/static/umap/vars.css +2 -1
- umap/static/umap/vendors/colorbrewer/colorbrewer.js +309 -317
- umap/static/umap/vendors/dompurify/purify.es.js +15 -16
- umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
- umap/static/umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js +111 -80
- umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +2 -2
- umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +1 -1
- umap/static/umap/vendors/simple-statistics/simple-statistics.min.js +1 -1
- umap/static/umap/vendors/simple-statistics/simple-statistics.min.js.map +1 -1
- umap/templates/umap/css.html +0 -2
- umap/templates/umap/dashboard_menu.html +4 -2
- umap/templates/umap/js.html +0 -5
- umap/templates/umap/map_detail.html +2 -2
- umap/tests/fixtures/test_upload_data.csv +2 -2
- umap/tests/integration/test_anonymous_owned_map.py +1 -0
- umap/tests/integration/test_basics.py +1 -1
- umap/tests/integration/test_browser.py +69 -7
- umap/tests/integration/test_caption.py +3 -3
- umap/tests/integration/test_circles_layer.py +12 -0
- umap/tests/integration/test_cluster.py +53 -0
- umap/tests/integration/test_datalayer.py +2 -1
- umap/tests/integration/test_draw_polygon.py +17 -9
- umap/tests/integration/test_draw_polyline.py +84 -7
- umap/tests/integration/test_edit_datalayer.py +5 -8
- umap/tests/integration/test_edit_map.py +2 -2
- umap/tests/integration/test_edit_marker.py +1 -1
- umap/tests/integration/test_facets_browser.py +3 -3
- umap/tests/integration/test_import.py +1 -0
- umap/tests/integration/test_map.py +1 -0
- umap/tests/integration/test_owned_map.py +1 -1
- umap/tests/integration/test_view_marker.py +63 -0
- umap/tests/integration/test_view_polygon.py +12 -12
- umap/tests/integration/test_websocket_sync.py +65 -3
- umap/tests/test_clean_tilelayer.py +83 -0
- umap/tests/test_datalayer.py +24 -0
- umap/tests/test_map_views.py +20 -0
- umap/tests/test_purge_purgatory.py +25 -0
- umap/tests/test_websocket_server.py +22 -0
- umap/urls.py +5 -1
- umap/views.py +6 -3
- umap/websocket_server.py +130 -27
- {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/METADATA +18 -14
- {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/RECORD +209 -200
- umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css +0 -1
- umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js +0 -7
- {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/WHEEL +0 -0
- {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -234,8 +234,8 @@ def test_can_delete_datalayer(live_server, map, login, datalayer):
|
|
|
234
234
|
expect(layers).to_have_count(1)
|
|
235
235
|
expect(markers).to_have_count(1)
|
|
236
236
|
page.get_by_role("link", name="Manage layers").click()
|
|
237
|
-
page.once("dialog", lambda dialog: dialog.accept())
|
|
238
237
|
page.locator(".panel.right").get_by_title("Delete layer").click()
|
|
238
|
+
page.get_by_role("button", name="OK").click()
|
|
239
239
|
with page.expect_response(re.compile(r".*/datalayer/delete/.*")):
|
|
240
240
|
page.get_by_role("button", name="Save").click()
|
|
241
241
|
expect(markers).to_have_count(0)
|
|
@@ -106,3 +106,66 @@ def test_extended_properties_in_popup(live_server, map, page, bootstrap):
|
|
|
106
106
|
expect(page.get_by_text("Alt: 241")).to_be_visible()
|
|
107
107
|
expect(page.get_by_text("Zoom: 7")).to_be_visible()
|
|
108
108
|
expect(page.get_by_text("Layer: test datalayer")).to_be_visible()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_only_visible_markers_are_added_to_dom(live_server, map, page):
|
|
112
|
+
data = {
|
|
113
|
+
"type": "FeatureCollection",
|
|
114
|
+
"features": [
|
|
115
|
+
{
|
|
116
|
+
"type": "Feature",
|
|
117
|
+
"properties": {
|
|
118
|
+
"name": "marker 1",
|
|
119
|
+
"description": "added to dom",
|
|
120
|
+
},
|
|
121
|
+
"geometry": {
|
|
122
|
+
"type": "Point",
|
|
123
|
+
"coordinates": [14.6, 48.5],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"type": "Feature",
|
|
128
|
+
"properties": {
|
|
129
|
+
"name": "marker 2",
|
|
130
|
+
"description": "not added to dom at load",
|
|
131
|
+
},
|
|
132
|
+
"geometry": {
|
|
133
|
+
"type": "Point",
|
|
134
|
+
"coordinates": [12.6, 44.5],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}
|
|
139
|
+
DataLayerFactory(map=map, data=data)
|
|
140
|
+
map.settings["properties"]["showLabel"] = True
|
|
141
|
+
map.save()
|
|
142
|
+
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
|
143
|
+
markers = page.locator(".leaflet-marker-icon")
|
|
144
|
+
tooltips = page.locator(".leaflet-tooltip")
|
|
145
|
+
expect(markers).to_have_count(1)
|
|
146
|
+
expect(tooltips).to_have_count(1)
|
|
147
|
+
|
|
148
|
+
# Zoom in/out to show the other marker
|
|
149
|
+
page.get_by_label("Zoom out").click()
|
|
150
|
+
expect(markers).to_have_count(2)
|
|
151
|
+
expect(tooltips).to_have_count(2)
|
|
152
|
+
page.get_by_label("Zoom in").click()
|
|
153
|
+
expect(markers).to_have_count(1)
|
|
154
|
+
expect(tooltips).to_have_count(1)
|
|
155
|
+
|
|
156
|
+
# Drag map to show/hide the marker
|
|
157
|
+
map_el = page.locator("#map")
|
|
158
|
+
map_el.drag_to(
|
|
159
|
+
map_el,
|
|
160
|
+
source_position={"x": 100, "y": 600},
|
|
161
|
+
target_position={"x": 100, "y": 200},
|
|
162
|
+
)
|
|
163
|
+
expect(markers).to_have_count(2)
|
|
164
|
+
expect(tooltips).to_have_count(2)
|
|
165
|
+
map_el.drag_to(
|
|
166
|
+
map_el,
|
|
167
|
+
source_position={"x": 100, "y": 600},
|
|
168
|
+
target_position={"x": 100, "y": 200},
|
|
169
|
+
)
|
|
170
|
+
expect(markers).to_have_count(1)
|
|
171
|
+
expect(tooltips).to_have_count(1)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
+
from copy import deepcopy
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
4
5
|
from playwright.sync_api import expect
|
|
@@ -33,19 +34,9 @@ DATALAYER_DATA = {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
def bootstrap(map, live_server):
|
|
38
|
-
map.settings["properties"]["zoom"] = 6
|
|
39
|
-
map.settings["geometry"] = {
|
|
40
|
-
"type": "Point",
|
|
41
|
-
"coordinates": [8.429, 53.239],
|
|
42
|
-
}
|
|
43
|
-
map.save()
|
|
37
|
+
def test_should_open_popup_on_click(live_server, map, page):
|
|
44
38
|
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_should_open_popup_on_click(live_server, map, page, bootstrap):
|
|
48
|
-
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
|
39
|
+
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/53.239/8.429")
|
|
49
40
|
polygon = page.locator("path").first
|
|
50
41
|
expect(polygon).to_have_attribute("fill-opacity", "0.3")
|
|
51
42
|
polygon.click()
|
|
@@ -57,3 +48,12 @@ def test_should_open_popup_on_click(live_server, map, page, bootstrap):
|
|
|
57
48
|
# Close popup
|
|
58
49
|
page.locator("#map").click()
|
|
59
50
|
expect(polygon).to_have_attribute("fill-opacity", "0.3")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_should_not_react_to_click_if_interactive_false(live_server, map, page):
|
|
54
|
+
data = deepcopy(DATALAYER_DATA)
|
|
55
|
+
data["features"][0]["properties"]["_umap_options"] = {"interactive": False}
|
|
56
|
+
DataLayerFactory(map=map, data=data)
|
|
57
|
+
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/53.239/8.429")
|
|
58
|
+
polygon = page.locator("path").first
|
|
59
|
+
expect(polygon).to_have_css("pointer-events", "none")
|
|
@@ -69,7 +69,7 @@ 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.get_by_role("
|
|
72
|
+
peerA.get_by_role("button", name="Delete this feature").click()
|
|
73
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)
|
|
@@ -153,7 +153,7 @@ 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.get_by_role("
|
|
156
|
+
peerA.get_by_role("button", name="Delete this feature").click()
|
|
157
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)
|
|
@@ -268,7 +268,7 @@ def test_websocket_connection_can_sync_cloned_polygons(
|
|
|
268
268
|
|
|
269
269
|
# Clone on peer B and save
|
|
270
270
|
b_polygon.click(button="right")
|
|
271
|
-
peerB.get_by_role("
|
|
271
|
+
peerB.get_by_role("button", name="Clone this feature").click()
|
|
272
272
|
|
|
273
273
|
expect(peerB.locator("path")).to_have_count(2)
|
|
274
274
|
|
|
@@ -281,3 +281,65 @@ def test_websocket_connection_can_sync_cloned_polygons(
|
|
|
281
281
|
peerB.get_by_role("button", name="Save").click()
|
|
282
282
|
|
|
283
283
|
expect(peerB.locator("path")).to_have_count(2)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@pytest.mark.xdist_group(name="websockets")
|
|
287
|
+
def test_websocket_connection_can_sync_late_joining_peer(
|
|
288
|
+
new_page, live_server, websocket_server, tilelayer
|
|
289
|
+
):
|
|
290
|
+
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
|
291
|
+
map.settings["properties"]["syncEnabled"] = True
|
|
292
|
+
map.save()
|
|
293
|
+
DataLayerFactory(map=map, data={})
|
|
294
|
+
|
|
295
|
+
# Create first peer (A) and have it join immediately
|
|
296
|
+
peerA = new_page("Page A")
|
|
297
|
+
peerA.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
298
|
+
|
|
299
|
+
# Add a marker from peer A
|
|
300
|
+
a_create_marker = peerA.get_by_title("Draw a marker")
|
|
301
|
+
expect(a_create_marker).to_be_visible()
|
|
302
|
+
a_create_marker.click()
|
|
303
|
+
|
|
304
|
+
a_map_el = peerA.locator("#map")
|
|
305
|
+
a_map_el.click(position={"x": 220, "y": 220})
|
|
306
|
+
peerA.locator("body").type("First marker")
|
|
307
|
+
peerA.locator("body").press("Escape")
|
|
308
|
+
|
|
309
|
+
# Add a polygon from peer A
|
|
310
|
+
create_polygon = peerA.locator(".leaflet-control-toolbar ").get_by_title(
|
|
311
|
+
"Draw a polygon"
|
|
312
|
+
)
|
|
313
|
+
create_polygon.click()
|
|
314
|
+
|
|
315
|
+
a_map_el.click(position={"x": 200, "y": 200})
|
|
316
|
+
a_map_el.click(position={"x": 100, "y": 200})
|
|
317
|
+
a_map_el.click(position={"x": 100, "y": 100})
|
|
318
|
+
a_map_el.click(position={"x": 200, "y": 100})
|
|
319
|
+
a_map_el.click(position={"x": 200, "y": 100})
|
|
320
|
+
peerA.keyboard.press("Escape")
|
|
321
|
+
|
|
322
|
+
# Now create peer B and have it join
|
|
323
|
+
peerB = new_page("Page B")
|
|
324
|
+
peerB.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
325
|
+
|
|
326
|
+
# Check if peer B has received all the updates
|
|
327
|
+
b_marker_pane = peerB.locator(".leaflet-marker-pane > div")
|
|
328
|
+
b_polygons = peerB.locator(".leaflet-overlay-pane path[fill='DarkBlue']")
|
|
329
|
+
|
|
330
|
+
expect(b_marker_pane).to_have_count(1)
|
|
331
|
+
expect(b_polygons).to_have_count(1)
|
|
332
|
+
|
|
333
|
+
# Verify marker properties
|
|
334
|
+
peerB.locator(".leaflet-marker-icon").first.click()
|
|
335
|
+
peerB.get_by_role("link", name="Toggle edit mode (⇧+Click)").click()
|
|
336
|
+
expect(peerB.locator('input[name="name"]')).to_have_value("First marker")
|
|
337
|
+
|
|
338
|
+
# Verify polygon exists (we've already checked the count)
|
|
339
|
+
b_polygon = peerB.locator("path")
|
|
340
|
+
expect(b_polygon).to_be_visible()
|
|
341
|
+
|
|
342
|
+
# Optional: Verify polygon properties if you have any specific ones set
|
|
343
|
+
|
|
344
|
+
# Clean up: close edit mode
|
|
345
|
+
peerB.locator("body").press("Escape")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from django.core.management import call_command
|
|
3
|
+
|
|
4
|
+
from umap.models import Map
|
|
5
|
+
|
|
6
|
+
pytestmark = pytest.mark.django_db
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_can_delete_tilelayer_from_settings(map):
|
|
10
|
+
map.settings["properties"]["tilelayer"] = {
|
|
11
|
+
"name": "My TileLayer",
|
|
12
|
+
"maxZoom": 18,
|
|
13
|
+
"minZoom": 0,
|
|
14
|
+
"attribution": "My attribution",
|
|
15
|
+
"url_template": "http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
16
|
+
}
|
|
17
|
+
map.save()
|
|
18
|
+
# Make sure its saved
|
|
19
|
+
map = Map.objects.get(pk=map.pk)
|
|
20
|
+
assert "tilelayer" in map.settings["properties"]
|
|
21
|
+
call_command(
|
|
22
|
+
"clean_tilelayer", "http://{s}.foo.bar.baz/{z}/{x}/{y}.png", "--no-input"
|
|
23
|
+
)
|
|
24
|
+
map = Map.objects.get(pk=map.pk)
|
|
25
|
+
assert "tilelayer" not in map.settings["properties"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_can_replace_tilelayer_url_in_map_settings(map):
|
|
29
|
+
map.settings["properties"]["tilelayer"] = {
|
|
30
|
+
"name": "My TileLayer",
|
|
31
|
+
"maxZoom": 18,
|
|
32
|
+
"minZoom": 0,
|
|
33
|
+
"attribution": "My attribution",
|
|
34
|
+
"url_template": "http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
35
|
+
}
|
|
36
|
+
map.save()
|
|
37
|
+
new = "https://{s}.foo.bar.baz/{z}/{x}/{y}.png"
|
|
38
|
+
call_command(
|
|
39
|
+
"clean_tilelayer",
|
|
40
|
+
"http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
41
|
+
new,
|
|
42
|
+
"--no-input",
|
|
43
|
+
)
|
|
44
|
+
map = Map.objects.get(pk=map.pk)
|
|
45
|
+
assert map.settings["properties"]["tilelayer"]["url_template"] == new
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_can_replace_tilelayer_by_name_in_map_settings(map, tilelayer):
|
|
49
|
+
map.settings["properties"]["tilelayer"] = {
|
|
50
|
+
"name": "My TileLayer",
|
|
51
|
+
"maxZoom": 18,
|
|
52
|
+
"minZoom": 0,
|
|
53
|
+
"attribution": "My attribution",
|
|
54
|
+
"url_template": "http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
55
|
+
}
|
|
56
|
+
map.save()
|
|
57
|
+
call_command(
|
|
58
|
+
"clean_tilelayer",
|
|
59
|
+
"http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
60
|
+
tilelayer.name,
|
|
61
|
+
"--no-input",
|
|
62
|
+
)
|
|
63
|
+
map = Map.objects.get(pk=map.pk)
|
|
64
|
+
assert map.settings["properties"]["tilelayer"] == tilelayer.json
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_can_replace_tilelayer_by_id_in_map_settings(map, tilelayer):
|
|
68
|
+
map.settings["properties"]["tilelayer"] = {
|
|
69
|
+
"name": "My TileLayer",
|
|
70
|
+
"maxZoom": 18,
|
|
71
|
+
"minZoom": 0,
|
|
72
|
+
"attribution": "My attribution",
|
|
73
|
+
"url_template": "http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
74
|
+
}
|
|
75
|
+
map.save()
|
|
76
|
+
call_command(
|
|
77
|
+
"clean_tilelayer",
|
|
78
|
+
"http://{s}.foo.bar.baz/{z}/{x}/{y}.png",
|
|
79
|
+
tilelayer.pk,
|
|
80
|
+
"--no-input",
|
|
81
|
+
)
|
|
82
|
+
map = Map.objects.get(pk=map.pk)
|
|
83
|
+
assert map.settings["properties"]["tilelayer"] == tilelayer.json
|
umap/tests/test_datalayer.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import tempfile
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -269,3 +270,26 @@ def test_anonymous_can_edit_in_inherit_mode_and_map_in_public_mode(
|
|
|
269
270
|
map.save()
|
|
270
271
|
fake_request.user = AnonymousUser()
|
|
271
272
|
assert datalayer.can_edit(fake_request)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def test_should_remove_all_versions_on_delete(map, settings):
|
|
276
|
+
settings.UMAP_PURGATORY_ROOT = tempfile.mkdtemp()
|
|
277
|
+
datalayer = DataLayerFactory(uuid="0f1161c0-c07f-4ba4-86c5-8d8981d8a813", old_id=17)
|
|
278
|
+
root = Path(datalayer.storage_root())
|
|
279
|
+
before = len(datalayer.geojson.storage.listdir(root)[1])
|
|
280
|
+
other = "123456_1440918637.geojson"
|
|
281
|
+
files = [
|
|
282
|
+
f"{datalayer.pk}_1440924889.geojson",
|
|
283
|
+
f"{datalayer.pk}_1440923687.geojson",
|
|
284
|
+
f"{datalayer.pk}_1440918637.geojson",
|
|
285
|
+
f"{datalayer.old_id}_1440918537.geojson",
|
|
286
|
+
other,
|
|
287
|
+
]
|
|
288
|
+
for path in files:
|
|
289
|
+
datalayer.geojson.storage.save(root / path, ContentFile("{}"))
|
|
290
|
+
datalayer.geojson.storage.save(root / f"{path}.gz", ContentFile("{}"))
|
|
291
|
+
assert len(datalayer.geojson.storage.listdir(root)[1]) == 10 + before
|
|
292
|
+
datalayer.delete()
|
|
293
|
+
found = datalayer.geojson.storage.listdir(root)[1]
|
|
294
|
+
assert found == [other, f"{other}.gz"]
|
|
295
|
+
assert len(list(Path(settings.UMAP_PURGATORY_ROOT).iterdir())) == 4 + before
|
umap/tests/test_map_views.py
CHANGED
|
@@ -368,6 +368,7 @@ def test_anonymous_create(cookieclient, post_data):
|
|
|
368
368
|
assert (
|
|
369
369
|
created_map.get_anonymous_edit_url() in j["permissions"]["anonymous_edit_url"]
|
|
370
370
|
)
|
|
371
|
+
assert j["user"]["is_owner"] is True
|
|
371
372
|
assert created_map.name == name
|
|
372
373
|
key, value = created_map.signed_cookie_elements
|
|
373
374
|
assert key in cookieclient.cookies
|
|
@@ -860,3 +861,22 @@ def test_ogp_links(client, map, datalayer):
|
|
|
860
861
|
assert f'<meta property="og:title" content="{map.name}" />' in content
|
|
861
862
|
assert f'<meta property="og:description" content="{map.description}" />' in content
|
|
862
863
|
assert '<meta property="og:site_name" content="uMap" />' in content
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
def test_non_public_map_should_have_noindex_meta(client, map, datalayer):
|
|
867
|
+
map.share_status = Map.OPEN
|
|
868
|
+
map.save()
|
|
869
|
+
response = client.get(map.get_absolute_url())
|
|
870
|
+
assert response.status_code == 200
|
|
871
|
+
assert (
|
|
872
|
+
'<meta name="robots" content="noindex,nofollow">' in response.content.decode()
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def test_demo_instance_should_have_noindex(client, map, datalayer, settings):
|
|
877
|
+
settings.UMAP_DEMO_SITE = True
|
|
878
|
+
response = client.get(map.get_absolute_url())
|
|
879
|
+
assert response.status_code == 200
|
|
880
|
+
assert (
|
|
881
|
+
'<meta name="robots" content="noindex,nofollow">' in response.content.decode()
|
|
882
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from django.core.management import call_command
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_purge_purgatory(settings):
|
|
9
|
+
settings.UMAP_PURGATORY_ROOT = tempfile.mkdtemp()
|
|
10
|
+
root = Path(settings.UMAP_PURGATORY_ROOT)
|
|
11
|
+
old = root / "old.json"
|
|
12
|
+
old.write_text("{}")
|
|
13
|
+
stat = old.stat()
|
|
14
|
+
os.utime(old, times=(stat.st_mtime - 31 * 86400, stat.st_mtime - 31 * 86400))
|
|
15
|
+
recent = root / "recent.json"
|
|
16
|
+
recent.write_text("{}")
|
|
17
|
+
stat = recent.stat()
|
|
18
|
+
os.utime(recent, times=(stat.st_mtime - 8 * 86400, stat.st_mtime - 8 * 86400))
|
|
19
|
+
now = root / "now.json"
|
|
20
|
+
now.write_text("{}")
|
|
21
|
+
assert {f.name for f in root.iterdir()} == {"old.json", "recent.json", "now.json"}
|
|
22
|
+
call_command("purge_purgatory")
|
|
23
|
+
assert {f.name for f in root.iterdir()} == {"recent.json", "now.json"}
|
|
24
|
+
call_command("purge_purgatory", "--days=7")
|
|
25
|
+
assert {f.name for f in root.iterdir()} == {"now.json"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from umap.websocket_server import OperationMessage, PeerMessage, Request, ServerRequest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_messages_are_parsed_correctly():
|
|
5
|
+
server = Request.model_validate(dict(kind="Server", action="list-peers")).root
|
|
6
|
+
assert type(server) is ServerRequest
|
|
7
|
+
|
|
8
|
+
operation = Request.model_validate(
|
|
9
|
+
dict(
|
|
10
|
+
kind="OperationMessage",
|
|
11
|
+
verb="upsert",
|
|
12
|
+
subject="map",
|
|
13
|
+
metadata={},
|
|
14
|
+
key="key",
|
|
15
|
+
)
|
|
16
|
+
).root
|
|
17
|
+
assert type(operation) is OperationMessage
|
|
18
|
+
|
|
19
|
+
peer_message = Request.model_validate(
|
|
20
|
+
dict(kind="PeerMessage", sender="Alice", recipient="Bob", message={})
|
|
21
|
+
).root
|
|
22
|
+
assert type(peer_message) is PeerMessage
|
umap/urls.py
CHANGED
|
@@ -115,11 +115,15 @@ i18n_urls += decorated_patterns(
|
|
|
115
115
|
name="map_star",
|
|
116
116
|
),
|
|
117
117
|
path("me", views.user_dashboard, name="user_dashboard"),
|
|
118
|
-
path("me/profile", views.user_profile, name="user_profile"),
|
|
119
118
|
path("me/download", views.user_download, name="user_download"),
|
|
120
119
|
path("me/teams", views.UserTeams.as_view(), name="user_teams"),
|
|
121
120
|
path("team/create/", views.TeamNew.as_view(), name="team_new"),
|
|
122
121
|
)
|
|
122
|
+
|
|
123
|
+
if settings.UMAP_ALLOW_EDIT_PROFILE:
|
|
124
|
+
i18n_urls.append(
|
|
125
|
+
path("me/profile", login_required(views.user_profile), name="user_profile")
|
|
126
|
+
)
|
|
123
127
|
i18n_urls += decorated_patterns(
|
|
124
128
|
[login_required, team_members_only],
|
|
125
129
|
path("team/<int:pk>/edit/", views.TeamUpdate.as_view(), name="team_update"),
|
umap/views.py
CHANGED
|
@@ -863,15 +863,17 @@ class MapCreate(FormLessEditMixin, PermissionsMixin, SessionMixin, CreateView):
|
|
|
863
863
|
form.instance.owner = self.request.user
|
|
864
864
|
self.object = form.save()
|
|
865
865
|
permissions = self.get_permissions()
|
|
866
|
+
user_data = self.get_user_data()
|
|
866
867
|
# User does not have the cookie yet.
|
|
867
868
|
if not self.object.owner:
|
|
868
869
|
anonymous_url = self.object.get_anonymous_edit_url()
|
|
869
870
|
permissions["anonymous_edit_url"] = anonymous_url
|
|
871
|
+
user_data["is_owner"] = True
|
|
870
872
|
response = simple_json_response(
|
|
871
873
|
id=self.object.pk,
|
|
872
874
|
url=self.object.get_absolute_url(),
|
|
873
875
|
permissions=permissions,
|
|
874
|
-
user=
|
|
876
|
+
user=user_data,
|
|
875
877
|
)
|
|
876
878
|
if not self.request.user.is_authenticated:
|
|
877
879
|
key, value = self.object.signed_cookie_elements
|
|
@@ -908,7 +910,7 @@ def get_websocket_auth_token(request, map_id, map_inst):
|
|
|
908
910
|
return simple_json_response(token=signed_token)
|
|
909
911
|
|
|
910
912
|
|
|
911
|
-
class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
|
913
|
+
class MapUpdate(FormLessEditMixin, PermissionsMixin, SessionMixin, UpdateView):
|
|
912
914
|
model = Map
|
|
913
915
|
form_class = MapSettingsForm
|
|
914
916
|
pk_url_kwarg = "map_id"
|
|
@@ -920,6 +922,7 @@ class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
|
|
920
922
|
id=self.object.pk,
|
|
921
923
|
url=self.object.get_absolute_url(),
|
|
922
924
|
permissions=self.get_permissions(),
|
|
925
|
+
user=self.get_user_data(),
|
|
923
926
|
)
|
|
924
927
|
|
|
925
928
|
|
|
@@ -1359,7 +1362,7 @@ def logout(request):
|
|
|
1359
1362
|
|
|
1360
1363
|
class LoginPopupEnd(TemplateView):
|
|
1361
1364
|
"""
|
|
1362
|
-
End of a
|
|
1365
|
+
End of a login process in popup.
|
|
1363
1366
|
Basically close the popup.
|
|
1364
1367
|
"""
|
|
1365
1368
|
|