umap-project 2.6.3__py3-none-any.whl → 2.7.0b0__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 (104) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +64 -1
  3. umap/context_processors.py +1 -0
  4. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/cs_CZ/LC_MESSAGES/django.po +96 -92
  6. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/de/LC_MESSAGES/django.po +19 -18
  8. umap/locale/en/LC_MESSAGES/django.po +47 -43
  9. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/fr/LC_MESSAGES/django.po +51 -47
  11. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/pt/LC_MESSAGES/django.po +64 -60
  13. umap/management/commands/clean_tilelayer.py +152 -0
  14. umap/management/commands/purge_purgatory.py +28 -0
  15. umap/models.py +27 -2
  16. umap/settings/base.py +2 -0
  17. umap/static/umap/base.css +4 -4
  18. umap/static/umap/css/contextmenu.css +5 -0
  19. umap/static/umap/css/icon.css +7 -2
  20. umap/static/umap/img/16-white.svg +9 -2
  21. umap/static/umap/img/16.svg +3 -0
  22. umap/static/umap/img/source/16-white.svg +10 -3
  23. umap/static/umap/img/source/16.svg +4 -1
  24. umap/static/umap/js/modules/autocomplete.js +7 -3
  25. umap/static/umap/js/modules/browser.js +7 -1
  26. umap/static/umap/js/modules/caption.js +6 -1
  27. umap/static/umap/js/modules/data/features.js +176 -2
  28. umap/static/umap/js/modules/data/layer.js +31 -26
  29. umap/static/umap/js/modules/formatter.js +3 -2
  30. umap/static/umap/js/modules/global.js +2 -0
  31. umap/static/umap/js/modules/importers/communesfr.js +13 -1
  32. umap/static/umap/js/modules/permissions.js +123 -93
  33. umap/static/umap/js/modules/rendering/ui.js +37 -212
  34. umap/static/umap/js/modules/sync/engine.js +365 -14
  35. umap/static/umap/js/modules/sync/hlc.js +106 -0
  36. umap/static/umap/js/modules/sync/updaters.js +4 -4
  37. umap/static/umap/js/modules/sync/websocket.js +1 -1
  38. umap/static/umap/js/modules/ui/base.js +2 -2
  39. umap/static/umap/js/modules/ui/contextmenu.js +34 -17
  40. umap/static/umap/js/modules/urls.js +5 -1
  41. umap/static/umap/js/modules/utils.js +5 -1
  42. umap/static/umap/js/umap.controls.js +47 -47
  43. umap/static/umap/js/umap.core.js +3 -3
  44. umap/static/umap/js/umap.forms.js +3 -1
  45. umap/static/umap/js/umap.js +95 -112
  46. umap/static/umap/locale/br.js +13 -4
  47. umap/static/umap/locale/br.json +13 -4
  48. umap/static/umap/locale/ca.js +21 -12
  49. umap/static/umap/locale/ca.json +21 -12
  50. umap/static/umap/locale/cs_CZ.js +87 -78
  51. umap/static/umap/locale/cs_CZ.json +87 -78
  52. umap/static/umap/locale/de.js +17 -8
  53. umap/static/umap/locale/de.json +17 -8
  54. umap/static/umap/locale/en.js +9 -2
  55. umap/static/umap/locale/en.json +9 -2
  56. umap/static/umap/locale/eu.js +10 -3
  57. umap/static/umap/locale/eu.json +10 -3
  58. umap/static/umap/locale/fa_IR.js +11 -4
  59. umap/static/umap/locale/fa_IR.json +11 -4
  60. umap/static/umap/locale/fr.js +11 -4
  61. umap/static/umap/locale/fr.json +11 -4
  62. umap/static/umap/locale/hu.js +10 -3
  63. umap/static/umap/locale/hu.json +10 -3
  64. umap/static/umap/locale/pt.js +17 -8
  65. umap/static/umap/locale/pt.json +17 -8
  66. umap/static/umap/locale/pt_PT.js +13 -4
  67. umap/static/umap/locale/pt_PT.json +13 -4
  68. umap/static/umap/locale/zh_TW.js +13 -4
  69. umap/static/umap/locale/zh_TW.json +13 -4
  70. umap/static/umap/map.css +7 -22
  71. umap/static/umap/unittests/hlc.js +158 -0
  72. umap/static/umap/unittests/sync.js +321 -15
  73. umap/static/umap/unittests/utils.js +23 -0
  74. umap/static/umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js +111 -80
  75. umap/templates/umap/dashboard_menu.html +4 -2
  76. umap/templates/umap/js.html +0 -4
  77. umap/tests/integration/test_anonymous_owned_map.py +1 -0
  78. umap/tests/integration/test_basics.py +1 -1
  79. umap/tests/integration/test_circles_layer.py +12 -0
  80. umap/tests/integration/test_datalayer.py +5 -0
  81. umap/tests/integration/test_draw_polygon.py +17 -9
  82. umap/tests/integration/test_draw_polyline.py +12 -8
  83. umap/tests/integration/test_edit_datalayer.py +4 -6
  84. umap/tests/integration/test_edit_map.py +1 -1
  85. umap/tests/integration/test_import.py +5 -0
  86. umap/tests/integration/test_map.py +5 -0
  87. umap/tests/integration/test_owned_map.py +1 -1
  88. umap/tests/integration/test_view_polygon.py +12 -12
  89. umap/tests/integration/test_websocket_sync.py +65 -3
  90. umap/tests/test_clean_tilelayer.py +83 -0
  91. umap/tests/test_datalayer.py +24 -0
  92. umap/tests/test_map_views.py +1 -0
  93. umap/tests/test_purge_purgatory.py +25 -0
  94. umap/tests/test_websocket_server.py +22 -0
  95. umap/urls.py +5 -1
  96. umap/views.py +6 -3
  97. umap/websocket_server.py +130 -27
  98. {umap_project-2.6.3.dist-info → umap_project-2.7.0b0.dist-info}/METADATA +9 -9
  99. {umap_project-2.6.3.dist-info → umap_project-2.7.0b0.dist-info}/RECORD +102 -97
  100. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css +0 -1
  101. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js +0 -7
  102. {umap_project-2.6.3.dist-info → umap_project-2.7.0b0.dist-info}/WHEEL +0 -0
  103. {umap_project-2.6.3.dist-info → umap_project-2.7.0b0.dist-info}/entry_points.txt +0 -0
  104. {umap_project-2.6.3.dist-info → umap_project-2.7.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -10,7 +10,7 @@ msgid ""
10
10
  msgstr ""
11
11
  "Project-Id-Version: uMap\n"
12
12
  "Report-Msgid-Bugs-To: \n"
13
- "POT-Creation-Date: 2024-08-30 18:23+0000\n"
13
+ "POT-Creation-Date: 2024-10-04 16:35+0000\n"
14
14
  "PO-Revision-Date: 2013-11-22 14:00+0000\n"
15
15
  "Last-Translator: lecalam, 2024\n"
16
16
  "Language-Team: Portuguese (http://app.transifex.com/openstreetmap/umap/language/pt/)\n"
@@ -20,6 +20,10 @@ msgstr ""
20
20
  "Language: pt\n"
21
21
  "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
22
22
 
23
+ #: admin.py:16
24
+ msgid "CSV Export"
25
+ msgstr ""
26
+
23
27
  #: forms.py:44 forms.py:70
24
28
  msgid "Only editable with secret edit link"
25
29
  msgstr "Unicamente editável através de hiperligação secreta"
@@ -28,7 +32,7 @@ msgstr "Unicamente editável através de hiperligação secreta"
28
32
  msgid "Everyone can edit"
29
33
  msgstr "Todos podem editar"
30
34
 
31
- #: forms.py:69 models.py:423
35
+ #: forms.py:69 models.py:441
32
36
  msgid "Inherit"
33
37
  msgstr "Herdado"
34
38
 
@@ -36,111 +40,111 @@ msgstr "Herdado"
36
40
  msgid "Site is readonly for maintenance"
37
41
  msgstr "O site está em modo de leitura para manutenção"
38
42
 
39
- #: models.py:54 models.py:73
43
+ #: models.py:55 models.py:74
40
44
  msgid "name"
41
45
  msgstr "nome"
42
46
 
43
- #: models.py:56 models.py:433
47
+ #: models.py:57 models.py:451
44
48
  msgid "description"
45
49
  msgstr "descrição"
46
50
 
47
- #: models.py:104
51
+ #: models.py:105
48
52
  msgid "details"
49
53
  msgstr "detalhes"
50
54
 
51
- #: models.py:105
55
+ #: models.py:106
52
56
  msgid "Link to a page where the licence is detailed."
53
57
  msgstr "Hiperligação para uma página detalhando a licença."
54
58
 
55
- #: models.py:115
59
+ #: models.py:116
56
60
  msgid "URL template using OSM tile format"
57
61
  msgstr "Modelo de URL no formato de mosaicos OSM"
58
62
 
59
- #: models.py:121
63
+ #: models.py:122
60
64
  msgid "Order of the tilelayers in the edit box"
61
65
  msgstr "Ordem das camadas de mosaicos na caixa de edição"
62
66
 
63
- #: models.py:167 models.py:424
67
+ #: models.py:168 models.py:442
64
68
  msgid "Everyone"
65
69
  msgstr "Todos"
66
70
 
67
- #: models.py:168 models.py:174 models.py:425
71
+ #: models.py:169 models.py:175 models.py:443
68
72
  msgid "Editors and team only"
69
- msgstr ""
73
+ msgstr "Apenas editores e equipa"
70
74
 
71
- #: models.py:169 models.py:426
75
+ #: models.py:170 models.py:444
72
76
  msgid "Owner only"
73
77
  msgstr "Apenas o proprietário"
74
78
 
75
- #: models.py:172
79
+ #: models.py:173
76
80
  msgid "Everyone (public)"
77
81
  msgstr "Todos (público)"
78
82
 
79
- #: models.py:173
83
+ #: models.py:174
80
84
  msgid "Anyone with link"
81
85
  msgstr "Quem tiver a hiperligação"
82
86
 
83
- #: models.py:175
87
+ #: models.py:176
84
88
  msgid "Blocked"
85
89
  msgstr "Bloqueado"
86
90
 
87
- #: models.py:178
91
+ #: models.py:179
88
92
  msgid "center"
89
93
  msgstr "centro"
90
94
 
91
- #: models.py:179
95
+ #: models.py:180
92
96
  msgid "zoom"
93
97
  msgstr "zoom"
94
98
 
95
- #: models.py:181
99
+ #: models.py:182
96
100
  msgid "locate"
97
101
  msgstr "localizar"
98
102
 
99
- #: models.py:181
103
+ #: models.py:182
100
104
  msgid "Locate user on load?"
101
105
  msgstr "Localizar utilizador no início?"
102
106
 
103
- #: models.py:185
107
+ #: models.py:186
104
108
  msgid "Choose the map licence."
105
109
  msgstr "Escolha uma licença para o mapa."
106
110
 
107
- #: models.py:186
111
+ #: models.py:187
108
112
  msgid "licence"
109
113
  msgstr "licença"
110
114
 
111
- #: models.py:197
115
+ #: models.py:198
112
116
  msgid "owner"
113
117
  msgstr "proprietário"
114
118
 
115
- #: models.py:201
119
+ #: models.py:202
116
120
  msgid "editors"
117
121
  msgstr "editores"
118
122
 
119
- #: models.py:207
123
+ #: models.py:208
120
124
  msgid "team"
121
- msgstr ""
125
+ msgstr "equipa"
122
126
 
123
- #: models.py:213 models.py:447
127
+ #: models.py:214 models.py:465
124
128
  msgid "edit status"
125
129
  msgstr "editar estado"
126
130
 
127
- #: models.py:218
131
+ #: models.py:219
128
132
  msgid "share status"
129
133
  msgstr "partilhar estado"
130
134
 
131
- #: models.py:221 models.py:442
135
+ #: models.py:222 models.py:460
132
136
  msgid "settings"
133
137
  msgstr "parâmetros"
134
138
 
135
- #: models.py:364
139
+ #: models.py:382
136
140
  msgid "Clone of"
137
141
  msgstr "Clone de"
138
142
 
139
- #: models.py:437
143
+ #: models.py:455
140
144
  msgid "display on load"
141
145
  msgstr "mostrar no início"
142
146
 
143
- #: models.py:438
147
+ #: models.py:456
144
148
  msgid "Display this layer on load."
145
149
  msgstr "Mostrar esta camada ao carregar."
146
150
 
@@ -324,19 +328,19 @@ msgstr "Esta é uma versão de demonstração, utilizada para testes e pré-lan
324
328
 
325
329
  #: templates/umap/content_footer.html:5
326
330
  msgid "An OpenStreetMap project"
327
- msgstr ""
331
+ msgstr "Um projeto OpenStreetMap"
328
332
 
329
333
  #: templates/umap/content_footer.html:6
330
334
  msgid "version"
331
- msgstr ""
335
+ msgstr "versão"
332
336
 
333
337
  #: templates/umap/content_footer.html:7
334
338
  msgid "Hosted by"
335
- msgstr ""
339
+ msgstr "Alojado por"
336
340
 
337
341
  #: templates/umap/content_footer.html:8
338
342
  msgid "Contact"
339
- msgstr ""
343
+ msgstr "Contacto"
340
344
 
341
345
  #: templates/umap/content_footer.html:9 templates/umap/navigation.html:25
342
346
  msgid "Help"
@@ -351,13 +355,13 @@ msgstr "Meus mapas (%(count)s)"
351
355
  msgid "My Maps"
352
356
  msgstr "Meus mapas"
353
357
 
354
- #: templates/umap/dashboard_menu.html:11
358
+ #: templates/umap/dashboard_menu.html:12
355
359
  msgid "My profile"
356
360
  msgstr "Meu perfil"
357
361
 
358
- #: templates/umap/dashboard_menu.html:13
362
+ #: templates/umap/dashboard_menu.html:15
359
363
  msgid "My teams"
360
- msgstr ""
364
+ msgstr "Minhas equipas"
361
365
 
362
366
  #: templates/umap/home.html:14
363
367
  msgid "Map of the uMaps"
@@ -371,11 +375,11 @@ msgstr "Inspire-se, explore os mapas"
371
375
  msgid "You are logged in. Continuing..."
372
376
  msgstr "Sucesso na identificação. Continuando..."
373
377
 
374
- #: templates/umap/map_list.html:10 views.py:433
378
+ #: templates/umap/map_list.html:11 views.py:433
375
379
  msgid "by"
376
380
  msgstr "por"
377
381
 
378
- #: templates/umap/map_list.html:18
382
+ #: templates/umap/map_list.html:20
379
383
  msgid "More"
380
384
  msgstr "Mais"
381
385
 
@@ -552,20 +556,20 @@ msgstr "Procurar"
552
556
  #: templates/umap/team_detail.html:10
553
557
  #, python-format
554
558
  msgid "Browse %(current_team)s's maps"
555
- msgstr ""
559
+ msgstr "Ver mapas de %(current_team)s"
556
560
 
557
561
  #: templates/umap/team_detail.html:22
558
562
  #, python-format
559
563
  msgid "%(current_team)s has no public maps."
560
- msgstr ""
564
+ msgstr "%(current_team)s não tem mapas públicos."
561
565
 
562
566
  #: templates/umap/team_form.html:24
563
567
  msgid "Delete this team"
564
- msgstr ""
568
+ msgstr "Eliminar esta equipa"
565
569
 
566
570
  #: templates/umap/team_form.html:47
567
571
  msgid "Add user"
568
- msgstr ""
572
+ msgstr "Adicionar utilizador"
569
573
 
570
574
  #: templates/umap/user_dashboard.html:9 templates/umap/user_dashboard.html:25
571
575
  msgid "Search my maps"
@@ -586,76 +590,76 @@ msgstr "Ainda não tem nenhum mapa."
586
590
 
587
591
  #: templates/umap/user_teams.html:17
588
592
  msgid "Users"
589
- msgstr ""
593
+ msgstr "Utilizadores"
590
594
 
591
595
  #: templates/umap/user_teams.html:48
592
596
  msgid "New team"
593
- msgstr ""
597
+ msgstr "Nova equipa"
594
598
 
595
599
  #: views.py:235
596
600
  msgid "Cannot delete a team with more than one member"
597
- msgstr ""
601
+ msgstr "Não é possível eliminar uma equipa com mais de um membro"
598
602
 
599
603
  #: views.py:239
600
604
  #, python-format
601
605
  msgid "Team “%(name)s” has been deleted"
602
- msgstr ""
606
+ msgstr "A equipa “%(name)s” foi eliminada"
603
607
 
604
608
  #: views.py:438
605
609
  msgid "View the map"
606
610
  msgstr "Ver o mapa"
607
611
 
608
- #: views.py:824
612
+ #: views.py:818
609
613
  msgid "See full screen"
610
614
  msgstr "Ver em ecrã inteiro"
611
615
 
612
- #: views.py:953
616
+ #: views.py:950
613
617
  msgid "Map editors updated with success!"
614
618
  msgstr "Os editores do mapa foram atualizados com sucesso!"
615
619
 
616
- #: views.py:989
620
+ #: views.py:986
617
621
  #, python-format
618
622
  msgid "The uMap edit link for your map: %(map_name)s"
619
623
  msgstr "A hiperligação de edição do uMap para o seu mapa: %(map_name)s"
620
624
 
621
- #: views.py:992
625
+ #: views.py:989
622
626
  #, python-format
623
627
  msgid "Here is your secret edit link: %(link)s"
624
628
  msgstr "Aqui está a hiperligação de edição secreta: %(link)s"
625
629
 
626
- #: views.py:999
630
+ #: views.py:996
627
631
  #, python-format
628
632
  msgid "Can't send email to %(email)s"
629
633
  msgstr "Não é possível enviar o email para %(email)s"
630
634
 
631
- #: views.py:1002
635
+ #: views.py:999
632
636
  #, python-format
633
637
  msgid "Email sent to %(email)s"
634
638
  msgstr "Email enviado para %(email)s"
635
639
 
636
- #: views.py:1013
640
+ #: views.py:1010
637
641
  msgid "Only its owner can delete the map."
638
642
  msgstr "Só o proprietário pode eliminar o mapa."
639
643
 
640
- #: views.py:1016
644
+ #: views.py:1013
641
645
  msgid "Map successfully deleted."
642
646
  msgstr "Mapa eliminado com sucesso."
643
647
 
644
- #: views.py:1042
648
+ #: views.py:1039
645
649
  #, python-format
646
650
  msgid ""
647
651
  "Your map has been cloned! If you want to edit this map from another "
648
652
  "computer, please use this link: %(anonymous_url)s"
649
653
  msgstr "O seu mapa foi clonado! Se quiser editar este mapa noutro computador, por favor utilize esta hiperligação: %(anonymous_url)s"
650
654
 
651
- #: views.py:1047
655
+ #: views.py:1044
652
656
  msgid "Congratulations, your map has been cloned!"
653
657
  msgstr "Parabéns, o seu mapa foi clonado!"
654
658
 
655
- #: views.py:1282
659
+ #: views.py:1277
656
660
  msgid "Layer successfully deleted."
657
661
  msgstr "Camada eliminada com sucesso."
658
662
 
659
- #: views.py:1304
663
+ #: views.py:1299
660
664
  msgid "Permissions updated with success!"
661
665
  msgstr "Permissões atualizadas com sucesso!"
@@ -0,0 +1,152 @@
1
+ import json
2
+ import sys
3
+
4
+ from django.core.management.base import BaseCommand
5
+ from django.db import connection
6
+ from psycopg.types.json import Jsonb
7
+
8
+ from umap.models import Map, TileLayer
9
+
10
+
11
+ class Command(BaseCommand):
12
+ help = """Clean tilelayer in database
13
+
14
+ This will simply replace the URL in maps settings:
15
+ umap clean_tilelayer http://my.old/url/template http://my.new/url/template
16
+
17
+ This will replace the whole tilelayer in maps settings by the one with this name:
18
+ umap clean_tilelayer http://my.old/url/template "some string"
19
+
20
+ This will replace the whole tilelayer in maps settings by the one with this id:
21
+ umap clean_tilelayer http://my.old/url/template an_id
22
+
23
+ This will delete the whole tilelayer from maps settings:
24
+ umap clean_tilelayer http://my.old/url/template
25
+
26
+ To get the available tilelayers in db (available for users):
27
+ umap clean_tilelayer --available
28
+
29
+ To get statistics of tilelayers usage in db (including custom ones):
30
+ umap clean_tilelayer --available
31
+ """
32
+
33
+ def add_arguments(self, parser):
34
+ parser.add_argument("old", nargs="?", help="url template we want to clean")
35
+ parser.add_argument(
36
+ "new", help="what to replace this tilelayer with", nargs="?"
37
+ )
38
+ parser.add_argument(
39
+ "--no-input", action="store_true", help="Do not ask for confirm."
40
+ )
41
+ parser.add_argument(
42
+ "--available", action="store_true", help="List known tilelayers."
43
+ )
44
+ parser.add_argument(
45
+ "--stats", action="store_true", help="Display stats on tilelayer usage."
46
+ )
47
+
48
+ def handle(self, *args, **options):
49
+ self.no_input = options["no_input"]
50
+ if options["available"]:
51
+ self.list_available()
52
+ sys.exit()
53
+ if options["stats"]:
54
+ self.stats()
55
+ sys.exit()
56
+ old = options["old"]
57
+ new = options["new"]
58
+ if not old:
59
+ sys.exit("⚠ You must define an url_template")
60
+
61
+ count = Map.objects.filter(
62
+ settings__properties__tilelayer__url_template=old
63
+ ).count()
64
+ if not count:
65
+ self.stdout.write("⚠ No map found. Exiting.")
66
+ sys.exit()
67
+ self.stdout.write(f"{count} maps found.")
68
+ if not new:
69
+ self.delete(old)
70
+ elif new.startswith("http"):
71
+ self.replace_url(old, new)
72
+ else:
73
+ # Let's consider it's a name or an id
74
+ self.replace_tilelayer(old, new)
75
+
76
+ def confirm(self, message):
77
+ if self.no_input:
78
+ return True
79
+ result = input("%s (y/N) " % message) or "n"
80
+ if not result[0].lower() == "y":
81
+ self.stdout.write("⚠ Action cancelled.")
82
+ sys.exit()
83
+ return True
84
+
85
+ def delete(self, old):
86
+ if self.confirm(
87
+ "Are you sure you want to delete the tilelayer key from all those "
88
+ "maps settings ?"
89
+ ):
90
+ with connection.cursor() as cursor:
91
+ ret = cursor.execute(
92
+ "UPDATE umap_map "
93
+ "SET settings['properties'] = (settings->'properties') - 'tilelayer'"
94
+ "WHERE settings->'properties'->'tilelayer'->'url_template' = %s",
95
+ [Jsonb(old)],
96
+ )
97
+ self.stdout.write(f"✔ Deleted {old} from {ret.rowcount} maps.")
98
+
99
+ def replace_url(self, old, new):
100
+ if self.confirm(
101
+ f"Are you sure you want to replace '{old}'' by '{new}'' from all those "
102
+ "map settings ?"
103
+ ):
104
+ with connection.cursor() as cursor:
105
+ ret = cursor.execute(
106
+ "UPDATE umap_map "
107
+ "SET settings['properties']['tilelayer']['url_template'] = %s "
108
+ "WHERE settings->'properties'->'tilelayer'->'url_template' = %s",
109
+ [Jsonb(new), Jsonb(old)],
110
+ )
111
+ self.stdout.write(f"✔ Replaced {old} by {new} in {ret.rowcount} maps.")
112
+
113
+ def replace_tilelayer(self, old, new):
114
+ try:
115
+ tilelayer = TileLayer.objects.get(name=new)
116
+ except TileLayer.DoesNotExist:
117
+ try:
118
+ tilelayer = TileLayer.objects.get(id=new)
119
+ except (TileLayer.DoesNotExist, ValueError):
120
+ sys.exit(f"⚠ Cannot find a TileLayer with name or id = '{new}'.")
121
+ if self.confirm(
122
+ f"Are you sure you want to replace {old} by '{tilelayer.name}' "
123
+ "from all those map settings ?"
124
+ ):
125
+ with connection.cursor() as cursor:
126
+ ret = cursor.execute(
127
+ "UPDATE umap_map "
128
+ "SET settings['properties']['tilelayer'] = %s "
129
+ "WHERE settings->'properties'->'tilelayer'->'url_template' = %s",
130
+ [Jsonb(tilelayer.json), Jsonb(old)],
131
+ )
132
+ self.stdout.write(
133
+ f"✔ Replaced {old} by {tilelayer.name} in {ret.rowcount} maps."
134
+ )
135
+
136
+ def list_available(self):
137
+ tilelayers = TileLayer.objects.all()
138
+ for tilelayer in tilelayers:
139
+ print(f"{tilelayer.pk} '{tilelayer.name}' {tilelayer.url_template}")
140
+
141
+ def stats(self):
142
+ with connection.cursor() as cursor:
143
+ cursor.execute(
144
+ "SELECT COUNT(*) as count, "
145
+ "settings->'properties'->'tilelayer'->'url_template' as url "
146
+ "FROM umap_map "
147
+ "GROUP BY settings->'properties'->'tilelayer'->'url_template' "
148
+ "ORDER BY count DESC"
149
+ )
150
+ res = cursor.fetchall()
151
+ for count, url in res:
152
+ print(f"{count}\t{url}")
@@ -0,0 +1,28 @@
1
+ import time
2
+ from pathlib import Path
3
+
4
+ from django.conf import settings
5
+ from django.core.management.base import BaseCommand
6
+
7
+
8
+ class Command(BaseCommand):
9
+ help = "Remove old files from purgatory. Eg.: umap purge_purgatory --days 7"
10
+
11
+ def add_arguments(self, parser):
12
+ parser.add_argument(
13
+ "--days",
14
+ help="Number of days to consider files for removal",
15
+ default=30,
16
+ type=int,
17
+ )
18
+
19
+ def handle(self, *args, **options):
20
+ days = options["days"]
21
+ root = Path(settings.UMAP_PURGATORY_ROOT)
22
+ threshold = time.time() - days * 86400
23
+ for path in root.iterdir():
24
+ stats = path.stat()
25
+ filestamp = stats.st_mtime
26
+ if filestamp < threshold:
27
+ path.unlink()
28
+ print(f"Removed old file {path}")
umap/models.py CHANGED
@@ -3,6 +3,7 @@ import operator
3
3
  import os
4
4
  import time
5
5
  import uuid
6
+ from pathlib import Path
6
7
 
7
8
  from django.conf import settings
8
9
  from django.contrib.auth.models import User
@@ -255,6 +256,13 @@ class Map(NamedModel):
255
256
  )
