umap-project 3.1.2__py3-none-any.whl → 3.2.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/locale/en/LC_MESSAGES/django.po +21 -17
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +21 -17
- umap/management/commands/export_pictogram.py +29 -0
- umap/management/commands/migrate_to_S3.py +5 -1
- umap/management/commands/purge_old_versions.py +8 -6
- umap/settings/__init__.py +21 -0
- umap/settings/base.py +1 -0
- umap/static/umap/content.css +7 -2
- umap/static/umap/css/icon.css +77 -3
- umap/static/umap/css/panel.css +31 -1
- umap/static/umap/js/modules/browser.js +1 -1
- umap/static/umap/js/modules/data/features.js +14 -29
- umap/static/umap/js/modules/data/layer.js +248 -136
- umap/static/umap/js/modules/facets.js +2 -2
- umap/static/umap/js/modules/form/fields.js +56 -19
- umap/static/umap/js/modules/formatter.js +36 -8
- umap/static/umap/js/modules/importers/opendata.js +23 -6
- umap/static/umap/js/modules/managers.js +59 -0
- umap/static/umap/js/modules/rendering/icon.js +3 -5
- umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
- umap/static/umap/js/modules/rendering/map.js +1 -1
- umap/static/umap/js/modules/rendering/ui.js +13 -0
- umap/static/umap/js/modules/rules.js +76 -23
- umap/static/umap/js/modules/schema.js +3 -0
- umap/static/umap/js/modules/slideshow.js +1 -1
- umap/static/umap/js/modules/sync/updaters.js +1 -6
- umap/static/umap/js/modules/tableeditor.js +13 -37
- umap/static/umap/js/modules/templates.js +7 -6
- umap/static/umap/js/modules/ui/panel.js +7 -0
- umap/static/umap/js/modules/umap.js +17 -6
- umap/static/umap/js/modules/utils.js +8 -7
- umap/static/umap/locale/am_ET.js +43 -6
- umap/static/umap/locale/am_ET.json +43 -6
- umap/static/umap/locale/ar.js +43 -6
- umap/static/umap/locale/ar.json +43 -6
- umap/static/umap/locale/ast.js +43 -6
- umap/static/umap/locale/ast.json +43 -6
- umap/static/umap/locale/bg.js +43 -6
- umap/static/umap/locale/bg.json +43 -6
- umap/static/umap/locale/br.js +30 -26
- umap/static/umap/locale/br.json +30 -26
- umap/static/umap/locale/ca.js +50 -13
- umap/static/umap/locale/ca.json +50 -13
- umap/static/umap/locale/cs_CZ.js +43 -6
- umap/static/umap/locale/cs_CZ.json +43 -6
- umap/static/umap/locale/da.js +10 -6
- umap/static/umap/locale/da.json +10 -6
- umap/static/umap/locale/de.js +10 -6
- umap/static/umap/locale/de.json +10 -6
- umap/static/umap/locale/el.js +20 -10
- umap/static/umap/locale/el.json +20 -10
- umap/static/umap/locale/en.js +10 -6
- umap/static/umap/locale/en.json +10 -6
- umap/static/umap/locale/en_US.json +43 -6
- umap/static/umap/locale/es.js +10 -6
- umap/static/umap/locale/es.json +10 -6
- umap/static/umap/locale/et.js +43 -6
- umap/static/umap/locale/et.json +43 -6
- umap/static/umap/locale/eu.js +43 -6
- umap/static/umap/locale/eu.json +43 -6
- umap/static/umap/locale/fa_IR.js +43 -6
- umap/static/umap/locale/fa_IR.json +43 -6
- umap/static/umap/locale/fi.js +43 -6
- umap/static/umap/locale/fi.json +43 -6
- umap/static/umap/locale/fr.js +10 -6
- umap/static/umap/locale/fr.json +10 -6
- umap/static/umap/locale/gl.js +43 -6
- umap/static/umap/locale/gl.json +43 -6
- umap/static/umap/locale/he.js +43 -6
- umap/static/umap/locale/he.json +43 -6
- umap/static/umap/locale/hr.js +43 -6
- umap/static/umap/locale/hr.json +43 -6
- umap/static/umap/locale/hu.js +34 -24
- umap/static/umap/locale/hu.json +34 -24
- umap/static/umap/locale/id.js +43 -6
- umap/static/umap/locale/id.json +43 -6
- umap/static/umap/locale/is.js +43 -6
- umap/static/umap/locale/is.json +43 -6
- umap/static/umap/locale/it.js +10 -6
- umap/static/umap/locale/it.json +10 -6
- umap/static/umap/locale/ja.js +43 -6
- umap/static/umap/locale/ja.json +43 -6
- umap/static/umap/locale/ko.js +43 -6
- umap/static/umap/locale/ko.json +43 -6
- umap/static/umap/locale/lt.js +43 -6
- umap/static/umap/locale/lt.json +43 -6
- umap/static/umap/locale/ms.js +43 -6
- umap/static/umap/locale/ms.json +43 -6
- umap/static/umap/locale/nl.js +10 -6
- umap/static/umap/locale/nl.json +10 -6
- umap/static/umap/locale/no.js +43 -6
- umap/static/umap/locale/no.json +43 -6
- umap/static/umap/locale/pl.js +43 -6
- umap/static/umap/locale/pl.json +43 -6
- umap/static/umap/locale/pl_PL.json +43 -6
- umap/static/umap/locale/pt.js +43 -6
- umap/static/umap/locale/pt.json +43 -6
- umap/static/umap/locale/pt_BR.js +53 -16
- umap/static/umap/locale/pt_BR.json +53 -16
- umap/static/umap/locale/pt_PT.js +43 -6
- umap/static/umap/locale/pt_PT.json +43 -6
- umap/static/umap/locale/ro.js +43 -6
- umap/static/umap/locale/ro.json +43 -6
- umap/static/umap/locale/ru.js +43 -6
- umap/static/umap/locale/ru.json +43 -6
- umap/static/umap/locale/sk_SK.js +43 -6
- umap/static/umap/locale/sk_SK.json +43 -6
- umap/static/umap/locale/sl.js +43 -6
- umap/static/umap/locale/sl.json +43 -6
- umap/static/umap/locale/sr.js +43 -6
- umap/static/umap/locale/sr.json +43 -6
- umap/static/umap/locale/sv.js +43 -6
- umap/static/umap/locale/sv.json +43 -6
- umap/static/umap/locale/th_TH.js +43 -6
- umap/static/umap/locale/th_TH.json +43 -6
- umap/static/umap/locale/tr.js +43 -6
- umap/static/umap/locale/tr.json +43 -6
- umap/static/umap/locale/uk_UA.js +43 -6
- umap/static/umap/locale/uk_UA.json +43 -6
- umap/static/umap/locale/vi.js +43 -6
- umap/static/umap/locale/vi.json +43 -6
- umap/static/umap/locale/vi_VN.json +43 -6
- umap/static/umap/locale/zh.js +43 -6
- umap/static/umap/locale/zh.json +43 -6
- umap/static/umap/locale/zh_CN.json +43 -6
- umap/static/umap/locale/zh_TW.Big5.json +43 -6
- umap/static/umap/locale/zh_TW.js +43 -6
- umap/static/umap/locale/zh_TW.json +43 -6
- umap/static/umap/map.css +239 -65
- umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
- umap/storage/fs.py +3 -2
- umap/templates/base.html +4 -1
- umap/tests/base.py +9 -1
- umap/tests/integration/test_basics.py +1 -1
- umap/tests/integration/test_conditional_rules.py +62 -20
- umap/tests/integration/test_edit_datalayer.py +1 -1
- umap/tests/integration/test_edit_marker.py +1 -1
- umap/tests/integration/test_export_map.py +10 -0
- umap/tests/integration/test_import.py +140 -0
- umap/tests/integration/test_optimistic_merge.py +72 -12
- umap/tests/integration/test_tableeditor.py +6 -3
- umap/utils.py +33 -0
- umap/views.py +16 -2
- umap_project-3.2.0.dist-info/METADATA +76 -0
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/RECORD +150 -148
- umap_project-3.1.2.dist-info/METADATA +0 -68
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/WHEEL +0 -0
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/entry_points.txt +0 -0
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -557,6 +557,38 @@ def test_import_multipolyline(live_server, page, tilelayer):
|
|
|
557
557
|
expect(paths).to_have_count(1)
|
|
558
558
|
|
|
559
559
|
|
|
560
|
+
def test_import_false_multipoint(live_server, page, tilelayer):
|
|
561
|
+
data = {
|
|
562
|
+
"type": "FeatureCollection",
|
|
563
|
+
"features": [
|
|
564
|
+
{
|
|
565
|
+
"type": "Feature",
|
|
566
|
+
"geometry": {
|
|
567
|
+
"coordinates": [[1.43661447777346, 43.59853484073553]],
|
|
568
|
+
"type": "MultiPoint",
|
|
569
|
+
},
|
|
570
|
+
"properties": {"name": "foo bar"},
|
|
571
|
+
}
|
|
572
|
+
],
|
|
573
|
+
}
|
|
574
|
+
page.goto(f"{live_server.url}/map/new/")
|
|
575
|
+
page.get_by_title("Open browser").click()
|
|
576
|
+
layers = page.locator(".umap-browser .datalayer")
|
|
577
|
+
markers = page.locator(".leaflet-marker-icon")
|
|
578
|
+
expect(markers).to_have_count(0)
|
|
579
|
+
expect(layers).to_have_count(0)
|
|
580
|
+
button = page.get_by_title("Import data")
|
|
581
|
+
expect(button).to_be_visible()
|
|
582
|
+
button.click()
|
|
583
|
+
textarea = page.locator(".umap-import textarea")
|
|
584
|
+
textarea.fill(json.dumps(data))
|
|
585
|
+
page.locator('select[name="format"]').select_option("geojson")
|
|
586
|
+
page.get_by_role("button", name="Import data", exact=True).click()
|
|
587
|
+
# A layer has been created
|
|
588
|
+
expect(layers).to_have_count(1)
|
|
589
|
+
expect(markers).to_have_count(1)
|
|
590
|
+
|
|
591
|
+
|
|
560
592
|
def test_should_not_import_empty_coordinates(live_server, page, tilelayer):
|
|
561
593
|
data = {
|
|
562
594
|
"type": "FeatureCollection",
|
|
@@ -670,6 +702,114 @@ def test_import_csv_with_commas_in_latlon(tilelayer, live_server, page, settings
|
|
|
670
702
|
}
|
|
671
703
|
|
|
672
704
|
|
|
705
|
+
def test_import_csv_with_wkt_geom(tilelayer, live_server, page, settings):
|
|
706
|
+
settings.UMAP_ALLOW_ANONYMOUS = True
|
|
707
|
+
page.goto(f"{live_server.url}/map/new/")
|
|
708
|
+
page.get_by_title("Open browser").click()
|
|
709
|
+
layers = page.locator(".umap-browser .datalayer")
|
|
710
|
+
markers = page.locator(".leaflet-marker-icon")
|
|
711
|
+
paths = page.locator("path")
|
|
712
|
+
page.get_by_title("Import data").click()
|
|
713
|
+
textarea = page.locator(".umap-import textarea")
|
|
714
|
+
textarea.fill(
|
|
715
|
+
"geom;foobar\nPOLYGON ((-64.8 32.3, -65.5 18.3, -80.3 25.2, -64.8 32.3));mypoly\nPOINT(48.35 12.23);mypoint"
|
|
716
|
+
)
|
|
717
|
+
page.locator('select[name="format"]').select_option("csv")
|
|
718
|
+
page.get_by_role("button", name="Import data", exact=True).click()
|
|
719
|
+
expect(layers).to_have_count(1)
|
|
720
|
+
expect(markers).to_have_count(1)
|
|
721
|
+
expect(paths).to_have_count(1)
|
|
722
|
+
with page.expect_response(re.compile(r".*/datalayer/create/.*")):
|
|
723
|
+
page.get_by_role("button", name="Save").click()
|
|
724
|
+
datalayer = DataLayer.objects.last()
|
|
725
|
+
saved_data = json.loads(Path(datalayer.geojson.path).read_text())
|
|
726
|
+
assert saved_data["features"][0]["geometry"] == {
|
|
727
|
+
"coordinates": [
|
|
728
|
+
48.35,
|
|
729
|
+
12.23,
|
|
730
|
+
],
|
|
731
|
+
"type": "Point",
|
|
732
|
+
}
|
|
733
|
+
assert saved_data["features"][1]["geometry"] == {
|
|
734
|
+
"coordinates": [
|
|
735
|
+
[
|
|
736
|
+
[
|
|
737
|
+
-64.8,
|
|
738
|
+
32.3,
|
|
739
|
+
],
|
|
740
|
+
[
|
|
741
|
+
-65.5,
|
|
742
|
+
18.3,
|
|
743
|
+
],
|
|
744
|
+
[
|
|
745
|
+
-80.3,
|
|
746
|
+
25.2,
|
|
747
|
+
],
|
|
748
|
+
[
|
|
749
|
+
-64.8,
|
|
750
|
+
32.3,
|
|
751
|
+
],
|
|
752
|
+
],
|
|
753
|
+
],
|
|
754
|
+
"type": "Polygon",
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
def test_import_csv_with_geojson_geom(tilelayer, live_server, page, settings):
|
|
759
|
+
settings.UMAP_ALLOW_ANONYMOUS = True
|
|
760
|
+
page.goto(f"{live_server.url}/map/new/")
|
|
761
|
+
page.get_by_title("Open browser").click()
|
|
762
|
+
layers = page.locator(".umap-browser .datalayer")
|
|
763
|
+
markers = page.locator(".leaflet-marker-icon")
|
|
764
|
+
paths = page.locator("path")
|
|
765
|
+
page.get_by_title("Import data").click()
|
|
766
|
+
textarea = page.locator(".umap-import textarea")
|
|
767
|
+
textarea.fill(
|
|
768
|
+
"geojson;foobar\n"
|
|
769
|
+
'{"coordinates": [[[-64.8,32.3],[-65.5,18.3],[-80.3,25.2],[-64.8,32.3]]],"type": "Polygon"};mypoly\n'
|
|
770
|
+
'{"coordinates": [48.35,12.23],"type": "Point"};mypoint'
|
|
771
|
+
)
|
|
772
|
+
page.locator('select[name="format"]').select_option("csv")
|
|
773
|
+
page.get_by_role("button", name="Import data", exact=True).click()
|
|
774
|
+
expect(layers).to_have_count(1)
|
|
775
|
+
expect(markers).to_have_count(1)
|
|
776
|
+
expect(paths).to_have_count(1)
|
|
777
|
+
with page.expect_response(re.compile(r".*/datalayer/create/.*")):
|
|
778
|
+
page.get_by_role("button", name="Save").click()
|
|
779
|
+
datalayer = DataLayer.objects.last()
|
|
780
|
+
saved_data = json.loads(Path(datalayer.geojson.path).read_text())
|
|
781
|
+
assert saved_data["features"][0]["geometry"] == {
|
|
782
|
+
"coordinates": [
|
|
783
|
+
48.35,
|
|
784
|
+
12.23,
|
|
785
|
+
],
|
|
786
|
+
"type": "Point",
|
|
787
|
+
}
|
|
788
|
+
assert saved_data["features"][1]["geometry"] == {
|
|
789
|
+
"coordinates": [
|
|
790
|
+
[
|
|
791
|
+
[
|
|
792
|
+
-64.8,
|
|
793
|
+
32.3,
|
|
794
|
+
],
|
|
795
|
+
[
|
|
796
|
+
-65.5,
|
|
797
|
+
18.3,
|
|
798
|
+
],
|
|
799
|
+
[
|
|
800
|
+
-80.3,
|
|
801
|
+
25.2,
|
|
802
|
+
],
|
|
803
|
+
[
|
|
804
|
+
-64.8,
|
|
805
|
+
32.3,
|
|
806
|
+
],
|
|
807
|
+
],
|
|
808
|
+
],
|
|
809
|
+
"type": "Polygon",
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
|
|
673
813
|
def test_create_remote_data(page, live_server, tilelayer):
|
|
674
814
|
def handle(route):
|
|
675
815
|
route.fulfill(
|
|
@@ -12,13 +12,13 @@ from ..base import DataLayerFactory, MapFactory
|
|
|
12
12
|
DATALAYER_UPDATE = re.compile(r".*/datalayer/update/.*")
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def test_created_markers_are_merged(
|
|
15
|
+
def test_created_markers_are_merged(new_page, live_server, tilelayer):
|
|
16
16
|
# Let's create a new map with an empty datalayer
|
|
17
17
|
map = MapFactory(name="server-side merge")
|
|
18
18
|
datalayer = DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
|
|
19
19
|
|
|
20
20
|
# Now navigate to this map and create marker
|
|
21
|
-
page_one =
|
|
21
|
+
page_one = new_page("page 1")
|
|
22
22
|
page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
23
23
|
|
|
24
24
|
save_p1 = page_one.get_by_role("button", name="Save")
|
|
@@ -52,10 +52,20 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|
|
52
52
|
"id": str(datalayer.pk),
|
|
53
53
|
"rank": 0,
|
|
54
54
|
"remoteData": {},
|
|
55
|
+
"fields": [
|
|
56
|
+
{
|
|
57
|
+
"key": "name",
|
|
58
|
+
"type": "String",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"key": "description",
|
|
62
|
+
"type": "Text",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
# Now navigate to this map from another tab
|
|
58
|
-
page_two =
|
|
68
|
+
page_two = new_page("page 2")
|
|
59
69
|
|
|
60
70
|
page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
61
71
|
|
|
@@ -91,6 +101,16 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|
|
91
101
|
"id": str(datalayer.pk),
|
|
92
102
|
"rank": 0,
|
|
93
103
|
"remoteData": {},
|
|
104
|
+
"fields": [
|
|
105
|
+
{
|
|
106
|
+
"key": "name",
|
|
107
|
+
"type": "String",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"key": "description",
|
|
111
|
+
"type": "Text",
|
|
112
|
+
},
|
|
113
|
+
],
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
# Now create another marker in the first tab
|
|
@@ -111,6 +131,16 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|
|
111
131
|
"id": str(datalayer.pk),
|
|
112
132
|
"rank": 0,
|
|
113
133
|
"remoteData": {},
|
|
134
|
+
"fields": [
|
|
135
|
+
{
|
|
136
|
+
"key": "name",
|
|
137
|
+
"type": "String",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"key": "description",
|
|
141
|
+
"type": "Text",
|
|
142
|
+
},
|
|
143
|
+
],
|
|
114
144
|
}
|
|
115
145
|
|
|
116
146
|
# And again
|
|
@@ -131,6 +161,16 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|
|
131
161
|
"id": str(datalayer.pk),
|
|
132
162
|
"rank": 0,
|
|
133
163
|
"remoteData": {},
|
|
164
|
+
"fields": [
|
|
165
|
+
{
|
|
166
|
+
"key": "name",
|
|
167
|
+
"type": "String",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"key": "description",
|
|
171
|
+
"type": "Text",
|
|
172
|
+
},
|
|
173
|
+
],
|
|
134
174
|
}
|
|
135
175
|
expect(marker_pane_p1).to_have_count(4)
|
|
136
176
|
|
|
@@ -153,20 +193,30 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|
|
153
193
|
"id": str(datalayer.pk),
|
|
154
194
|
"rank": 0,
|
|
155
195
|
"remoteData": {},
|
|
196
|
+
"fields": [
|
|
197
|
+
{
|
|
198
|
+
"key": "name",
|
|
199
|
+
"type": "String",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"key": "description",
|
|
203
|
+
"type": "Text",
|
|
204
|
+
},
|
|
205
|
+
],
|
|
156
206
|
}
|
|
157
207
|
expect(marker_pane_p2).to_have_count(5)
|
|
158
208
|
|
|
159
209
|
|
|
160
|
-
def test_empty_datalayers_can_be_merged(
|
|
210
|
+
def test_empty_datalayers_can_be_merged(new_page, live_server, tilelayer):
|
|
161
211
|
# Let's create a new map with an empty datalayer
|
|
162
212
|
map = MapFactory(name="server-side merge")
|
|
163
213
|
DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
|
|
164
214
|
|
|
165
215
|
# Open two tabs at the same time, on the same empty map
|
|
166
|
-
page_one =
|
|
216
|
+
page_one = new_page("page 1")
|
|
167
217
|
page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
168
218
|
|
|
169
|
-
page_two =
|
|
219
|
+
page_two = new_page("page 2")
|
|
170
220
|
page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
171
221
|
|
|
172
222
|
save_p1 = page_one.get_by_role("button", name="Save")
|
|
@@ -213,15 +263,15 @@ def test_empty_datalayers_can_be_merged(context, live_server, tilelayer):
|
|
|
213
263
|
expect(marker_pane_p2).to_have_count(2)
|
|
214
264
|
|
|
215
265
|
|
|
216
|
-
def test_same_second_edit_doesnt_conflict(
|
|
266
|
+
def test_same_second_edit_doesnt_conflict(new_page, live_server, tilelayer):
|
|
217
267
|
# Let's create a new map with an empty datalayer
|
|
218
268
|
map = MapFactory(name="server-side merge")
|
|
219
269
|
datalayer = DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
|
|
220
270
|
|
|
221
271
|
# Open the created map on two pages.
|
|
222
|
-
page_one =
|
|
272
|
+
page_one = new_page("page 1")
|
|
223
273
|
page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
224
|
-
page_two =
|
|
274
|
+
page_two = new_page("page 2")
|
|
225
275
|
page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
|
226
276
|
|
|
227
277
|
save_p1 = page_one.get_by_role("button", name="Save")
|
|
@@ -280,14 +330,24 @@ def test_same_second_edit_doesnt_conflict(context, live_server, tilelayer):
|
|
|
280
330
|
"id": str(datalayer.pk),
|
|
281
331
|
"rank": 0,
|
|
282
332
|
"remoteData": {},
|
|
333
|
+
"fields": [
|
|
334
|
+
{
|
|
335
|
+
"key": "name",
|
|
336
|
+
"type": "String",
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"key": "description",
|
|
340
|
+
"type": "Text",
|
|
341
|
+
},
|
|
342
|
+
],
|
|
283
343
|
}
|
|
284
344
|
|
|
285
345
|
|
|
286
|
-
def test_should_display_alert_on_conflict(
|
|
346
|
+
def test_should_display_alert_on_conflict(new_page, live_server, datalayer, openmap):
|
|
287
347
|
# Open the map on two pages.
|
|
288
|
-
page_one =
|
|
348
|
+
page_one = new_page("page 1")
|
|
289
349
|
page_one.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
290
|
-
page_two =
|
|
350
|
+
page_two = new_page("page 2")
|
|
291
351
|
page_two.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
292
352
|
|
|
293
353
|
# Change name on page one and save
|
|
@@ -69,7 +69,9 @@ def test_table_editor(live_server, openmap, datalayer, page):
|
|
|
69
69
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
70
70
|
page.get_by_role("button", name="Manage layers").click()
|
|
71
71
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
|
72
|
-
page.
|
|
72
|
+
page.locator("td[data-property=description]").dblclick()
|
|
73
|
+
page.locator('textarea[name="description"]').fill("nice new description")
|
|
74
|
+
page.get_by_text("Add a new field").click()
|
|
73
75
|
page.locator("dialog").locator("input").fill("newprop")
|
|
74
76
|
page.locator("dialog").get_by_role("button", name="OK").click()
|
|
75
77
|
page.locator("td").nth(2).dblclick()
|
|
@@ -83,6 +85,7 @@ def test_table_editor(live_server, openmap, datalayer, page):
|
|
|
83
85
|
page.get_by_role("button", name="Save").click()
|
|
84
86
|
saved = DataLayer.objects.last()
|
|
85
87
|
data = json.loads(Path(saved.geojson.path).read_text())
|
|
88
|
+
assert data["features"][0]["properties"]["description"] == "nice new description"
|
|
86
89
|
assert data["features"][0]["properties"]["newprop"] == "newvalue"
|
|
87
90
|
assert "name" not in data["features"][0]["properties"]
|
|
88
91
|
|
|
@@ -91,7 +94,7 @@ def test_cannot_add_existing_property_name(live_server, openmap, datalayer, page
|
|
|
91
94
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
92
95
|
page.get_by_role("button", name="Manage layers").click()
|
|
93
96
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
|
94
|
-
page.get_by_text("Add a new
|
|
97
|
+
page.get_by_text("Add a new field").click()
|
|
95
98
|
page.locator("dialog").locator("input").fill("name")
|
|
96
99
|
page.get_by_role("button", name="OK").click()
|
|
97
100
|
expect(page.get_by_role("dialog")).to_contain_text(
|
|
@@ -104,7 +107,7 @@ def test_cannot_add_property_with_a_dot(live_server, openmap, datalayer, page):
|
|
|
104
107
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
|
105
108
|
page.get_by_role("button", name="Manage layers").click()
|
|
106
109
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
|
107
|
-
page.get_by_text("Add a new
|
|
110
|
+
page.get_by_text("Add a new field").click()
|
|
108
111
|
page.locator("dialog").locator("input").fill("foo.bar")
|
|
109
112
|
page.get_by_role("button", name="OK").click()
|
|
110
113
|
expect(page.get_by_role("dialog")).to_contain_text(
|
umap/utils.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from django.conf import settings
|
|
7
|
+
from django.contrib.staticfiles import finders
|
|
6
8
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
7
9
|
from django.urls import URLPattern, URLResolver, get_resolver
|
|
8
10
|
|
|
@@ -185,3 +187,34 @@ def merge_features(reference: list, latest: list, incoming: list):
|
|
|
185
187
|
def json_dumps(obj, **kwargs):
|
|
186
188
|
"""Utility using the Django JSON Encoder when dumping objects"""
|
|
187
189
|
return json.dumps(obj, cls=DjangoJSONEncoder, **kwargs)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def collect_pictograms():
|
|
193
|
+
pictograms = {}
|
|
194
|
+
|
|
195
|
+
for name, definition in settings.UMAP_PICTOGRAMS_COLLECTIONS.items():
|
|
196
|
+
root = Path(definition["path"])
|
|
197
|
+
subfolder = "pictograms"
|
|
198
|
+
if not root.is_absolute():
|
|
199
|
+
root = Path(finders.find(root).removesuffix(definition["path"]))
|
|
200
|
+
subfolder = definition["path"]
|
|
201
|
+
categories = {}
|
|
202
|
+
for path in (root / subfolder).iterdir():
|
|
203
|
+
if path.is_dir():
|
|
204
|
+
categories[path.name] = []
|
|
205
|
+
for subpath in path.iterdir():
|
|
206
|
+
if subpath.is_dir() or subpath.name.startswith("."):
|
|
207
|
+
continue
|
|
208
|
+
src = subpath.relative_to(root)
|
|
209
|
+
categories[path.name].append(
|
|
210
|
+
{
|
|
211
|
+
"name": subpath.stem,
|
|
212
|
+
"src": f"{settings.STATIC_URL}{src}",
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
pictograms[name] = {
|
|
216
|
+
"attribution": definition.get("attribution"),
|
|
217
|
+
"categories": categories,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return pictograms
|
umap/views.py
CHANGED
|
@@ -71,6 +71,7 @@ from .models import DataLayer, Licence, Map, Pictogram, Star, Team, TileLayer
|
|
|
71
71
|
from .utils import (
|
|
72
72
|
ConflictError,
|
|
73
73
|
_urls_for_js,
|
|
74
|
+
collect_pictograms,
|
|
74
75
|
gzip_file,
|
|
75
76
|
is_ajax,
|
|
76
77
|
json_dumps,
|
|
@@ -1389,8 +1390,21 @@ class PictogramJSONList(ListView):
|
|
|
1389
1390
|
model = Pictogram
|
|
1390
1391
|
|
|
1391
1392
|
def render_to_response(self, context, **response_kwargs):
|
|
1392
|
-
|
|
1393
|
-
|
|
1393
|
+
if settings.UMAP_PICTOGRAMS_COLLECTIONS:
|
|
1394
|
+
content = collect_pictograms()
|
|
1395
|
+
else:
|
|
1396
|
+
categories = {}
|
|
1397
|
+
for picto in Pictogram.objects.all():
|
|
1398
|
+
category = picto.category or _("Generic")
|
|
1399
|
+
categories.setdefault(category, [])
|
|
1400
|
+
categories[category].append(picto.json)
|
|
1401
|
+
content = {
|
|
1402
|
+
settings.SITE_NAME: {
|
|
1403
|
+
"attribution": settings.SITE_NAME,
|
|
1404
|
+
"categories": categories,
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
return simple_json_response(data=content)
|
|
1394
1408
|
|
|
1395
1409
|
|
|
1396
1410
|
# ############## #
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: umap-project
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: Create maps with OpenStreetMap layers in a minute and embed them in your site.
|
|
5
|
+
Author-email: Yohan Boniface <yb@enix.org>
|
|
6
|
+
Maintainer-email: David Larlet <david@larlet.fr>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: django,geodjango,leaflet,map,openstreetmap
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: django-agnocomplete==2.2.0
|
|
20
|
+
Requires-Dist: django-environ==0.12.0
|
|
21
|
+
Requires-Dist: django-probes==1.7.0
|
|
22
|
+
Requires-Dist: django==5.2.4
|
|
23
|
+
Requires-Dist: pillow==11.3.0
|
|
24
|
+
Requires-Dist: psycopg==3.2.9
|
|
25
|
+
Requires-Dist: rcssmin==1.2.1
|
|
26
|
+
Requires-Dist: requests==2.32.4
|
|
27
|
+
Requires-Dist: rjsmin==1.2.4
|
|
28
|
+
Requires-Dist: social-auth-app-django==5.4.3
|
|
29
|
+
Requires-Dist: social-auth-core==4.5.6
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: djlint==1.36.4; extra == 'dev'
|
|
32
|
+
Requires-Dist: hatch==1.14.1; extra == 'dev'
|
|
33
|
+
Requires-Dist: isort==6.0.1; extra == 'dev'
|
|
34
|
+
Requires-Dist: mkdocs-material==9.6.15; extra == 'dev'
|
|
35
|
+
Requires-Dist: mkdocs-static-i18n==1.3.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: mkdocs==1.6.1; extra == 'dev'
|
|
37
|
+
Requires-Dist: pymdown-extensions==10.16; extra == 'dev'
|
|
38
|
+
Requires-Dist: ruff==0.12.2; extra == 'dev'
|
|
39
|
+
Requires-Dist: vermin==1.6.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: docker
|
|
41
|
+
Requires-Dist: uvicorn==0.35.0; extra == 'docker'
|
|
42
|
+
Provides-Extra: s3
|
|
43
|
+
Requires-Dist: django-storages[s3]==1.14.6; extra == 's3'
|
|
44
|
+
Provides-Extra: sync
|
|
45
|
+
Requires-Dist: pydantic==2.11.7; extra == 'sync'
|
|
46
|
+
Requires-Dist: redis==6.2.0; extra == 'sync'
|
|
47
|
+
Requires-Dist: websockets==15.0.1; extra == 'sync'
|
|
48
|
+
Provides-Extra: test
|
|
49
|
+
Requires-Dist: daphne==4.2.0; extra == 'test'
|
|
50
|
+
Requires-Dist: factory-boy==3.3.3; extra == 'test'
|
|
51
|
+
Requires-Dist: moto[s3]==5.1.6; extra == 'test'
|
|
52
|
+
Requires-Dist: playwright>=1.39; extra == 'test'
|
|
53
|
+
Requires-Dist: pytest-django==4.11.1; extra == 'test'
|
|
54
|
+
Requires-Dist: pytest-playwright==0.7.0; extra == 'test'
|
|
55
|
+
Requires-Dist: pytest-rerunfailures==15.1; extra == 'test'
|
|
56
|
+
Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
|
|
57
|
+
Requires-Dist: pytest==8.4.1; extra == 'test'
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
[](https://matrix.to/#/#umap:matrix.org)
|
|
61
|
+
[](https://forum.openstreetmap.fr/c/utiliser/umap/29)
|
|
62
|
+
[](https://lists.openstreetmap.org/listinfo/umap)
|
|
63
|
+
|
|
64
|
+
[](https://liberapay.com/uMap)
|
|
65
|
+
[](https://opencollective.com/uMap)
|
|
66
|
+
[](https://github.com/sponsors/umap-project)
|
|
67
|
+
|
|
68
|
+
# uMap project
|
|
69
|
+
|
|
70
|
+
uMap lets you create maps with OpenStreetMap layers within minutes and embed them in your site.
|
|
71
|
+
*Because we think that the more OSM will be used, the more OSM will be improved.*
|
|
72
|
+
Built on top of Django and Leaflet.
|
|
73
|
+
|
|
74
|
+
- Have a look at [our website](https://umap-project.org) for an introduction
|
|
75
|
+
- See [our docs](https://docs.umap-project.org/) for technical information
|
|
76
|
+
- Come [chat with us on matrix](https://matrix.to/#/#umap:matrix.org), start a thread on [the forum](https://forum.openstreetmap.fr/c/utiliser/umap/29) or join [the mailing list](https://lists.openstreetmap.org/listinfo/umap)
|