umap-project 1.10.0__py3-none-any.whl → 1.11.1__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 (181) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +7 -3
  3. umap/autocomplete.py +3 -5
  4. umap/bin/__init__.py +2 -5
  5. umap/decorators.py +4 -5
  6. umap/forms.py +5 -5
  7. umap/locale/en/LC_MESSAGES/django.po +23 -23
  8. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  9. umap/locale/fr/LC_MESSAGES/django.po +28 -27
  10. umap/management/commands/generate_js_locale.py +9 -11
  11. umap/management/commands/import_pictograms.py +49 -28
  12. umap/managers.py +5 -3
  13. umap/middleware.py +2 -3
  14. umap/migrations/0001_initial.py +204 -56
  15. umap/migrations/0002_tilelayer_tms.py +3 -4
  16. umap/migrations/0003_add_tilelayer.py +13 -9
  17. umap/migrations/0004_add_licence.py +3 -6
  18. umap/migrations/0005_remove_map_tilelayer.py +3 -4
  19. umap/migrations/0006_auto_20190407_0719.py +6 -5
  20. umap/migrations/0007_auto_20190416_1757.py +13 -5
  21. umap/migrations/0008_alter_map_settings.py +0 -1
  22. umap/migrations/0009_star.py +27 -8
  23. umap/migrations/0010_alter_map_edit_status_alter_map_share_status.py +20 -8
  24. umap/migrations/0011_alter_map_edit_status_alter_map_share_status.py +21 -8
  25. umap/migrations/0014_map_created_at.py +1 -1
  26. umap/migrations/0015_alter_pictogram_pictogram.py +17 -0
  27. umap/migrations/0016_pictogram_category.py +17 -0
  28. umap/models.py +9 -7
  29. umap/settings/base.py +1 -4
  30. umap/static/umap/base.css +59 -20
  31. umap/static/umap/content.css +2 -13
  32. umap/static/umap/favicons/apple-touch-icon.png +0 -0
  33. umap/static/umap/favicons/favicon.ico +0 -0
  34. umap/static/umap/favicons/icon-192.png +0 -0
  35. umap/static/umap/favicons/icon-512.png +0 -0
  36. umap/static/umap/favicons/icon.svg +5 -0
  37. umap/static/umap/img/16-white.svg +20 -9
  38. umap/static/umap/img/24-white.svg +2 -2
  39. umap/static/umap/img/source/16-white.svg +25 -13
  40. umap/static/umap/img/source/24-white.svg +5 -5
  41. umap/static/umap/js/umap.controls.js +15 -23
  42. umap/static/umap/js/umap.core.js +33 -24
  43. umap/static/umap/js/umap.features.js +24 -2
  44. umap/static/umap/js/umap.forms.js +182 -84
  45. umap/static/umap/js/umap.icon.js +19 -14
  46. umap/static/umap/js/umap.js +14 -32
  47. umap/static/umap/js/umap.layer.js +13 -2
  48. umap/static/umap/js/umap.popup.js +21 -0
  49. umap/static/umap/locale/am_ET.js +9 -4
  50. umap/static/umap/locale/am_ET.json +9 -4
  51. umap/static/umap/locale/ar.js +9 -4
  52. umap/static/umap/locale/ar.json +9 -4
  53. umap/static/umap/locale/ast.js +9 -4
  54. umap/static/umap/locale/ast.json +9 -4
  55. umap/static/umap/locale/bg.js +9 -4
  56. umap/static/umap/locale/bg.json +9 -4
  57. umap/static/umap/locale/br.js +20 -15
  58. umap/static/umap/locale/br.json +20 -15
  59. umap/static/umap/locale/ca.js +9 -4
  60. umap/static/umap/locale/ca.json +9 -4
  61. umap/static/umap/locale/cs_CZ.js +9 -4
  62. umap/static/umap/locale/cs_CZ.json +9 -4
  63. umap/static/umap/locale/da.js +9 -4
  64. umap/static/umap/locale/da.json +9 -4
  65. umap/static/umap/locale/de.js +9 -4
  66. umap/static/umap/locale/de.json +9 -4
  67. umap/static/umap/locale/el.js +9 -4
  68. umap/static/umap/locale/el.json +9 -4
  69. umap/static/umap/locale/en.js +9 -4
  70. umap/static/umap/locale/en.json +9 -4
  71. umap/static/umap/locale/en_US.json +9 -4
  72. umap/static/umap/locale/es.js +15 -10
  73. umap/static/umap/locale/es.json +15 -10
  74. umap/static/umap/locale/et.js +9 -4
  75. umap/static/umap/locale/et.json +9 -4
  76. umap/static/umap/locale/fa_IR.js +9 -4
  77. umap/static/umap/locale/fa_IR.json +9 -4
  78. umap/static/umap/locale/fi.js +9 -4
  79. umap/static/umap/locale/fi.json +9 -4
  80. umap/static/umap/locale/fr.js +10 -5
  81. umap/static/umap/locale/fr.json +10 -5
  82. umap/static/umap/locale/gl.js +9 -4
  83. umap/static/umap/locale/gl.json +9 -4
  84. umap/static/umap/locale/he.js +9 -4
  85. umap/static/umap/locale/he.json +9 -4
  86. umap/static/umap/locale/hr.js +9 -4
  87. umap/static/umap/locale/hr.json +9 -4
  88. umap/static/umap/locale/hu.js +64 -59
  89. umap/static/umap/locale/hu.json +64 -59
  90. umap/static/umap/locale/id.js +9 -4
  91. umap/static/umap/locale/id.json +9 -4
  92. umap/static/umap/locale/is.js +9 -4
  93. umap/static/umap/locale/is.json +9 -4
  94. umap/static/umap/locale/it.js +9 -4
  95. umap/static/umap/locale/it.json +9 -4
  96. umap/static/umap/locale/ja.js +9 -4
  97. umap/static/umap/locale/ja.json +9 -4
  98. umap/static/umap/locale/ko.js +9 -4
  99. umap/static/umap/locale/ko.json +9 -4
  100. umap/static/umap/locale/lt.js +9 -4
  101. umap/static/umap/locale/lt.json +9 -4
  102. umap/static/umap/locale/ms.js +15 -10
  103. umap/static/umap/locale/ms.json +15 -10
  104. umap/static/umap/locale/nl.js +9 -4
  105. umap/static/umap/locale/nl.json +9 -4
  106. umap/static/umap/locale/no.js +9 -4
  107. umap/static/umap/locale/no.json +9 -4
  108. umap/static/umap/locale/pl.js +9 -4
  109. umap/static/umap/locale/pl.json +9 -4
  110. umap/static/umap/locale/pl_PL.json +9 -4
  111. umap/static/umap/locale/pt.js +9 -4
  112. umap/static/umap/locale/pt.json +9 -4
  113. umap/static/umap/locale/pt_BR.js +9 -4
  114. umap/static/umap/locale/pt_BR.json +9 -4
  115. umap/static/umap/locale/pt_PT.js +9 -4
  116. umap/static/umap/locale/pt_PT.json +9 -4
  117. umap/static/umap/locale/ro.js +9 -4
  118. umap/static/umap/locale/ro.json +9 -4
  119. umap/static/umap/locale/ru.js +9 -4
  120. umap/static/umap/locale/ru.json +9 -4
  121. umap/static/umap/locale/si.js +55 -20
  122. umap/static/umap/locale/si.json +55 -20
  123. umap/static/umap/locale/sk_SK.js +9 -4
  124. umap/static/umap/locale/sk_SK.json +9 -4
  125. umap/static/umap/locale/sl.js +9 -4
  126. umap/static/umap/locale/sl.json +9 -4
  127. umap/static/umap/locale/sr.js +9 -4
  128. umap/static/umap/locale/sr.json +9 -4
  129. umap/static/umap/locale/sv.js +9 -4
  130. umap/static/umap/locale/sv.json +9 -4
  131. umap/static/umap/locale/th_TH.js +9 -4
  132. umap/static/umap/locale/th_TH.json +9 -4
  133. umap/static/umap/locale/tr.js +9 -4
  134. umap/static/umap/locale/tr.json +9 -4
  135. umap/static/umap/locale/uk_UA.js +9 -4
  136. umap/static/umap/locale/uk_UA.json +9 -4
  137. umap/static/umap/locale/vi.js +9 -4
  138. umap/static/umap/locale/vi.json +9 -4
  139. umap/static/umap/locale/vi_VN.json +9 -4
  140. umap/static/umap/locale/zh.js +9 -4
  141. umap/static/umap/locale/zh.json +9 -4
  142. umap/static/umap/locale/zh_CN.json +9 -4
  143. umap/static/umap/locale/zh_TW.Big5.json +9 -4
  144. umap/static/umap/locale/zh_TW.js +9 -4
  145. umap/static/umap/locale/zh_TW.json +9 -4
  146. umap/static/umap/map.css +104 -21
  147. umap/static/umap/nav.css +0 -1
  148. umap/static/umap/test/Controls.js +0 -1
  149. umap/static/umap/test/Feature.js +29 -0
  150. umap/static/umap/test/Map.Export.js +2 -127
  151. umap/static/umap/test/Util.js +10 -0
  152. umap/static/umap/test/_pre.js +2 -3
  153. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +13 -2
  154. umap/static/umap/vendors/leaflet/leaflet-src.js +7144 -7144
  155. umap/templates/auth/user_form.html +2 -2
  156. umap/templates/base.html +11 -0
  157. umap/templates/umap/map_table.html +3 -3
  158. umap/templatetags/umap_tags.py +32 -34
  159. umap/tests/base.py +4 -4
  160. umap/tests/conftest.py +3 -6
  161. umap/tests/fixtures/circle.svg +4 -0
  162. umap/tests/fixtures/star.svg +4 -0
  163. umap/tests/integration/test_export_map.py +5 -18
  164. umap/tests/integration/test_picto.py +217 -0
  165. umap/tests/integration/test_slideshow.py +70 -0
  166. umap/tests/settings.py +11 -5
  167. umap/tests/test_datalayer.py +7 -6
  168. umap/tests/test_map.py +6 -5
  169. umap/tests/test_map_views.py +82 -10
  170. umap/tests/test_tilelayer.py +17 -11
  171. umap/tests/test_views.py +36 -9
  172. umap/urls.py +27 -4
  173. umap/utils.py +1 -2
  174. umap/views.py +67 -35
  175. umap/wsgi.py +2 -2
  176. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/METADATA +11 -9
  177. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/RECORD +180 -170
  178. umap/static/favicon.ico +0 -0
  179. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/WHEEL +0 -0
  180. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/entry_points.txt +0 -0
  181. {umap_project-1.10.0.dist-info → umap_project-1.11.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,8 +3,8 @@ import json
3
3
  import pytest
4
4
  from django.contrib.auth import get_user_model
5
5
  from django.core import mail
6
- from django.urls import reverse
7
6
  from django.core.signing import Signer
7
+ from django.urls import reverse
8
8
 
9
9
  from umap.models import DataLayer, Map, Star
10
10
 
@@ -275,9 +275,7 @@ def test_owner_cannot_access_map_with_share_status_blocked(client, map):
275
275
  assert response.status_code == 403
276
276
 
277
277
 
278
- def test_non_editor_cannot_access_map_if_share_status_private(
279
- client, map, user
280
- ): # noqa
278
+ def test_non_editor_cannot_access_map_if_share_status_private(client, map, user): # noqa
281
279
  url = reverse("map", args=(map.slug, map.pk))
282
280
  map.share_status = map.PRIVATE
283
281
  map.save()
@@ -355,9 +353,7 @@ def test_anonymous_update_without_cookie_fails(client, anonymap, post_data): #
355
353
 
356
354
 
357
355
  @pytest.mark.usefixtures("allow_anonymous")
358
- def test_anonymous_update_with_cookie_should_work(
359
- cookieclient, anonymap, post_data
360
- ): # noqa
356
+ def test_anonymous_update_with_cookie_should_work(cookieclient, anonymap, post_data): # noqa
361
357
  url = reverse("map_update", kwargs={"map_id": anonymap.pk})
362
358
  # POST only mendatory fields
363
359
  name = "new map name"
@@ -438,9 +434,7 @@ def test_clone_anonymous_map_should_not_be_possible_if_user_is_not_allowed(
438
434
 
439
435
 
440
436
  @pytest.mark.usefixtures("allow_anonymous")
441
- def test_clone_map_should_be_possible_if_edit_status_is_anonymous(
442
- client, anonymap
443
- ): # noqa
437
+ def test_clone_map_should_be_possible_if_edit_status_is_anonymous(client, anonymap): # noqa
444
438
  assert Map.objects.count() == 1
445
439
  url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
446
440
  anonymap.edit_status = anonymap.ANONYMOUS
@@ -603,3 +597,81 @@ def test_can_send_link_on_anonymous_map_with_cookie(cookieclient, anonymap):
603
597
  assert resp.status_code == 200
604
598
  assert len(mail.outbox) == 1
605
599
  assert mail.outbox[0].subject == "The uMap edit link for your map: test map"
600
+
601
+
602
+ def test_download(client, map, datalayer):
603
+ url = reverse("map_download", args=(map.pk,))
604
+ response = client.get(url)
605
+ assert response.status_code == 200
606
+ # Test response is a json
607
+ j = json.loads(response.content.decode())
608
+ assert j["type"] == "umap"
609
+ assert j["uri"] == f"http://testserver/en/map/test-map_{map.pk}"
610
+ assert j["geometry"] == {
611
+ "coordinates": [13.447265624999998, 48.94415123418794],
612
+ "type": "Point",
613
+ }
614
+ assert j["properties"] == {
615
+ "datalayersControl": True,
616
+ "description": "Which is just the Danube, at the end",
617
+ "displayPopupFooter": False,
618
+ "licence": "",
619
+ "miniMap": False,
620
+ "moreControl": True,
621
+ "name": "test map",
622
+ "scaleControl": True,
623
+ "tilelayer": {
624
+ "attribution": "© OSM Contributors",
625
+ "maxZoom": 18,
626
+ "minZoom": 0,
627
+ "url_template": "http://{s}.osm.fr/{z}/{x}/{y}.png",
628
+ },
629
+ "tilelayersControl": True,
630
+ "zoom": 7,
631
+ "zoomControl": True,
632
+ }
633
+ assert j["layers"] == [
634
+ {
635
+ "_umap_options": {
636
+ "browsable": True,
637
+ "displayOnLoad": True,
638
+ "name": "test datalayer",
639
+ },
640
+ "features": [
641
+ {
642
+ "geometry": {
643
+ "coordinates": [13.68896484375, 48.55297816440071],
644
+ "type": "Point",
645
+ },
646
+ "properties": {
647
+ "_umap_options": {"color": "DarkCyan", "iconClass": "Ball"},
648
+ "description": "Da place anonymous again 755",
649
+ "name": "Here",
650
+ },
651
+ "type": "Feature",
652
+ }
653
+ ],
654
+ "type": "FeatureCollection",
655
+ },
656
+ ]
657
+
658
+
659
+ @pytest.mark.parametrize("share_status", [Map.PRIVATE, Map.BLOCKED])
660
+ def test_download_shared_status_map(client, map, datalayer, share_status):
661
+ map.share_status = share_status
662
+ map.save()
663
+ url = reverse("map_download", args=(map.pk,))
664
+ response = client.get(url)
665
+ assert response.status_code == 403
666
+
667
+
668
+ def test_download_my_map(client, map, datalayer):
669
+ map.share_status = Map.PRIVATE
670
+ map.save()
671
+ client.login(username=map.owner.username, password="123123")
672
+ url = reverse("map_download", args=(map.pk,))
673
+ response = client.get(url)
674
+ assert response.status_code == 200
675
+ # Test response is a json
676
+ j = json.loads(response.content.decode())
677
+ assert j["type"] == "umap"
@@ -6,16 +6,22 @@ pytestmark = pytest.mark.django_db
6
6
 
7
7
 
8
8
  def test_tilelayer_json():
9
- tilelayer = TileLayerFactory(attribution='Attribution', maxZoom=19,
10
- minZoom=0, name='Name', rank=1, tms=True,
11
- url_template='http://{s}.x.fr/{z}/{x}/{y}')
9
+ tilelayer = TileLayerFactory(
10
+ attribution="Attribution",
11
+ maxZoom=19,
12
+ minZoom=0,
13
+ name="Name",
14
+ rank=1,
15
+ tms=True,
16
+ url_template="http://{s}.x.fr/{z}/{x}/{y}",
17
+ )
12
18
  assert tilelayer.json == {
13
- 'attribution': 'Attribution',
14
- 'id': tilelayer.id,
15
- 'maxZoom': 19,
16
- 'minZoom': 0,
17
- 'name': 'Name',
18
- 'rank': 1,
19
- 'tms': True,
20
- 'url_template': 'http://{s}.x.fr/{z}/{x}/{y}'
19
+ "attribution": "Attribution",
20
+ "id": tilelayer.id,
21
+ "maxZoom": 19,
22
+ "minZoom": 0,
23
+ "name": "Name",
24
+ "rank": 1,
25
+ "tms": True,
26
+ "url_template": "http://{s}.x.fr/{z}/{x}/{y}",
21
27
  }
umap/tests/test_views.py CHANGED
@@ -1,17 +1,18 @@
1
1
  import json
2
2
  import socket
3
- from datetime import date, timedelta
3
+ from datetime import date, datetime, timedelta
4
4
 
5
5
  import pytest
6
6
  from django.conf import settings
7
7
  from django.contrib.auth import get_user, get_user_model
8
- from django.urls import reverse
9
8
  from django.test import RequestFactory
9
+ from django.urls import reverse
10
+ from django.utils.timezone import make_aware
10
11
 
11
12
  from umap import VERSION
12
13
  from umap.views import validate_url
13
14
 
14
- from .base import UserFactory, MapFactory
15
+ from .base import MapFactory, UserFactory
15
16
 
16
17
  User = get_user_model()
17
18
 
@@ -186,9 +187,9 @@ def test_stats_empty(client):
186
187
 
187
188
  @pytest.mark.django_db
188
189
  def test_stats_basic(client, map, datalayer, user2):
189
- map.owner.last_login = date.today()
190
+ map.owner.last_login = make_aware(datetime.now())
190
191
  map.owner.save()
191
- user2.last_login = date.today() - timedelta(days=8)
192
+ user2.last_login = make_aware(datetime.now()) - timedelta(days=8)
192
193
  user2.save()
193
194
  response = client.get(reverse("stats"))
194
195
  assert json.loads(response.content.decode()) == {
@@ -280,7 +281,7 @@ def test_user_dashboard_display_user_maps(client, map):
280
281
  assert map.name in body
281
282
  assert f"{map.get_absolute_url()}?edit" in body
282
283
  assert f"{map.get_absolute_url()}?share" in body
283
- assert f"{map.get_absolute_url()}?download" in body
284
+ assert f"/map/{map.pk}/download" in body
284
285
  assert "Everyone (public)" in body
285
286
  assert "Owner only" in body
286
287
 
@@ -289,8 +290,8 @@ def test_user_dashboard_display_user_maps(client, map):
289
290
  def test_user_dashboard_display_user_maps_distinct(client, map):
290
291
  # cf https://github.com/umap-project/umap/issues/1325
291
292
  anonymap = MapFactory(name="Map witout owner should not appear")
292
- user1 = UserFactory(username='user1')
293
- user2 = UserFactory(username='user2')
293
+ user1 = UserFactory(username="user1")
294
+ user2 = UserFactory(username="user2")
294
295
  map.editors.add(user1)
295
296
  map.editors.add(user2)
296
297
  map.save()
@@ -298,7 +299,7 @@ def test_user_dashboard_display_user_maps_distinct(client, map):
298
299
  response = client.get(reverse("user_dashboard"))
299
300
  assert response.status_code == 200
300
301
  body = response.content.decode()
301
- assert body.count(map.name) == 1
302
+ assert body.count(f'<a href="/en/map/test-map_{map.pk}">test map</a>') == 1
302
303
  assert body.count(anonymap.name) == 0
303
304
 
304
305
 
@@ -364,3 +365,29 @@ def test_user_profile_does_not_allow_to_edit_other_fields(client, map):
364
365
  user = User.objects.get(pk=map.owner.pk)
365
366
  assert user.email != new_email
366
367
  assert user.is_superuser is False
368
+
369
+
370
+ def test_favicon_redirection(client):
371
+ response = client.get("/favicon.ico")
372
+ assert response.status_code == 302
373
+ assert response.url == "/static/umap/favicons/favicon.ico"
374
+
375
+
376
+ def test_webmanifest(client):
377
+ response = client.get("/manifest.webmanifest")
378
+ assert response.status_code == 200
379
+ assert response["content-type"] == "application/json"
380
+ assert response.json() == {
381
+ "icons": [
382
+ {
383
+ "sizes": "192x192",
384
+ "src": "/static/umap/favicons/icon-192.png",
385
+ "type": "image/png",
386
+ },
387
+ {
388
+ "sizes": "512x512",
389
+ "src": "/static/umap/favicons/icon-512.png",
390
+ "type": "image/png",
391
+ },
392
+ ]
393
+ }
umap/urls.py CHANGED
@@ -1,20 +1,22 @@
1
1
  from django.conf import settings
2
- from django.urls import include, path, re_path
3
2
  from django.conf.urls.i18n import i18n_patterns
4
3
  from django.conf.urls.static import static
5
4
  from django.contrib import admin
6
5
  from django.contrib.auth import views as auth_views
7
6
  from django.contrib.auth.decorators import login_required
7
+ from django.contrib.staticfiles.storage import staticfiles_storage
8
8
  from django.contrib.staticfiles.urls import staticfiles_urlpatterns
9
+ from django.urls import include, path, re_path
9
10
  from django.views.decorators.cache import cache_control, cache_page, never_cache
10
11
  from django.views.decorators.csrf import ensure_csrf_cookie
12
+ from django.views.generic.base import RedirectView
11
13
 
12
14
  from . import views
13
15
  from .decorators import (
14
- jsonize_view,
15
- login_required_if_not_anonymous_allowed,
16
16
  can_edit_map,
17
17
  can_view_map,
18
+ jsonize_view,
19
+ login_required_if_not_anonymous_allowed,
18
20
  )
19
21
  from .utils import decorated_patterns
20
22
 
@@ -39,6 +41,11 @@ urlpatterns = [
39
41
  ),
40
42
  re_path(r"^i18n/", include("django.conf.urls.i18n")),
41
43
  re_path(r"^agnocomplete/", include("agnocomplete.urls")),
44
+ re_path(
45
+ r"^map/(?P<map_id>\d+)/download/",
46
+ can_view_map(views.MapDownload.as_view()),
47
+ name="map_download",
48
+ ),
42
49
  ]
43
50
 
44
51
  i18n_urls = [
@@ -183,7 +190,23 @@ urlpatterns += i18n_patterns(
183
190
  re_path(r"^user/(?P<identifier>.+)/$", views.user_maps, name="user_maps"),
184
191
  re_path(r"", include(i18n_urls)),
185
192
  )
186
- urlpatterns += (path("stats/", cache_page(60 * 60)(views.stats), name="stats"),)
193
+ urlpatterns += (
194
+ path("stats/", cache_page(60 * 60)(views.stats), name="stats"),
195
+ path(
196
+ "favicon.ico",
197
+ cache_control(max_age=60 * 60 * 24, immutable=True, public=True)(
198
+ RedirectView.as_view(
199
+ url=staticfiles_storage.url("umap/favicons/favicon.ico")
200
+ )
201
+ ),
202
+ ),
203
+ path(
204
+ "manifest.webmanifest",
205
+ cache_control(max_age=60 * 60 * 24, immutable=True, public=True)(
206
+ views.webmanifest
207
+ ),
208
+ ),
209
+ )
187
210
 
188
211
  if settings.DEBUG and settings.MEDIA_ROOT:
189
212
  urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
umap/utils.py CHANGED
@@ -1,8 +1,7 @@
1
1
  import gzip
2
2
  import os
3
3
 
4
- from django.urls import get_resolver
5
- from django.urls import URLPattern, URLResolver
4
+ from django.urls import URLPattern, URLResolver, get_resolver
6
5
 
7
6
 
8
7
  def get_uri_template(urlname, args=None, prefix=""):
umap/views.py CHANGED
@@ -3,18 +3,20 @@ import mimetypes
3
3
  import os
4
4
  import re
5
5
  import socket
6
- from datetime import date, timedelta
6
+ from datetime import datetime, timedelta
7
7
  from http.client import InvalidURL
8
8
  from pathlib import Path
9
- from urllib.error import URLError
10
- from urllib.parse import quote
9
+ from urllib.error import HTTPError, URLError
10
+ from urllib.parse import quote, urlparse
11
+ from urllib.request import Request, build_opener
11
12
 
12
13
  from django.conf import settings
13
14
  from django.contrib import messages
14
- from django.contrib.auth import logout as do_logout
15
15
  from django.contrib.auth import get_user_model
16
+ from django.contrib.auth import logout as do_logout
16
17
  from django.contrib.gis.measure import D
17
18
  from django.contrib.postgres.search import SearchQuery, SearchVector
19
+ from django.contrib.staticfiles.storage import staticfiles_storage
18
20
  from django.core.mail import send_mail
19
21
  from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
20
22
  from django.core.signing import BadSignature, Signer
@@ -32,8 +34,11 @@ from django.shortcuts import get_object_or_404
32
34
  from django.urls import reverse, reverse_lazy
33
35
  from django.utils.encoding import smart_bytes
34
36
  from django.utils.http import http_date
37
+ from django.utils.timezone import make_aware
35
38
  from django.utils.translation import gettext as _
36
39
  from django.utils.translation import to_locale
40
+ from django.views.decorators.cache import cache_control
41
+ from django.views.decorators.http import require_GET
37
42
  from django.views.generic import DetailView, TemplateView, View
38
43
  from django.views.generic.base import RedirectView
39
44
  from django.views.generic.detail import BaseDetailView
@@ -42,13 +47,13 @@ from django.views.generic.list import ListView
42
47
 
43
48
  from . import VERSION
44
49
  from .forms import (
50
+ DEFAULT_CENTER,
45
51
  DEFAULT_LATITUDE,
46
52
  DEFAULT_LONGITUDE,
47
- DEFAULT_CENTER,
48
- DataLayerForm,
49
- DataLayerPermissionsForm,
50
53
  AnonymousDataLayerPermissionsForm,
51
54
  AnonymousMapPermissionsForm,
55
+ DataLayerForm,
56
+ DataLayerPermissionsForm,
52
57
  FlatErrorList,
53
58
  MapSettingsForm,
54
59
  SendLinkForm,
@@ -58,16 +63,6 @@ from .forms import (
58
63
  from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
59
64
  from .utils import get_uri_template, gzip_file, is_ajax
60
65
 
61
- try:
62
- # python3
63
- from urllib.parse import urlparse
64
- from urllib.request import Request, build_opener
65
- from urllib.error import HTTPError
66
- except ImportError:
67
- from urlparse import urlparse
68
- from urllib2 import Request, HTTPError, build_opener
69
-
70
-
71
66
  User = get_user_model()
72
67
 
73
68
 
@@ -137,8 +132,6 @@ class Home(PaginatorMixin, TemplateView, PublicMapsMixin):
137
132
  demo_map = Map.public.get(pk=settings.UMAP_DEMO_PK)
138
133
  except Map.DoesNotExist:
139
134
  pass
140
- else:
141
- maps = maps.exclude(id=demo_map.pk)
142
135
 
143
136
  showcase_map = None
144
137
  if hasattr(settings, "UMAP_SHOWCASE_PK"):
@@ -146,8 +139,6 @@ class Home(PaginatorMixin, TemplateView, PublicMapsMixin):
146
139
  showcase_map = Map.public.get(pk=settings.UMAP_SHOWCASE_PK)
147
140
  except Map.DoesNotExist:
148
141
  pass
149
- else:
150
- maps = maps.exclude(id=showcase_map.pk)
151
142
 
152
143
  maps = self.paginate(maps, settings.UMAP_MAPS_PER_PAGE)
153
144
 
@@ -202,13 +193,10 @@ class UserMaps(PaginatorMixin, DetailView):
202
193
  return settings.UMAP_MAPS_PER_PAGE_OWNER
203
194
  return settings.UMAP_MAPS_PER_PAGE
204
195
 
205
- def get_map_queryset(self):
206
- return Map.objects if self.is_owner() else Map.public
207
-
208
196
  def get_maps(self):
209
- qs = self.get_map_queryset()
210
- qs = qs.filter(Q(owner=self.object) | Q(editors=self.object))
211
- return qs.distinct().order_by("-modified_at")
197
+ qs = Map.public
198
+ qs = qs.filter(owner=self.object).union(qs.filter(editors=self.object))
199
+ return qs.order_by("-modified_at")
212
200
 
213
201
  def get_context_data(self, **kwargs):
214
202
  kwargs.update({"maps": self.paginate(self.get_maps(), self.per_page)})
@@ -222,9 +210,8 @@ class UserStars(UserMaps):
222
210
  template_name = "auth/user_stars.html"
223
211
 
224
212
  def get_maps(self):
225
- qs = self.get_map_queryset()
226
213
  stars = Star.objects.filter(by=self.object).values("map")
227
- qs = qs.filter(pk__in=stars)
214
+ qs = Map.public.filter(pk__in=stars)
228
215
  return qs.order_by("-modified_at")
229
216
 
230
217
 
@@ -408,7 +395,7 @@ def _urls_for_js(urls=None):
408
395
  """
409
396
  if urls is None:
410
397
  # prevent circular import
411
- from .urls import urlpatterns, i18n_urls
398
+ from .urls import i18n_urls, urlpatterns
412
399
 
413
400
  urls = [
414
401
  url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None)
@@ -419,7 +406,7 @@ def _urls_for_js(urls=None):
419
406
 
420
407
 
421
408
  def simple_json_response(**kwargs):
422
- return HttpResponse(json.dumps(kwargs))
409
+ return HttpResponse(json.dumps(kwargs), content_type="application/json")
423
410
 
424
411
 
425
412
  # ############## #
@@ -469,9 +456,7 @@ class MapDetailMixin:
469
456
  else:
470
457
  map_statuses = AnonymousMapPermissionsForm.STATUS
471
458
  datalayer_statuses = AnonymousDataLayerPermissionsForm.STATUS
472
- properties["edit_statuses"] = [
473
- (i, str(label)) for i, label in map_statuses
474
- ]
459
+ properties["edit_statuses"] = [(i, str(label)) for i, label in map_statuses]
475
460
  properties["datalayer_edit_statuses"] = [
476
461
  (i, str(label)) for i, label in datalayer_statuses
477
462
  ]
@@ -606,6 +591,32 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
606
591
  return Star.objects.filter(by=user, map=self.object).exists()
607
592
 
608
593
 
594
+ class MapDownload(DetailView):
595
+ model = Map
596
+ pk_url_kwarg = "map_id"
597
+
598
+ def get_canonical_url(self):
599
+ return reverse("map_download", args=(self.object.pk,))
600
+
601
+ def render_to_response(self, context, *args, **kwargs):
602
+ geojson = self.object.settings
603
+ geojson["type"] = "umap"
604
+ geojson["uri"] = self.request.build_absolute_uri(self.object.get_absolute_url())
605
+ datalayers = []
606
+ for datalayer in self.object.datalayer_set.all():
607
+ with open(datalayer.geojson.path, "rb") as f:
608
+ layer = json.loads(f.read())
609
+ if datalayer.settings:
610
+ layer["_umap_options"] = datalayer.settings
611
+ datalayers.append(layer)
612
+ geojson["layers"] = datalayers
613
+ response = simple_json_response(**geojson)
614
+ response[
615
+ "Content-Disposition"
616
+ ] = f'attachment; filename="umap_backup_{self.object.slug}.umap"'
617
+ return response
618
+
619
+
609
620
  class MapViewGeoJSON(MapView):
610
621
  def get_canonical_url(self):
611
622
  return reverse("map_geojson", args=(self.object.pk,))
@@ -1000,7 +1011,7 @@ class PictogramJSONList(ListView):
1000
1011
 
1001
1012
 
1002
1013
  def stats(request):
1003
- last_week = date.today() - timedelta(days=7)
1014
+ last_week = make_aware(datetime.now()) - timedelta(days=7)
1004
1015
  return simple_json_response(
1005
1016
  **{
1006
1017
  "version": VERSION,
@@ -1016,6 +1027,27 @@ def stats(request):
1016
1027
  )
1017
1028
 
1018
1029
 
1030
+ @require_GET
1031
+ @cache_control(max_age=60 * 60 * 24, immutable=True, public=True) # One day.
1032
+ def webmanifest(request):
1033
+ return simple_json_response(
1034
+ **{
1035
+ "icons": [
1036
+ {
1037
+ "src": staticfiles_storage.url("umap/favicons/icon-192.png"),
1038
+ "type": "image/png",
1039
+ "sizes": "192x192",
1040
+ },
1041
+ {
1042
+ "src": staticfiles_storage.url("umap/favicons/icon-512.png"),
1043
+ "type": "image/png",
1044
+ "sizes": "512x512",
1045
+ },
1046
+ ]
1047
+ }
1048
+ )
1049
+
1050
+
1019
1051
  def logout(request):
1020
1052
  do_logout(request)
1021
1053
  if is_ajax(request):
umap/wsgi.py CHANGED
@@ -15,13 +15,13 @@ framework.
15
15
  """
16
16
  import os
17
17
 
18
- os.environ.setdefault("DJANGO_SETTINGS_MODULE",
19
- "umap.settings")
18
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings")
20
19
 
21
20
  # This application object is used by any WSGI server configured to use this
22
21
  # file. This includes Django's development server, if the WSGI_APPLICATION
23
22
  # setting points here.
24
23
  from django.core.wsgi import get_wsgi_application
24
+
25
25
  application = get_wsgi_application()
26
26
 
27
27
  # Apply WSGI middleware here.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: umap-project
3
- Version: 1.10.0
3
+ Version: 1.11.1
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>
@@ -11,37 +11,39 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
13
  Classifier: Programming Language :: Python :: 3 :: Only
14
- Classifier: Programming Language :: Python :: 3.4
15
- Classifier: Programming Language :: Python :: 3.5
16
- Classifier: Programming Language :: Python :: 3.6
17
- Classifier: Programming Language :: Python :: 3.7
18
14
  Classifier: Programming Language :: Python :: 3.8
19
15
  Classifier: Programming Language :: Python :: 3.9
20
16
  Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
21
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
20
  Requires-Python: >=3.8
23
21
  Requires-Dist: django-agnocomplete==2.2.0
24
22
  Requires-Dist: django-compressor==4.3.1
25
23
  Requires-Dist: django-environ==0.10.0
26
24
  Requires-Dist: django-probes==1.7.0
27
- Requires-Dist: django>=4.1
25
+ Requires-Dist: django<5,>=4.2
28
26
  Requires-Dist: pillow==10.0.1
29
27
  Requires-Dist: psycopg2==2.9.6
30
28
  Requires-Dist: requests==2.31.0
31
29
  Requires-Dist: social-auth-app-django==5.2.0
32
30
  Requires-Dist: social-auth-core==4.4.2
33
31
  Provides-Extra: dev
34
- Requires-Dist: black==23.3.0; extra == 'dev'
35
32
  Requires-Dist: djlint==1.31.0; extra == 'dev'
36
33
  Requires-Dist: hatch==1.7.0; extra == 'dev'
34
+ Requires-Dist: isort==5.12; extra == 'dev'
37
35
  Requires-Dist: mkdocs==1.5.2; extra == 'dev'
36
+ Requires-Dist: pymdown-extensions==10.4; extra == 'dev'
37
+ Requires-Dist: ruff==0.1.6; extra == 'dev'
38
+ Requires-Dist: vermin==1.5.2; extra == 'dev'
38
39
  Provides-Extra: docker
39
40
  Requires-Dist: uwsgi==2.0.21; extra == 'docker'
40
41
  Provides-Extra: test
41
42
  Requires-Dist: factory-boy==3.2.1; extra == 'test'
42
- Requires-Dist: playwright==1.38.0; extra == 'test'
43
+ Requires-Dist: playwright<2,>=1.39; extra == 'test'
43
44
  Requires-Dist: pytest-django==4.5.2; extra == 'test'
44
- Requires-Dist: pytest-playwright==0.4.2; extra == 'test'
45
+ Requires-Dist: pytest-playwright<1,>=0.4.3; extra == 'test'
46
+ Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
45
47
  Requires-Dist: pytest==6.2.5; extra == 'test'
46
48
  Description-Content-Type: text/markdown
47
49