umap-project 2.9.3__py3-none-any.whl → 3.0.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/context_processors.py +1 -0
- umap/forms.py +1 -2
- umap/locale/de/LC_MESSAGES/django.mo +0 -0
- umap/locale/de/LC_MESSAGES/django.po +218 -96
- umap/locale/en/LC_MESSAGES/django.po +128 -52
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +128 -52
- umap/locale/hu/LC_MESSAGES/django.mo +0 -0
- umap/locale/hu/LC_MESSAGES/django.po +209 -88
- umap/locale/is/LC_MESSAGES/django.mo +0 -0
- umap/locale/is/LC_MESSAGES/django.po +296 -175
- umap/migrations/0027_map_tags.py +23 -0
- umap/models.py +13 -2
- umap/settings/base.py +23 -5
- umap/static/umap/base.css +41 -8
- umap/static/umap/content.css +72 -37
- umap/static/umap/css/bar.css +43 -21
- umap/static/umap/css/dialog.css +4 -1
- umap/static/umap/css/form.css +40 -27
- umap/static/umap/css/icon.css +11 -1
- umap/static/umap/css/importers.css +7 -0
- umap/static/umap/img/16-white.svg +23 -2
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/24.svg +4 -4
- umap/static/umap/img/home.svg +7 -0
- umap/static/umap/img/importers/banfr.svg +1 -0
- umap/static/umap/img/marker.svg +2 -5
- umap/static/umap/img/source/16-white.svg +24 -3
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/img/source/24.svg +5 -5
- umap/static/umap/img/target.svg +1 -0
- umap/static/umap/js/components/alerts/alert.js +0 -1
- umap/static/umap/js/modules/browser.js +4 -4
- umap/static/umap/js/modules/caption.js +1 -1
- umap/static/umap/js/modules/data/features.js +25 -25
- umap/static/umap/js/modules/data/layer.js +91 -97
- umap/static/umap/js/modules/facets.js +9 -5
- umap/static/umap/js/modules/form/builder.js +21 -29
- umap/static/umap/js/modules/form/fields.js +40 -14
- umap/static/umap/js/modules/formatter.js +1 -1
- umap/static/umap/js/modules/global.js +9 -5
- umap/static/umap/js/modules/help.js +18 -5
- umap/static/umap/js/modules/importer.js +5 -2
- umap/static/umap/js/modules/importers/banfr.js +93 -0
- umap/static/umap/js/modules/importers/cadastrefr.js +2 -2
- umap/static/umap/js/modules/importers/communesfr.js +1 -1
- umap/static/umap/js/modules/permissions.js +20 -10
- umap/static/umap/js/modules/rendering/icon.js +15 -2
- umap/static/umap/js/modules/rendering/layers/classified.js +7 -7
- umap/static/umap/js/modules/rendering/layers/cluster.js +2 -2
- umap/static/umap/js/modules/rendering/layers/heat.js +4 -4
- umap/static/umap/js/modules/rendering/map.js +14 -6
- umap/static/umap/js/modules/rendering/popup.js +2 -2
- umap/static/umap/js/modules/rendering/template.js +3 -3
- umap/static/umap/js/modules/rendering/ui.js +17 -11
- umap/static/umap/js/modules/rules.js +13 -16
- umap/static/umap/js/modules/schema.js +23 -1
- umap/static/umap/js/modules/share.js +1 -1
- umap/static/umap/js/modules/slideshow.js +1 -0
- umap/static/umap/js/modules/sync/engine.js +141 -19
- umap/static/umap/js/modules/sync/undo.js +101 -0
- umap/static/umap/js/modules/sync/updaters.js +51 -28
- umap/static/umap/js/modules/tableeditor.js +1 -1
- umap/static/umap/js/modules/ui/bar.js +61 -21
- umap/static/umap/js/modules/ui/tooltip.js +1 -1
- umap/static/umap/js/modules/umap.js +190 -176
- umap/static/umap/js/modules/utils.js +30 -4
- umap/static/umap/js/umap.controls.js +82 -38
- umap/static/umap/locale/am_ET.js +11 -6
- umap/static/umap/locale/am_ET.json +11 -6
- umap/static/umap/locale/ar.js +11 -6
- umap/static/umap/locale/ar.json +11 -6
- umap/static/umap/locale/ast.js +11 -6
- umap/static/umap/locale/ast.json +11 -6
- umap/static/umap/locale/bg.js +11 -6
- umap/static/umap/locale/bg.json +11 -6
- umap/static/umap/locale/br.js +12 -7
- umap/static/umap/locale/br.json +12 -7
- umap/static/umap/locale/ca.js +11 -6
- umap/static/umap/locale/ca.json +11 -6
- umap/static/umap/locale/cs_CZ.js +11 -6
- umap/static/umap/locale/cs_CZ.json +11 -6
- umap/static/umap/locale/da.js +11 -6
- umap/static/umap/locale/da.json +11 -6
- umap/static/umap/locale/de.js +47 -42
- umap/static/umap/locale/de.json +47 -42
- umap/static/umap/locale/el.js +11 -6
- umap/static/umap/locale/el.json +11 -6
- umap/static/umap/locale/en.js +11 -6
- umap/static/umap/locale/en.json +11 -6
- umap/static/umap/locale/en_US.json +11 -6
- umap/static/umap/locale/es.js +11 -6
- umap/static/umap/locale/es.json +11 -6
- umap/static/umap/locale/et.js +11 -6
- umap/static/umap/locale/et.json +11 -6
- umap/static/umap/locale/eu.js +11 -6
- umap/static/umap/locale/eu.json +11 -6
- umap/static/umap/locale/fa_IR.js +11 -6
- umap/static/umap/locale/fa_IR.json +11 -6
- umap/static/umap/locale/fi.js +11 -6
- umap/static/umap/locale/fi.json +11 -6
- umap/static/umap/locale/fr.js +11 -6
- umap/static/umap/locale/fr.json +11 -6
- umap/static/umap/locale/gl.js +12 -7
- umap/static/umap/locale/gl.json +12 -7
- umap/static/umap/locale/he.js +11 -6
- umap/static/umap/locale/he.json +11 -6
- umap/static/umap/locale/hr.js +11 -6
- umap/static/umap/locale/hr.json +11 -6
- umap/static/umap/locale/hu.js +25 -20
- umap/static/umap/locale/hu.json +25 -20
- umap/static/umap/locale/id.js +11 -6
- umap/static/umap/locale/id.json +11 -6
- umap/static/umap/locale/is.js +151 -146
- umap/static/umap/locale/is.json +151 -146
- umap/static/umap/locale/it.js +11 -6
- umap/static/umap/locale/it.json +11 -6
- umap/static/umap/locale/ja.js +11 -6
- umap/static/umap/locale/ja.json +11 -6
- umap/static/umap/locale/ko.js +11 -6
- umap/static/umap/locale/ko.json +11 -6
- umap/static/umap/locale/lt.js +11 -6
- umap/static/umap/locale/lt.json +11 -6
- umap/static/umap/locale/ms.js +11 -6
- umap/static/umap/locale/ms.json +11 -6
- umap/static/umap/locale/nl.js +12 -7
- umap/static/umap/locale/nl.json +12 -7
- umap/static/umap/locale/no.js +11 -6
- umap/static/umap/locale/no.json +11 -6
- umap/static/umap/locale/pl.js +11 -6
- umap/static/umap/locale/pl.json +11 -6
- umap/static/umap/locale/pl_PL.json +11 -6
- umap/static/umap/locale/pt.js +11 -6
- umap/static/umap/locale/pt.json +11 -6
- umap/static/umap/locale/pt_BR.js +11 -6
- umap/static/umap/locale/pt_BR.json +11 -6
- umap/static/umap/locale/pt_PT.js +11 -6
- umap/static/umap/locale/pt_PT.json +11 -6
- umap/static/umap/locale/ro.js +11 -6
- umap/static/umap/locale/ro.json +11 -6
- umap/static/umap/locale/ru.js +11 -6
- umap/static/umap/locale/ru.json +11 -6
- umap/static/umap/locale/sk_SK.js +11 -6
- umap/static/umap/locale/sk_SK.json +11 -6
- umap/static/umap/locale/sl.js +11 -6
- umap/static/umap/locale/sl.json +11 -6
- umap/static/umap/locale/sr.js +11 -6
- umap/static/umap/locale/sr.json +11 -6
- umap/static/umap/locale/sv.js +11 -6
- umap/static/umap/locale/sv.json +11 -6
- umap/static/umap/locale/th_TH.js +11 -6
- umap/static/umap/locale/th_TH.json +11 -6
- umap/static/umap/locale/tr.js +11 -6
- umap/static/umap/locale/tr.json +11 -6
- umap/static/umap/locale/uk_UA.js +11 -6
- umap/static/umap/locale/uk_UA.json +11 -6
- umap/static/umap/locale/vi.js +11 -6
- umap/static/umap/locale/vi.json +11 -6
- umap/static/umap/locale/vi_VN.json +11 -6
- umap/static/umap/locale/zh.js +11 -6
- umap/static/umap/locale/zh.json +11 -6
- umap/static/umap/locale/zh_CN.json +11 -6
- umap/static/umap/locale/zh_TW.Big5.json +11 -6
- umap/static/umap/locale/zh_TW.js +19 -14
- umap/static/umap/locale/zh_TW.json +19 -14
- umap/static/umap/map.css +58 -28
- umap/static/umap/unittests/sync.js +0 -57
- umap/static/umap/unittests/utils.js +47 -0
- umap/static/umap/vars.css +5 -2
- umap/static/umap/vendors/photon/leaflet.photon.js +3 -0
- umap/sync/payloads.py +3 -2
- umap/templates/auth/user_detail.html +1 -1
- umap/templates/auth/user_stars.html +1 -1
- umap/templates/umap/content.html +17 -12
- umap/templates/umap/home.html +7 -5
- umap/templates/umap/map_fragment.html +1 -1
- umap/templates/umap/map_list.html +20 -13
- umap/templates/umap/search.html +7 -3
- umap/templates/umap/search_bar.html +13 -11
- umap/templates/umap/team_detail.html +1 -1
- umap/tests/base.py +2 -1
- umap/tests/fixtures/remote_data.umap +55 -0
- umap/tests/fixtures/test_upload_data_with_iconurl.umap +122 -0
- umap/tests/integration/test_browser.py +1 -3
- umap/tests/integration/test_conditional_rules.py +3 -0
- umap/tests/integration/test_edit_datalayer.py +2 -7
- umap/tests/integration/test_edit_map.py +15 -0
- umap/tests/integration/test_edit_polygon.py +1 -2
- umap/tests/integration/test_import.py +59 -2
- umap/tests/integration/test_optimistic_merge.py +4 -3
- umap/tests/integration/test_owned_map.py +0 -1
- umap/tests/integration/test_save.py +2 -4
- umap/tests/integration/test_undo_redo.py +267 -0
- umap/tests/integration/test_websocket_sync.py +78 -11
- umap/tests/settings.py +1 -3
- umap/tests/test_datalayer_s3.py +1 -0
- umap/tests/test_map_views.py +1 -0
- umap/tests/test_views.py +34 -0
- umap/utils.py +1 -1
- umap/views.py +23 -2
- {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/METADATA +13 -12
- {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/RECORD +206 -208
- umap/static/umap/js/modules/saving.js +0 -52
- umap/static/umap/test/.eslintrc +0 -21
- umap/static/umap/test/DataLayer.js +0 -463
- umap/static/umap/test/Feature.js +0 -131
- umap/static/umap/test/Map.js +0 -37
- umap/static/umap/test/Marker.js +0 -126
- umap/static/umap/test/Polygon.js +0 -111
- umap/static/umap/test/Polyline.js +0 -286
- umap/static/umap/test/Util.js +0 -28
- umap/static/umap/test/_pre.js +0 -455
- umap/static/umap/test/index.html +0 -139
- {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/WHEEL +0 -0
- {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.9.3.dist-info → umap_project-3.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from playwright.sync_api import expect
|
|
6
|
+
|
|
7
|
+
from umap.models import Map, TileLayer
|
|
8
|
+
|
|
9
|
+
from ..base import DataLayerFactory
|
|
10
|
+
|
|
11
|
+
pytestmark = pytest.mark.django_db
|
|
12
|
+
|
|
13
|
+
DATALAYER_DATA = {
|
|
14
|
+
"type": "FeatureCollection",
|
|
15
|
+
"features": [
|
|
16
|
+
{
|
|
17
|
+
"type": "Feature",
|
|
18
|
+
"properties": {
|
|
19
|
+
"name": "name poly",
|
|
20
|
+
},
|
|
21
|
+
"id": "gyNzM",
|
|
22
|
+
"geometry": {
|
|
23
|
+
"type": "Polygon",
|
|
24
|
+
"coordinates": [
|
|
25
|
+
[
|
|
26
|
+
[11.25, 53.585984],
|
|
27
|
+
[10.151367, 52.975108],
|
|
28
|
+
[12.689209, 52.167194],
|
|
29
|
+
[14.084473, 53.199452],
|
|
30
|
+
[12.634277, 53.618579],
|
|
31
|
+
[11.25, 53.585984],
|
|
32
|
+
[11.25, 53.585984],
|
|
33
|
+
],
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def map_with_polygon(map, live_server):
|
|
43
|
+
map.settings["properties"]["zoom"] = 6
|
|
44
|
+
map.settings["geometry"] = {
|
|
45
|
+
"type": "Point",
|
|
46
|
+
"coordinates": [8.429, 53.239],
|
|
47
|
+
}
|
|
48
|
+
map.edit_status = Map.ANONYMOUS
|
|
49
|
+
map.save()
|
|
50
|
+
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
|
51
|
+
return map
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_can_undo_redo_map_name_change(page, live_server, tilelayer):
|
|
55
|
+
page.goto(f"{live_server.url}/en/map/new/")
|
|
56
|
+
|
|
57
|
+
expect(page.locator(".edit-undo")).to_be_disabled()
|
|
58
|
+
expect(page.locator(".edit-redo")).to_be_disabled()
|
|
59
|
+
page.get_by_title("Edit map name and caption").click()
|
|
60
|
+
name_input = page.locator('.map-metadata input[name="name"]')
|
|
61
|
+
expect(name_input).to_be_visible()
|
|
62
|
+
name_input.click()
|
|
63
|
+
name_input.press("Control+a")
|
|
64
|
+
name_input.fill("New map name")
|
|
65
|
+
expect(page.locator(".edit-undo")).to_be_enabled()
|
|
66
|
+
expect(page.locator(".edit-redo")).to_be_disabled()
|
|
67
|
+
map_name = page.locator(".umap-main-edit-toolbox .map-name")
|
|
68
|
+
expect(map_name).to_have_text("New map name")
|
|
69
|
+
name_input.fill("New name again")
|
|
70
|
+
expect(map_name).to_have_text("New name again")
|
|
71
|
+
|
|
72
|
+
page.locator(".edit-undo").click()
|
|
73
|
+
expect(map_name).to_have_text("New map name")
|
|
74
|
+
expect(page.locator(".edit-undo")).to_be_enabled()
|
|
75
|
+
expect(page.locator(".edit-redo")).to_be_enabled()
|
|
76
|
+
|
|
77
|
+
page.locator(".edit-redo").click()
|
|
78
|
+
expect(map_name).to_have_text("New name again")
|
|
79
|
+
expect(page.locator(".edit-undo")).to_be_enabled()
|
|
80
|
+
expect(page.locator(".edit-redo")).to_be_disabled()
|
|
81
|
+
|
|
82
|
+
page.locator(".edit-undo").click()
|
|
83
|
+
expect(map_name).to_have_text("New map name")
|
|
84
|
+
expect(page.locator(".edit-undo")).to_be_enabled()
|
|
85
|
+
expect(page.locator(".edit-redo")).to_be_enabled()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_can_undo_redo_layer_color_change(
|
|
89
|
+
page, map_with_polygon, live_server, tilelayer
|
|
90
|
+
):
|
|
91
|
+
page.goto(f"{live_server.url}{map_with_polygon.get_absolute_url()}?edit")
|
|
92
|
+
|
|
93
|
+
expect(page.locator(".edit-undo")).to_be_disabled()
|
|
94
|
+
expect(page.locator(".edit-redo")).to_be_disabled()
|
|
95
|
+
page.get_by_role("button", name="Manage layers").click()
|
|
96
|
+
page.locator(".panel").get_by_title("Edit", exact=True).click()
|
|
97
|
+
page.get_by_text("Shape properties").click()
|
|
98
|
+
page.locator(".umap-field-color .define").click()
|
|
99
|
+
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)
|
|
100
|
+
page.get_by_title("DarkRed").first.click()
|
|
101
|
+
expect(page.locator(".leaflet-overlay-pane path[fill='DarkRed']")).to_have_count(1)
|
|
102
|
+
expect(page.locator(".edit-undo")).to_be_enabled()
|
|
103
|
+
expect(page.locator(".edit-redo")).to_be_disabled()
|
|
104
|
+
|
|
105
|
+
page.locator(".edit-undo").click()
|
|
106
|
+
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)
|
|
107
|
+
expect(page.locator(".leaflet-overlay-pane path[fill='DarkRed']")).to_have_count(0)
|
|
108
|
+
expect(page.locator(".edit-undo")).to_be_disabled()
|
|
109
|
+
expect(page.locator(".edit-redo")).to_be_enabled()
|
|
110
|
+
|
|
111
|
+
page.locator(".edit-redo").click()
|
|
112
|
+
expect(page.locator(".leaflet-overlay-pane path[fill='DarkRed']")).to_have_count(1)
|
|
113
|
+
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(0)
|
|
114
|
+
expect(page.locator(".edit-undo")).to_be_enabled()
|
|
115
|
+
expect(page.locator(".edit-redo")).to_be_disabled()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_can_undo_redo_tilelayer_change(live_server, page, openmap, tilelayer):
|
|
119
|
+
TileLayer.objects.create(
|
|
120
|
+
url_template="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
|
|
121
|
+
attribution="OSM/Carto",
|
|
122
|
+
name="Black Tiles",
|
|
123
|
+
)
|
|
124
|
+
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
125
|
+
old_pattern = re.compile(
|
|
126
|
+
r"https://[abc]{1}.tile.openstreetmap.fr/osmfr/\d+/\d+/\d+.png"
|
|
127
|
+
)
|
|
128
|
+
tiles = page.locator(".leaflet-tile-pane img")
|
|
129
|
+
expect(tiles.first).to_have_attribute("src", old_pattern)
|
|
130
|
+
|
|
131
|
+
new_pattern = re.compile(
|
|
132
|
+
r"https://[abcd]{1}.basemaps.cartocdn.com/dark_all/\d+/\d+/\d+.png"
|
|
133
|
+
)
|
|
134
|
+
page.get_by_role("button", name="Change tilelayers").click()
|
|
135
|
+
page.locator("li").filter(has_text="Black Tiles").get_by_role("img").click()
|
|
136
|
+
|
|
137
|
+
tiles = page.locator(".leaflet-tile-pane img")
|
|
138
|
+
expect(tiles.first).to_have_attribute("src", new_pattern)
|
|
139
|
+
|
|
140
|
+
page.locator(".edit-undo").click()
|
|
141
|
+
tiles = page.locator(".leaflet-tile-pane img")
|
|
142
|
+
expect(tiles.first).to_have_attribute("src", old_pattern)
|
|
143
|
+
|
|
144
|
+
page.locator(".edit-redo").click()
|
|
145
|
+
tiles = page.locator(".leaflet-tile-pane img")
|
|
146
|
+
expect(tiles.first).to_have_attribute("src", new_pattern)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_can_undo_redo_marker_drag(live_server, page, tilelayer):
|
|
150
|
+
page.goto(f"{live_server.url}/en/map/new")
|
|
151
|
+
|
|
152
|
+
marker = page.locator(".leaflet-marker-icon")
|
|
153
|
+
map = page.locator("#map")
|
|
154
|
+
|
|
155
|
+
# Create a marker
|
|
156
|
+
page.get_by_title("Draw a marker").click()
|
|
157
|
+
map.click(position={"x": 225, "y": 225})
|
|
158
|
+
expect(marker).to_have_count(1)
|
|
159
|
+
|
|
160
|
+
# Drag marker
|
|
161
|
+
old_bbox = marker.bounding_box()
|
|
162
|
+
marker.first.drag_to(map, target_position={"x": 250, "y": 250})
|
|
163
|
+
assert marker.bounding_box() != old_bbox
|
|
164
|
+
|
|
165
|
+
# Undo
|
|
166
|
+
page.locator(".edit-undo").click()
|
|
167
|
+
assert marker.bounding_box() == old_bbox
|
|
168
|
+
|
|
169
|
+
# Redo
|
|
170
|
+
page.locator(".edit-redo").click()
|
|
171
|
+
assert marker.bounding_box() != old_bbox
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_can_undo_redo_polygon_geometry_change(live_server, page, tilelayer):
|
|
175
|
+
page.goto(f"{live_server.url}/en/map/new")
|
|
176
|
+
|
|
177
|
+
# Click on the Draw a polygon button on a new map.
|
|
178
|
+
page.get_by_title("Draw a polygon").click()
|
|
179
|
+
|
|
180
|
+
polygon = page.locator("path[fill='DarkBlue']")
|
|
181
|
+
expect(polygon).to_have_count(0)
|
|
182
|
+
|
|
183
|
+
# Click on the map, it will create a polygon.
|
|
184
|
+
map = page.locator("#map")
|
|
185
|
+
map.click(position={"x": 200, "y": 200})
|
|
186
|
+
map.click(position={"x": 100, "y": 200})
|
|
187
|
+
map.click(position={"x": 100, "y": 100})
|
|
188
|
+
map.click(position={"x": 100, "y": 100})
|
|
189
|
+
|
|
190
|
+
# It is created on peerA, and should be on peerB
|
|
191
|
+
expect(polygon).to_have_count(1)
|
|
192
|
+
old_bbox = polygon.bounding_box()
|
|
193
|
+
|
|
194
|
+
edited_vertex = page.locator(".leaflet-middle-icon:nth-child(3)").first
|
|
195
|
+
edited_vertex.drag_to(map, target_position={"x": 250, "y": 250})
|
|
196
|
+
page.keyboard.press("Escape")
|
|
197
|
+
|
|
198
|
+
assert polygon.bounding_box() != old_bbox
|
|
199
|
+
|
|
200
|
+
page.locator(".edit-undo").click()
|
|
201
|
+
assert polygon.bounding_box() == old_bbox
|
|
202
|
+
|
|
203
|
+
page.locator(".edit-redo").click()
|
|
204
|
+
assert polygon.bounding_box() != old_bbox
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_can_undo_redo_marker_create(live_server, page, tilelayer):
|
|
208
|
+
page.goto(f"{live_server.url}/en/map/new")
|
|
209
|
+
|
|
210
|
+
page.get_by_title("Open Browser").click()
|
|
211
|
+
marker = page.locator(".leaflet-marker-icon")
|
|
212
|
+
map = page.locator("#map")
|
|
213
|
+
|
|
214
|
+
# Create a marker
|
|
215
|
+
page.get_by_title("Draw a marker").click()
|
|
216
|
+
map.click(position={"x": 600, "y": 100})
|
|
217
|
+
expect(marker).to_have_count(1)
|
|
218
|
+
expect(page.locator(".panel .datalayer")).to_have_count(1)
|
|
219
|
+
|
|
220
|
+
page.locator(".edit-undo").click()
|
|
221
|
+
expect(marker).to_have_count(0)
|
|
222
|
+
# Layer still exists
|
|
223
|
+
expect(page.locator(".panel .datalayer")).to_have_count(1)
|
|
224
|
+
|
|
225
|
+
page.locator(".edit-undo").click()
|
|
226
|
+
expect(page.locator(".panel .datalayer")).to_have_count(0)
|
|
227
|
+
|
|
228
|
+
page.locator(".edit-redo").click()
|
|
229
|
+
expect(page.locator(".panel .datalayer")).to_have_count(1)
|
|
230
|
+
|
|
231
|
+
page.locator(".edit-redo").click()
|
|
232
|
+
expect(marker).to_have_count(1)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_undo_redo_import(live_server, page, tilelayer):
|
|
236
|
+
page.goto(f"{live_server.url}/map/new/")
|
|
237
|
+
page.get_by_title("Open Browser").click()
|
|
238
|
+
|
|
239
|
+
page.get_by_title("Import data").click()
|
|
240
|
+
file_input = page.locator("input[type='file']")
|
|
241
|
+
with page.expect_file_chooser() as fc_info:
|
|
242
|
+
file_input.click()
|
|
243
|
+
file_chooser = fc_info.value
|
|
244
|
+
path = Path(__file__).parent.parent / "fixtures/test_upload_data.json"
|
|
245
|
+
file_chooser.set_files(path)
|
|
246
|
+
page.get_by_role("button", name="Import data", exact=True).click()
|
|
247
|
+
# Close the import panel
|
|
248
|
+
page.keyboard.press("Escape")
|
|
249
|
+
|
|
250
|
+
layers = page.locator(".umap-browser .datalayer")
|
|
251
|
+
expect(layers).to_have_count(1)
|
|
252
|
+
|
|
253
|
+
features_count = page.locator(".umap-browser .datalayer-counter")
|
|
254
|
+
expect(features_count).to_have_text("(5)")
|
|
255
|
+
|
|
256
|
+
page.locator(".edit-undo").click()
|
|
257
|
+
expect(features_count).to_be_hidden()
|
|
258
|
+
expect(layers).to_have_count(1)
|
|
259
|
+
|
|
260
|
+
page.locator(".edit-undo").click()
|
|
261
|
+
expect(layers).to_have_count(0)
|
|
262
|
+
|
|
263
|
+
page.locator(".edit-redo").click()
|
|
264
|
+
expect(layers).to_have_count(1)
|
|
265
|
+
|
|
266
|
+
page.locator(".edit-redo").click()
|
|
267
|
+
expect(features_count).to_have_text("(5)")
|
|
@@ -86,7 +86,6 @@ def test_websocket_connection_can_sync_markers(new_page, asgi_live_server, tilel
|
|
|
86
86
|
# Delete a marker from peer A and check it's been deleted on peer B
|
|
87
87
|
a_first_marker.click(button="right")
|
|
88
88
|
peerA.get_by_role("button", name="Delete this feature").click()
|
|
89
|
-
peerA.locator("dialog").get_by_role("button", name="OK").click()
|
|
90
89
|
expect(a_marker_pane).to_have_count(1)
|
|
91
90
|
expect(b_marker_pane).to_have_count(1)
|
|
92
91
|
|
|
@@ -166,7 +165,6 @@ def test_websocket_connection_can_sync_polygons(context, asgi_live_server, tilel
|
|
|
166
165
|
# Delete a polygon from peer A and check it's been deleted on peer B
|
|
167
166
|
a_polygon.click(button="right")
|
|
168
167
|
peerA.get_by_role("button", name="Delete this feature").click()
|
|
169
|
-
peerA.locator("dialog").get_by_role("button", name="OK").click()
|
|
170
168
|
expect(a_polygons).to_have_count(0)
|
|
171
169
|
expect(b_polygons).to_have_count(0)
|
|
172
170
|
|
|
@@ -477,23 +475,22 @@ def test_should_sync_datalayers_delete(new_page, asgi_live_server, tilelayer):
|
|
|
477
475
|
peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
|
|
478
476
|
|
|
479
477
|
peerA.get_by_role("button", name="Open browser").click()
|
|
480
|
-
expect(peerA.get_by_text("datalayer 1")).to_be_visible()
|
|
481
|
-
expect(peerA.get_by_text("datalayer 2")).to_be_visible()
|
|
478
|
+
expect(peerA.locator(".panel").get_by_text("datalayer 1")).to_be_visible()
|
|
479
|
+
expect(peerA.locator(".panel").get_by_text("datalayer 2")).to_be_visible()
|
|
482
480
|
peerB.get_by_role("button", name="Open browser").click()
|
|
483
|
-
expect(peerB.get_by_text("datalayer 1")).to_be_visible()
|
|
484
|
-
expect(peerB.get_by_text("datalayer 2")).to_be_visible()
|
|
481
|
+
expect(peerB.locator(".panel").get_by_text("datalayer 1")).to_be_visible()
|
|
482
|
+
expect(peerB.locator(".panel").get_by_text("datalayer 2")).to_be_visible()
|
|
485
483
|
|
|
486
484
|
# Delete "datalayer 2" in peerA
|
|
487
485
|
peerA.locator(".datalayer").get_by_role("button", name="Delete layer").first.click()
|
|
488
|
-
peerA.
|
|
489
|
-
expect(
|
|
490
|
-
expect(peerB.get_by_text("datalayer 2")).to_be_hidden()
|
|
486
|
+
expect(peerA.locator(".panel").get_by_text("datalayer 2")).to_be_hidden()
|
|
487
|
+
expect(peerB.locator(".panel").get_by_text("datalayer 2")).to_be_hidden()
|
|
491
488
|
|
|
492
489
|
# Save delete to the server
|
|
493
490
|
with peerA.expect_response(re.compile(".*/datalayer/delete/.*")):
|
|
494
491
|
peerA.get_by_role("button", name="Save").click()
|
|
495
|
-
expect(peerA.get_by_text("datalayer 2")).to_be_hidden()
|
|
496
|
-
expect(peerB.get_by_text("datalayer 2")).to_be_hidden()
|
|
492
|
+
expect(peerA.locator(".panel").get_by_text("datalayer 2")).to_be_hidden()
|
|
493
|
+
expect(peerB.locator(".panel").get_by_text("datalayer 2")).to_be_hidden()
|
|
497
494
|
|
|
498
495
|
|
|
499
496
|
@pytest.mark.xdist_group(name="websockets")
|
|
@@ -659,3 +656,73 @@ def test_should_sync_line_on_escape(new_page, asgi_live_server, tilelayer):
|
|
|
659
656
|
|
|
660
657
|
expect(peerA.locator("path")).to_have_count(1)
|
|
661
658
|
expect(peerB.locator("path")).to_have_count(1)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
@pytest.mark.xdist_group(name="websockets")
|
|
662
|
+
def test_should_sync_datalayer_clear(
|
|
663
|
+
new_page, asgi_live_server, tilelayer, map, datalayer
|
|
664
|
+
):
|
|
665
|
+
map.settings["properties"]["syncEnabled"] = True
|
|
666
|
+
map.edit_status = Map.ANONYMOUS
|
|
667
|
+
map.save()
|
|
668
|
+
|
|
669
|
+
# Create two tabs
|
|
670
|
+
peerA = new_page("Page A")
|
|
671
|
+
peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
|
|
672
|
+
peerB = new_page("Page B")
|
|
673
|
+
peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
|
|
674
|
+
expect(peerA.locator(".leaflet-marker-icon")).to_have_count(1)
|
|
675
|
+
expect(peerB.locator(".leaflet-marker-icon")).to_have_count(1)
|
|
676
|
+
|
|
677
|
+
# Clear layer in peer A
|
|
678
|
+
peerA.get_by_role("button", name="Manage layers").click()
|
|
679
|
+
peerA.get_by_role("button", name="Edit", exact=True).click()
|
|
680
|
+
peerA.locator("summary").filter(has_text="Advanced actions").click()
|
|
681
|
+
peerA.get_by_role("button", name="Empty").click()
|
|
682
|
+
expect(peerA.locator(".leaflet-marker-icon")).to_have_count(0)
|
|
683
|
+
expect(peerB.locator(".leaflet-marker-icon")).to_have_count(0)
|
|
684
|
+
|
|
685
|
+
# Undo in peer A
|
|
686
|
+
peerA.locator(".edit-undo").click()
|
|
687
|
+
expect(peerA.locator(".leaflet-marker-icon")).to_have_count(1)
|
|
688
|
+
expect(peerB.locator(".leaflet-marker-icon")).to_have_count(1)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@pytest.mark.xdist_group(name="websockets")
|
|
692
|
+
def test_should_save_remote_dirty_datalayers(new_page, asgi_live_server, tilelayer):
|
|
693
|
+
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
|
694
|
+
map.settings["properties"]["syncEnabled"] = True
|
|
695
|
+
map.save()
|
|
696
|
+
|
|
697
|
+
assert not DataLayer.objects.count()
|
|
698
|
+
|
|
699
|
+
# Create two tabs
|
|
700
|
+
peerA = new_page("Page A")
|
|
701
|
+
peerA.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
|
|
702
|
+
peerB = new_page("Page B")
|
|
703
|
+
peerB.goto(f"{asgi_live_server.url}{map.get_absolute_url()}?edit")
|
|
704
|
+
|
|
705
|
+
# Create a new layer from peerA
|
|
706
|
+
peerA.get_by_role("button", name="Manage layers").click()
|
|
707
|
+
peerA.get_by_role("button", name="Add a layer").click()
|
|
708
|
+
|
|
709
|
+
# Create a new layer from peerB
|
|
710
|
+
peerB.get_by_role("button", name="Manage layers").click()
|
|
711
|
+
peerB.get_by_role("button", name="Add a layer").click()
|
|
712
|
+
|
|
713
|
+
# Save from peerA to the server
|
|
714
|
+
counter = 0
|
|
715
|
+
|
|
716
|
+
def on_response(response):
|
|
717
|
+
nonlocal counter
|
|
718
|
+
if "/datalayer/create/" in response.url:
|
|
719
|
+
counter += 1
|
|
720
|
+
# Wait for the two datalayer saves
|
|
721
|
+
if counter == 2:
|
|
722
|
+
return True
|
|
723
|
+
return False
|
|
724
|
+
|
|
725
|
+
with peerA.expect_response(on_response):
|
|
726
|
+
peerA.get_by_role("button", name="Save").click()
|
|
727
|
+
|
|
728
|
+
assert DataLayer.objects.count() == 2
|
umap/tests/settings.py
CHANGED
umap/tests/test_datalayer_s3.py
CHANGED
umap/tests/test_map_views.py
CHANGED
|
@@ -694,6 +694,7 @@ def test_download(client, map, datalayer):
|
|
|
694
694
|
"coordinates": [14.68896484375, 48.55297816440071],
|
|
695
695
|
"type": "Point",
|
|
696
696
|
},
|
|
697
|
+
"id": "ExNTQ",
|
|
697
698
|
"properties": {
|
|
698
699
|
"_umap_options": {"color": "DarkCyan", "iconClass": "Ball"},
|
|
699
700
|
"description": "Da place anonymous again 755",
|
umap/tests/test_views.py
CHANGED
|
@@ -183,7 +183,12 @@ def test_stats_empty(client):
|
|
|
183
183
|
"maps_count": 0,
|
|
184
184
|
"users_active_last_week_count": 0,
|
|
185
185
|
"users_count": 0,
|
|
186
|
+
"active_sessions": 0,
|
|
186
187
|
"version": VERSION,
|
|
188
|
+
"editors_count": 0,
|
|
189
|
+
"members_count": 0,
|
|
190
|
+
"orphans_count": 0,
|
|
191
|
+
"owners_count": 0,
|
|
187
192
|
}
|
|
188
193
|
|
|
189
194
|
|
|
@@ -199,7 +204,12 @@ def test_stats_basic(client, map, datalayer, user2):
|
|
|
199
204
|
"maps_count": 1,
|
|
200
205
|
"users_active_last_week_count": 1,
|
|
201
206
|
"users_count": 2,
|
|
207
|
+
"active_sessions": 0,
|
|
202
208
|
"version": VERSION,
|
|
209
|
+
"editors_count": 0,
|
|
210
|
+
"members_count": 0,
|
|
211
|
+
"orphans_count": 1,
|
|
212
|
+
"owners_count": 1,
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
|
|
@@ -476,3 +486,27 @@ def test_cannot_search_deleted_map(client, map):
|
|
|
476
486
|
url = reverse("search")
|
|
477
487
|
response = client.get(url + "?q=Blé")
|
|
478
488
|
assert "Blé dur" not in response.content.decode()
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@pytest.mark.django_db
|
|
492
|
+
def test_filter_by_tag(client, map):
|
|
493
|
+
# Very basic search, that do not deal with accent nor case.
|
|
494
|
+
# See install.md for how to have a smarter dict + index.
|
|
495
|
+
map.name = "Blé dur"
|
|
496
|
+
map.tags = ["bike"]
|
|
497
|
+
map.save()
|
|
498
|
+
url = reverse("search")
|
|
499
|
+
response = client.get(url + "?tags=bike")
|
|
500
|
+
assert "Blé dur" in response.content.decode()
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@pytest.mark.django_db
|
|
504
|
+
def test_can_combine_search_and_filter(client, map):
|
|
505
|
+
# Very basic search, that do not deal with accent nor case.
|
|
506
|
+
# See install.md for how to have a smarter dict + index.
|
|
507
|
+
map.name = "Blé dur"
|
|
508
|
+
map.tags = ["bike"]
|
|
509
|
+
map.save()
|
|
510
|
+
url = reverse("search")
|
|
511
|
+
response = client.get(url + "?q=dur&tags=bike")
|
|
512
|
+
assert "Blé dur" in response.content.decode()
|
umap/utils.py
CHANGED
umap/views.py
CHANGED
|
@@ -19,6 +19,7 @@ from django.contrib.auth import BACKEND_SESSION_KEY, get_user_model
|
|
|
19
19
|
from django.contrib.auth import logout as do_logout
|
|
20
20
|
from django.contrib.gis.measure import D
|
|
21
21
|
from django.contrib.postgres.search import SearchQuery, SearchVector
|
|
22
|
+
from django.contrib.sessions.models import Session
|
|
22
23
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
|
23
24
|
from django.core.exceptions import PermissionDenied
|
|
24
25
|
from django.core.mail import send_mail
|
|
@@ -333,12 +334,18 @@ class TeamMaps(PaginatorMixin, DetailView):
|
|
|
333
334
|
class SearchMixin:
|
|
334
335
|
def get_search_queryset(self, **kwargs):
|
|
335
336
|
q = self.request.GET.get("q")
|
|
337
|
+
tags = [t for t in self.request.GET.getlist("tags") if t]
|
|
338
|
+
qs = Map.objects.all()
|
|
336
339
|
if q:
|
|
337
340
|
vector = SearchVector("name", config=settings.UMAP_SEARCH_CONFIGURATION)
|
|
338
341
|
query = SearchQuery(
|
|
339
342
|
q, config=settings.UMAP_SEARCH_CONFIGURATION, search_type="websearch"
|
|
340
343
|
)
|
|
341
|
-
|
|
344
|
+
qs = qs.annotate(search=vector).filter(search=query)
|
|
345
|
+
if tags:
|
|
346
|
+
qs = qs.filter(tags__contains=tags)
|
|
347
|
+
if q or tags:
|
|
348
|
+
return qs
|
|
342
349
|
|
|
343
350
|
|
|
344
351
|
class Search(PaginatorMixin, TemplateView, PublicMapsMixin, SearchMixin):
|
|
@@ -609,7 +616,7 @@ class MapDetailMixin(SessionMixin):
|
|
|
609
616
|
"licences": dict((l.name, l.json) for l in Licence.objects.all()),
|
|
610
617
|
"umap_version": VERSION,
|
|
611
618
|
"featuresHaveOwner": settings.UMAP_DEFAULT_FEATURES_HAVE_OWNERS,
|
|
612
|
-
"websocketEnabled": settings.
|
|
619
|
+
"websocketEnabled": settings.REALTIME_ENABLED,
|
|
613
620
|
"importers": settings.UMAP_IMPORTERS,
|
|
614
621
|
"defaultLabelKeys": settings.UMAP_LABEL_KEYS,
|
|
615
622
|
}
|
|
@@ -1371,6 +1378,13 @@ class PictogramJSONList(ListView):
|
|
|
1371
1378
|
|
|
1372
1379
|
def stats(request):
|
|
1373
1380
|
last_week = make_aware(datetime.now()) - timedelta(days=7)
|
|
1381
|
+
users = User.objects.values_list("pk", flat=True)
|
|
1382
|
+
owners = set(
|
|
1383
|
+
Map.objects.filter(owner__isnull=False).values_list("owner", flat=True)
|
|
1384
|
+
)
|
|
1385
|
+
editors = set(Map.editors.through.objects.values_list("user_id", flat=True))
|
|
1386
|
+
members = set(Team.users.through.objects.values_list("user_id", flat=True))
|
|
1387
|
+
orphans = set(users) - owners - editors - members
|
|
1374
1388
|
return simple_json_response(
|
|
1375
1389
|
**{
|
|
1376
1390
|
"version": VERSION,
|
|
@@ -1382,6 +1396,13 @@ def stats(request):
|
|
|
1382
1396
|
"users_active_last_week_count": User.objects.filter(
|
|
1383
1397
|
last_login__gt=last_week
|
|
1384
1398
|
).count(),
|
|
1399
|
+
"active_sessions": Session.objects.filter(
|
|
1400
|
+
expire_date__gt=datetime.utcnow()
|
|
1401
|
+
).count(),
|
|
1402
|
+
"owners_count": len(owners),
|
|
1403
|
+
"editors_count": len(editors),
|
|
1404
|
+
"members_count": len(members),
|
|
1405
|
+
"orphans_count": len(orphans),
|
|
1385
1406
|
}
|
|
1386
1407
|
)
|
|
1387
1408
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: umap-project
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.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,41 +19,42 @@ Requires-Python: >=3.10
|
|
|
19
19
|
Requires-Dist: django-agnocomplete==2.2.0
|
|
20
20
|
Requires-Dist: django-environ==0.12.0
|
|
21
21
|
Requires-Dist: django-probes==1.7.0
|
|
22
|
-
Requires-Dist: django==5.1.
|
|
22
|
+
Requires-Dist: django==5.1.8
|
|
23
23
|
Requires-Dist: pillow==11.1.0
|
|
24
|
-
Requires-Dist: psycopg==3.2.
|
|
24
|
+
Requires-Dist: psycopg==3.2.6
|
|
25
25
|
Requires-Dist: rcssmin==1.2.1
|
|
26
26
|
Requires-Dist: requests==2.32.3
|
|
27
27
|
Requires-Dist: rjsmin==1.2.4
|
|
28
|
-
Requires-Dist: social-auth-app-django==5.4.
|
|
29
|
-
Requires-Dist: social-auth-core==4.5.
|
|
28
|
+
Requires-Dist: social-auth-app-django==5.4.3
|
|
29
|
+
Requires-Dist: social-auth-core==4.5.6
|
|
30
30
|
Provides-Extra: dev
|
|
31
31
|
Requires-Dist: djlint==1.36.4; extra == 'dev'
|
|
32
32
|
Requires-Dist: hatch==1.14.0; extra == 'dev'
|
|
33
33
|
Requires-Dist: isort==6.0.1; extra == 'dev'
|
|
34
|
-
Requires-Dist: mkdocs-material==9.6.
|
|
34
|
+
Requires-Dist: mkdocs-material==9.6.10; extra == 'dev'
|
|
35
35
|
Requires-Dist: mkdocs-static-i18n==1.3.0; extra == 'dev'
|
|
36
36
|
Requires-Dist: mkdocs==1.6.1; extra == 'dev'
|
|
37
37
|
Requires-Dist: pymdown-extensions==10.14.3; extra == 'dev'
|
|
38
|
-
Requires-Dist: ruff==0.
|
|
38
|
+
Requires-Dist: ruff==0.11.2; extra == 'dev'
|
|
39
39
|
Requires-Dist: vermin==1.6.0; extra == 'dev'
|
|
40
40
|
Provides-Extra: docker
|
|
41
|
-
Requires-Dist:
|
|
41
|
+
Requires-Dist: uvicorn==0.34.0; extra == 'docker'
|
|
42
42
|
Provides-Extra: s3
|
|
43
|
-
Requires-Dist: django-storages[s3]==1.14.
|
|
43
|
+
Requires-Dist: django-storages[s3]==1.14.5; extra == 's3'
|
|
44
44
|
Provides-Extra: sync
|
|
45
|
-
Requires-Dist: pydantic==2.
|
|
45
|
+
Requires-Dist: pydantic==2.11.1; extra == 'sync'
|
|
46
46
|
Requires-Dist: redis==5.2.1; extra == 'sync'
|
|
47
|
+
Requires-Dist: websockets==15.0.1; extra == 'sync'
|
|
47
48
|
Provides-Extra: test
|
|
48
49
|
Requires-Dist: daphne==4.1.2; extra == 'test'
|
|
49
50
|
Requires-Dist: factory-boy==3.3.3; extra == 'test'
|
|
50
|
-
Requires-Dist: moto[s3]==5.
|
|
51
|
+
Requires-Dist: moto[s3]==5.1.1; extra == 'test'
|
|
51
52
|
Requires-Dist: playwright>=1.39; extra == 'test'
|
|
52
53
|
Requires-Dist: pytest-django==4.10.0; extra == 'test'
|
|
53
54
|
Requires-Dist: pytest-playwright==0.7.0; extra == 'test'
|
|
54
55
|
Requires-Dist: pytest-rerunfailures==15.0; extra == 'test'
|
|
55
56
|
Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
|
|
56
|
-
Requires-Dist: pytest==8.3.
|
|
57
|
+
Requires-Dist: pytest==8.3.5; extra == 'test'
|
|
57
58
|
Description-Content-Type: text/markdown
|
|
58
59
|
|
|
59
60
|
# uMap project
|