256
257
  return map_settings
257
258
 
259
+ def delete(self, **kwargs):
260
+ # Explicitely call datalayers.delete, so we can deal with removing files
261
+ # (the cascade delete would not call the model delete method)
262
+ for datalayer in self.datalayer_set.all():
263
+ datalayer.delete()
264
+ return super().delete(**kwargs)
265
+
258
266
  def generate_umapjson(self, request):
259
267
  umapjson = self.settings
260
268
  umapjson["type"] = "umap"
@@ -462,7 +470,9 @@ class DataLayer(NamedModel):
462
470
 
463
471
  def save(self, force_insert=False, force_update=False, **kwargs):
464
472
  is_new = not bool(self.pk)
465
- super(DataLayer, self).save(force_insert, force_update, **kwargs)
473
+ super(DataLayer, self).save(
474
+ force_insert=force_insert, force_update=force_update, **kwargs
475
+ )
466
476
 
467
477
  if is_new:
468
478
  force_insert, force_update = False, True
@@ -471,10 +481,25 @@ class DataLayer(NamedModel):
471
481
  new_name = self.geojson.storage.save(filename, self.geojson)
472
482
  self.geojson.storage.delete(old_name)
473
483
  self.geojson.name = new_name
474
- super(DataLayer, self).save(force_insert, force_update, **kwargs)
484
+ super(DataLayer, self).save(
485
+ force_insert=force_insert, force_update=force_update, **kwargs
486
+ )
475
487
  self.purge_gzip()
