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.

Files changed (151) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.po +21 -17
  3. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/fr/LC_MESSAGES/django.po +21 -17
  5. umap/management/commands/export_pictogram.py +29 -0
  6. umap/management/commands/migrate_to_S3.py +5 -1
  7. umap/management/commands/purge_old_versions.py +8 -6
  8. umap/settings/__init__.py +21 -0
  9. umap/settings/base.py +1 -0
  10. umap/static/umap/content.css +7 -2
  11. umap/static/umap/css/icon.css +77 -3
  12. umap/static/umap/css/panel.css +31 -1
  13. umap/static/umap/js/modules/browser.js +1 -1
  14. umap/static/umap/js/modules/data/features.js +14 -29
  15. umap/static/umap/js/modules/data/layer.js +248 -136
  16. umap/static/umap/js/modules/facets.js +2 -2
  17. umap/static/umap/js/modules/form/fields.js +56 -19
  18. umap/static/umap/js/modules/formatter.js +36 -8
  19. umap/static/umap/js/modules/importers/opendata.js +23 -6
  20. umap/static/umap/js/modules/managers.js +59 -0
  21. umap/static/umap/js/modules/rendering/icon.js +3 -5
  22. umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
  23. umap/static/umap/js/modules/rendering/map.js +1 -1
  24. umap/static/umap/js/modules/rendering/ui.js +13 -0
  25. umap/static/umap/js/modules/rules.js +76 -23
  26. umap/static/umap/js/modules/schema.js +3 -0
  27. umap/static/umap/js/modules/slideshow.js +1 -1
  28. umap/static/umap/js/modules/sync/updaters.js +1 -6
  29. umap/static/umap/js/modules/tableeditor.js +13 -37
  30. umap/static/umap/js/modules/templates.js +7 -6
  31. umap/static/umap/js/modules/ui/panel.js +7 -0
  32. umap/static/umap/js/modules/umap.js +17 -6
  33. umap/static/umap/js/modules/utils.js +8 -7
  34. umap/static/umap/locale/am_ET.js +43 -6
  35. umap/static/umap/locale/am_ET.json +43 -6
  36. umap/static/umap/locale/ar.js +43 -6
  37. umap/static/umap/locale/ar.json +43 -6
  38. umap/static/umap/locale/ast.js +43 -6
  39. umap/static/umap/locale/ast.json +43 -6
  40. umap/static/umap/locale/bg.js +43 -6
  41. umap/static/umap/locale/bg.json +43 -6
  42. umap/static/umap/locale/br.js +30 -26
  43. umap/static/umap/locale/br.json +30 -26
  44. umap/static/umap/locale/ca.js +50 -13
  45. umap/static/umap/locale/ca.json +50 -13
  46. umap/static/umap/locale/cs_CZ.js +43 -6
  47. umap/static/umap/locale/cs_CZ.json +43 -6
  48. umap/static/umap/locale/da.js +10 -6
  49. umap/static/umap/locale/da.json +10 -6
  50. umap/static/umap/locale/de.js +10 -6
  51. umap/static/umap/locale/de.json +10 -6
  52. umap/static/umap/locale/el.js +20 -10
  53. umap/static/umap/locale/el.json +20 -10
  54. umap/static/umap/locale/en.js +10 -6
  55. umap/static/umap/locale/en.json +10 -6
  56. umap/static/umap/locale/en_US.json +43 -6
  57. umap/static/umap/locale/es.js +10 -6
  58. umap/static/umap/locale/es.json +10 -6
  59. umap/static/umap/locale/et.js +43 -6
  60. umap/static/umap/locale/et.json +43 -6
  61. umap/static/umap/locale/eu.js +43 -6
  62. umap/static/umap/locale/eu.json +43 -6
  63. umap/static/umap/locale/fa_IR.js +43 -6
  64. umap/static/umap/locale/fa_IR.json +43 -6
  65. umap/static/umap/locale/fi.js +43 -6
  66. umap/static/umap/locale/fi.json +43 -6
  67. umap/static/umap/locale/fr.js +10 -6
  68. umap/static/umap/locale/fr.json +10 -6
  69. umap/static/umap/locale/gl.js +43 -6
  70. umap/static/umap/locale/gl.json +43 -6
  71. umap/static/umap/locale/he.js +43 -6
  72. umap/static/umap/locale/he.json +43 -6
  73. umap/static/umap/locale/hr.js +43 -6
  74. umap/static/umap/locale/hr.json +43 -6
  75. umap/static/umap/locale/hu.js +34 -24
  76. umap/static/umap/locale/hu.json +34 -24
  77. umap/static/umap/locale/id.js +43 -6
  78. umap/static/umap/locale/id.json +43 -6
  79. umap/static/umap/locale/is.js +43 -6
  80. umap/static/umap/locale/is.json +43 -6
  81. umap/static/umap/locale/it.js +10 -6
  82. umap/static/umap/locale/it.json +10 -6
  83. umap/static/umap/locale/ja.js +43 -6
  84. umap/static/umap/locale/ja.json +43 -6
  85. umap/static/umap/locale/ko.js +43 -6
  86. umap/static/umap/locale/ko.json +43 -6
  87. umap/static/umap/locale/lt.js +43 -6
  88. umap/static/umap/locale/lt.json +43 -6
  89. umap/static/umap/locale/ms.js +43 -6
  90. umap/static/umap/locale/ms.json +43 -6
  91. umap/static/umap/locale/nl.js +10 -6
  92. umap/static/umap/locale/nl.json +10 -6
  93. umap/static/umap/locale/no.js +43 -6
  94. umap/static/umap/locale/no.json +43 -6
  95. umap/static/umap/locale/pl.js +43 -6
  96. umap/static/umap/locale/pl.json +43 -6
  97. umap/static/umap/locale/pl_PL.json +43 -6
  98. umap/static/umap/locale/pt.js +43 -6
  99. umap/static/umap/locale/pt.json +43 -6
  100. umap/static/umap/locale/pt_BR.js +53 -16
  101. umap/static/umap/locale/pt_BR.json +53 -16
  102. umap/static/umap/locale/pt_PT.js +43 -6
  103. umap/static/umap/locale/pt_PT.json +43 -6
  104. umap/static/umap/locale/ro.js +43 -6
  105. umap/static/umap/locale/ro.json +43 -6
  106. umap/static/umap/locale/ru.js +43 -6
  107. umap/static/umap/locale/ru.json +43 -6
  108. umap/static/umap/locale/sk_SK.js +43 -6
  109. umap/static/umap/locale/sk_SK.json +43 -6
  110. umap/static/umap/locale/sl.js +43 -6
  111. umap/static/umap/locale/sl.json +43 -6
  112. umap/static/umap/locale/sr.js +43 -6
  113. umap/static/umap/locale/sr.json +43 -6
  114. umap/static/umap/locale/sv.js +43 -6
  115. umap/static/umap/locale/sv.json +43 -6
  116. umap/static/umap/locale/th_TH.js +43 -6
  117. umap/static/umap/locale/th_TH.json +43 -6
  118. umap/static/umap/locale/tr.js +43 -6
  119. umap/static/umap/locale/tr.json +43 -6
  120. umap/static/umap/locale/uk_UA.js +43 -6
  121. umap/static/umap/locale/uk_UA.json +43 -6
  122. umap/static/umap/locale/vi.js +43 -6
  123. umap/static/umap/locale/vi.json +43 -6
  124. umap/static/umap/locale/vi_VN.json +43 -6
  125. umap/static/umap/locale/zh.js +43 -6
  126. umap/static/umap/locale/zh.json +43 -6
  127. umap/static/umap/locale/zh_CN.json +43 -6
  128. umap/static/umap/locale/zh_TW.Big5.json +43 -6
  129. umap/static/umap/locale/zh_TW.js +43 -6
  130. umap/static/umap/locale/zh_TW.json +43 -6
  131. umap/static/umap/map.css +239 -65
  132. umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
  133. umap/storage/fs.py +3 -2
  134. umap/templates/base.html +4 -1
  135. umap/tests/base.py +9 -1
  136. umap/tests/integration/test_basics.py +1 -1
  137. umap/tests/integration/test_conditional_rules.py +62 -20
  138. umap/tests/integration/test_edit_datalayer.py +1 -1
  139. umap/tests/integration/test_edit_marker.py +1 -1
  140. umap/tests/integration/test_export_map.py +10 -0
  141. umap/tests/integration/test_import.py +140 -0
  142. umap/tests/integration/test_optimistic_merge.py +72 -12
  143. umap/tests/integration/test_tableeditor.py +6 -3
  144. umap/utils.py +33 -0
  145. umap/views.py +16 -2
  146. umap_project-3.2.0.dist-info/METADATA +76 -0
  147. {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/RECORD +150 -148
  148. umap_project-3.1.2.dist-info/METADATA +0 -68
  149. {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/WHEEL +0 -0
  150. {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/entry_points.txt +0 -0
  151. {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(context, live_server, tilelayer):
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 = context.new_page()
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 = context.new_page()
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(context, live_server, tilelayer):
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 = context.new_page()
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 = context.new_page()
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(context, live_server, tilelayer):
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 = context.new_page()
272
+ page_one = new_page("page 1")
223
273
  page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
224
- page_two = context.new_page()
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(context, live_server, datalayer, openmap):
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 = context.new_page()
348
+ page_one = new_page("page 1")
289
349
  page_one.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
290
- page_two = context.new_page()
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.get_by_text("Add a new property").click()
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 property").click()
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 property").click()
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
- content = [p.json for p in Pictogram.objects.all()]
1393
- return simple_json_response(pictogram_list=content)
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
+ [![Matrix](https://img.shields.io/matrix/umap:matrix.org?server_fqdn=matrix.org&logo=matrix)](https://matrix.to/#/#umap:matrix.org)
61
+ [![Forum](https://img.shields.io/discourse/users?server=https://forum.openstreetmap.fr&label=Forum&logo=discourse)](https://forum.openstreetmap.fr/c/utiliser/umap/29)
62
+ [![Mailing list](https://img.shields.io/badge/Mailing%20list-Subscribe-598dc1?labelColor=white&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAZCAYAAAAmNZ4aAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5AICEi0F3iqxoQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAATBSURBVEjHrZbPb5xHHcY/z+z6R+06cRzWJQGCSgKhxPturSIOwIFSIeCEOHHqMZeqkUoLUiH1rvddlEOFxAn4HxAXblwIoEoICRrlfScFi0PVxo0rQtYBJ3ZiZ3ceDruOYid2kqojzeH9vpr5zPc7zzwzAqjXFySpKinYrgABhA0SgBPgYU+wHZMlp6LomMdseuaZhUq1yglJc5I+a/s48BRo1Pa4pHFwD3zb1qbEJnAbWAU+BD4AroA2JN8Ar9tat30zxs6dPcGNxsIYuAaqpaTLMeZdgPn5xbF+P83YOhyCD4EPpRRmJWrArM00+KCkafA0aEvyNWDNZhVYAXXttCKFFfAV26sxdraG4GYNdAS8YfMtiaWiyP/4qCV79tn2ZL+fJiVPSZqymZaYsTkkMZJS2pDCJvgmcA3oSr33lWWtY+A5yechPGnzKni5LPNf8zG1+fnFkZT6o7bmwWtS/1KwMVADpoqifc3mnK16lrUWPi5wr+fRlPSizaiEoRICpDvAUQgTADG2bwKvgz+fZc2fnDrVDh8V2Gi0lGXNOeBXwFKM+fmUwrwdKiEEtoDDNk9sD4ixvSbpR6DnKpX+mXq9Nfr40OYBm9MS50JIrRjzPwFIZGAFu7IFmgBP3juwKNpXbf8Q+Ibk04+Zad3Wm7a/VK3e+F5RdN4bxj8peVyCYPu27RFb07sniDFftvUK6Pl6vfXyo0CzrPl92z8Gn48xf+Xtt39x11xsnrNZB1KIsd2TSBIHs6yl++Ht9yWfkfzVer314kOgp4Hv2vy8LPPf7P5v+yug67bSUDjqgmd093tnK4r8Q4mXJV5oNFrPPxjaehU4Dn4txrzcw6/mwO9KqR+GK+kCnwFV9sqmKPJV4DXwt7Os+ald0EXbt8oyf70sOzcevO8LRyQfs7VcFB0HgBBYBo7aVPcrZVm2u6BfAl+v18+ODCd8KQR+H+P+hpOSjgPrIbgLDEA27wLTDwM3Gm8crFRuXen1xt+SQi3LmjWJ3/b7Xn+Y6ELwF22t2fovQJifPzsGlcugCYmJfc7lnK3DFy68mcoyX+n3fX1rqxcvXsyvSkxnWfPk3n6+WLE1ByzZDMAphcmybF21XQVN7qHWF2xGUqq+tx17552f3VpaOpcGW5BfAcbr9daXHzTeTjOgI7bKGNtbAKEoOqtDR+kCUw9Q63dAG7bKS5daae9ieknygUajdfJ+sJ4CT4D/ebf0gzL+NEh6C3xslwN9Dbhj+28xtvv7C6+zKRFtvlCvNz+xE8yngbUQvLQDXBTnUkr+O+jYPZmetF2z019izHuP4lpFkf9H4nIIZFnW2lb9iOSnbV0sis76DjBApbJxQfKTQ+hhm8/Z4c8xdm49jk8XRbuwCcCw5GEMNAv+ww4r2SWiH0D4ne0TkpbLcvF/H/1KbH4TKEHJ9pmyzNv7gBeOQhgdmInHgVmJGYnplDwlhRFwFSzQFrBp+3oIWrXpAv+WWC6KfGVYuRPDl2mtLNt/3RM8WOnZgymFGUmzwCHgwFDtB+65Pp8AxobdkoOtChAkG3TH5l9AtP2PEFguik5/XzDAqVNvqFIR0mAa2wohkJKlwUNbkofjhZ2242GQoGSTJPopuR9j575j+H+W9nlG4uJsnAAAAABJRU5ErkJggg==)](https://lists.openstreetmap.org/listinfo/umap)
63
+
64
+ [![Liberapay](https://img.shields.io/liberapay/patrons/uMap.svg?logo=liberapay)](https://liberapay.com/uMap)
65
+ [![OpenCollective](https://img.shields.io/opencollective/all/uMap?logo=opencollective)](https://opencollective.com/uMap)
66
+ [![GitHub Sponsors](https://img.shields.io/github/sponsors/umap-project?logo=github)](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)