476
488
  self.purge_old_versions()
477
489
 
490
+ def delete(self, **kwargs):
491
+ self.purge_gzip()
492
+ self.to_purgatory()
493
+ return super().delete(**kwargs)
494
+
495
+ def to_purgatory(self):
496
+ dest = Path(settings.UMAP_PURGATORY_ROOT)
497
+ dest.mkdir(parents=True, exist_ok=True)
498
+ src = Path(self.geojson.storage.location) / self.storage_root()
499
+ for version in self.versions:
500
+ name = version["name"]
501
+ (src / name).rename(dest / f"{self.map.pk}_{name}")
502
+
478
503
  def upload_to(self):
479
504
  root = self.storage_root()
480
505
  name = "%s_%s.geojson" % (self.pk, int(time.time() * 1000))
umap/settings/base.py CHANGED
@@ -240,6 +240,7 @@ USER_URL_FIELD = "username"
240
240
  # Miscellaneous project settings
241
241
  # =============================================================================
242
242
  UMAP_ALLOW_ANONYMOUS = env.bool("UMAP_ALLOW_ANONYMOUS", default=False)
243
+ UMAP_ALLOW_EDIT_PROFILE = env.bool("UMAP_ALLOW_EDIT_PROFILE", default=True)
243
244
 
244
245
  UMAP_EXTRA_URLS = {
245
246
  "routing": "http://www.openstreetmap.org/directions?engine=osrm_car&route={lat},{lng}&locale={locale}#map={zoom}/{lat}/{lng}", # noqa
@@ -266,6 +267,7 @@ UMAP_DEFAULT_FEATURES_HAVE_OWNERS = False
266
267
  UMAP_HOME_FEED = "latest"
267
268
  UMAP_IMPORTERS = {}
268
269
  UMAP_HOST_INFOS = {}
270
+ UMAP_PURGATORY_ROOT = "/tmp/umappurgatory"
269
271
 
270
272
  UMAP_READONLY = env("UMAP_READONLY", default=False)
271
273
  UMAP_GZIP = True
umap/static/umap/base.css CHANGED
@@ -307,9 +307,6 @@ input + .help-text {
307
307
  .formbox.with-switch {
308
308
  padding-top: 2px;
309
309
  }
310
- .formbox select {
311
- width: calc(100% - 14px);
312
- }
313
310
  fieldset.formbox {
314
311
  border: none;
315
312
  border-top: 1px solid var(--color-lightGray);
@@ -386,6 +383,10 @@ fieldset legend {
386
383
  font-size: .9rem;
387
384
  padding: 0 5px;
388
385
  }
386
+ fieldset.separator {
387
+ border: none;
388
+ border-top: 1px solid var(--color-lightGray);
389
+ }
389
390
 
390
391
  [data-badge] {
391
392
  position: relative;
@@ -633,7 +634,6 @@ i.info {
633
634
  .umap-datalayer-container,
634
635
  .umap-layer-properties-container,
635
636
  .umap-browse-data,
636
- .umap-facet-search,
637
637
  .umap-tilelayer-switcher-container {
638
638
  padding: 0 10px;
639
639
  }
@@ -9,3 +9,8 @@
9
9
  .umap-contextmenu li + li {
10
10
  margin-top: var(--text-margin);
11
11
  }
12
+
13
+ .umap-contextmenu hr {
14
+ margin-top: var(--text-margin);
15
+ margin-bottom: var(--text-margin);
16
+ }
@@ -35,9 +35,11 @@ html[dir="rtl"] .icon {
35
35
  .icon-block + span {
36
36
  margin-inline-start: 0;
37
37
  }
38
+ .icon-16.icon-white,
38
39
  .dark .icon-16 {
39
40
  background-image: url('../img/16-white.svg');
40
41
  }
42
+ .icon-24.icon-white,
41
43
  .dark .icon-24 {
42
44
  background-image: url('../img/24-white.svg');
43
45
  }
@@ -105,10 +107,13 @@ html[dir="rtl"] .icon {
105
107
  background-position: calc(var(--tile) * 3) calc(var(--tile) * 5);
106
108
  }
107
109
  .icon-polygon {
108
- background-position: var(--tile) calc(var(--tile) * 5);
110
+ background-position: var(--tile) calc(var(--tile) * 3);
109
111
  }
110
112
  .icon-polyline {
111
- background-position: 0 calc(var(--tile) * 5);
113
+ background-position: 0 calc(var(--tile) * 3);
114
+ }
115
+ .icon-profile {
116
+ background-position: 0 calc(var(--tile) * 4);
112
117
  }
113
118
  .icon-resize {
114
119
  background-position: calc(var(--tile) * 3) calc(var(--tile) * 6);