umap-project 2.5.1__py3-none-any.whl → 2.6.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 (193) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +6 -1
  3. umap/context_processors.py +2 -1
  4. umap/decorators.py +13 -2
  5. umap/forms.py +26 -2
  6. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/br/LC_MESSAGES/django.po +252 -146
  8. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  9. umap/locale/ca/LC_MESSAGES/django.po +274 -162
  10. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  11. umap/locale/cs_CZ/LC_MESSAGES/django.po +261 -150
  12. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/de/LC_MESSAGES/django.po +299 -187
  14. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/el/LC_MESSAGES/django.po +215 -159
  16. umap/locale/en/LC_MESSAGES/django.po +211 -155
  17. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/es/LC_MESSAGES/django.po +255 -144
  19. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/eu/LC_MESSAGES/django.po +254 -198
  21. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  22. umap/locale/fa_IR/LC_MESSAGES/django.po +346 -234
  23. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  24. umap/locale/fr/LC_MESSAGES/django.po +216 -160
  25. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  26. umap/locale/hu/LC_MESSAGES/django.po +215 -159
  27. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  28. umap/locale/it/LC_MESSAGES/django.po +252 -146
  29. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  30. umap/locale/ms/LC_MESSAGES/django.po +252 -146
  31. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  32. umap/locale/pl/LC_MESSAGES/django.po +254 -148
  33. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  34. umap/locale/pt/LC_MESSAGES/django.po +215 -159
  35. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  36. umap/locale/sv/LC_MESSAGES/django.po +254 -143
  37. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  38. umap/locale/th_TH/LC_MESSAGES/django.po +125 -70
  39. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  40. umap/locale/zh_TW/LC_MESSAGES/django.po +256 -145
  41. umap/migrations/0022_add_team.py +94 -0
  42. umap/models.py +45 -10
  43. umap/settings/__init__.py +2 -0
  44. umap/settings/base.py +3 -2
  45. umap/static/umap/base.css +32 -41
  46. umap/static/umap/content.css +19 -25
  47. umap/static/umap/css/icon.css +63 -37
  48. umap/static/umap/css/importers.css +1 -1
  49. umap/static/umap/css/slideshow.css +7 -5
  50. umap/static/umap/css/tableeditor.css +4 -3
  51. umap/static/umap/img/16-white.svg +1 -4
  52. umap/static/umap/img/16.svg +2 -6
  53. umap/static/umap/img/24-white.svg +4 -4
  54. umap/static/umap/img/24.svg +6 -6
  55. umap/static/umap/img/source/16-white.svg +2 -5
  56. umap/static/umap/img/source/16.svg +3 -7
  57. umap/static/umap/img/source/24-white.svg +7 -14
  58. umap/static/umap/img/source/24.svg +10 -17
  59. umap/static/umap/js/components/alerts/alert.css +20 -8
  60. umap/static/umap/js/modules/autocomplete.js +3 -3
  61. umap/static/umap/js/modules/browser.js +4 -3
  62. umap/static/umap/js/modules/caption.js +9 -11
  63. umap/static/umap/js/modules/data/features.js +994 -0
  64. umap/static/umap/js/modules/data/layer.js +1210 -0
  65. umap/static/umap/js/modules/formatter.js +12 -3
  66. umap/static/umap/js/modules/global.js +21 -5
  67. umap/static/umap/js/modules/permissions.js +280 -0
  68. umap/static/umap/js/{umap.icon.js → modules/rendering/icon.js} +77 -56
  69. umap/static/umap/js/modules/rendering/layers/base.js +105 -0
  70. umap/static/umap/js/modules/rendering/layers/classified.js +484 -0
  71. umap/static/umap/js/modules/rendering/layers/cluster.js +103 -0
  72. umap/static/umap/js/modules/rendering/layers/heat.js +182 -0
  73. umap/static/umap/js/modules/rendering/popup.js +99 -0
  74. umap/static/umap/js/modules/rendering/template.js +217 -0
  75. umap/static/umap/js/modules/rendering/ui.js +573 -0
  76. umap/static/umap/js/modules/schema.js +24 -0
  77. umap/static/umap/js/modules/share.js +66 -45
  78. umap/static/umap/js/modules/sync/updaters.js +9 -10
  79. umap/static/umap/js/modules/tableeditor.js +7 -7
  80. umap/static/umap/js/modules/ui/dialog.js +8 -4
  81. umap/static/umap/js/modules/utils.js +22 -13
  82. umap/static/umap/js/umap.controls.js +79 -146
  83. umap/static/umap/js/umap.core.js +9 -9
  84. umap/static/umap/js/umap.forms.js +32 -12
  85. umap/static/umap/js/umap.js +65 -63
  86. umap/static/umap/locale/br.js +35 -35
  87. umap/static/umap/locale/br.json +35 -35
  88. umap/static/umap/locale/ca.js +50 -50
  89. umap/static/umap/locale/ca.json +50 -50
  90. umap/static/umap/locale/de.js +136 -136
  91. umap/static/umap/locale/de.json +136 -136
  92. umap/static/umap/locale/el.js +47 -47
  93. umap/static/umap/locale/el.json +47 -47
  94. umap/static/umap/locale/en.js +7 -1
  95. umap/static/umap/locale/en.json +7 -1
  96. umap/static/umap/locale/fa_IR.js +44 -44
  97. umap/static/umap/locale/fa_IR.json +44 -44
  98. umap/static/umap/locale/fr.js +8 -2
  99. umap/static/umap/locale/fr.json +8 -2
  100. umap/static/umap/locale/pt.js +17 -17
  101. umap/static/umap/locale/pt.json +17 -17
  102. umap/static/umap/locale/pt_PT.js +207 -207
  103. umap/static/umap/locale/pt_PT.json +207 -207
  104. umap/static/umap/locale/th_TH.js +25 -25
  105. umap/static/umap/locale/th_TH.json +25 -25
  106. umap/static/umap/map.css +107 -104
  107. umap/static/umap/nav.css +19 -10
  108. umap/static/umap/unittests/utils.js +230 -107
  109. umap/static/umap/vendors/csv2geojson/csv2geojson.js +62 -40
  110. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +1 -1
  111. umap/storage.py +1 -0
  112. umap/templates/404.html +5 -1
  113. umap/templates/500.html +3 -1
  114. umap/templates/auth/user_detail.html +8 -2
  115. umap/templates/auth/user_form.html +19 -10
  116. umap/templates/auth/user_stars.html +8 -2
  117. umap/templates/base.html +1 -0
  118. umap/templates/registration/login.html +18 -3
  119. umap/templates/umap/about.html +1 -0
  120. umap/templates/umap/about_summary.html +22 -7
  121. umap/templates/umap/components/alerts/alert.html +42 -21
  122. umap/templates/umap/content.html +2 -0
  123. umap/templates/umap/content_footer.html +6 -2
  124. umap/templates/umap/css.html +1 -0
  125. umap/templates/umap/dashboard_menu.html +15 -0
  126. umap/templates/umap/home.html +14 -4
  127. umap/templates/umap/js.html +4 -9
  128. umap/templates/umap/login_popup_end.html +10 -4
  129. umap/templates/umap/map_detail.html +8 -2
  130. umap/templates/umap/map_fragment.html +3 -1
  131. umap/templates/umap/map_init.html +2 -1
  132. umap/templates/umap/map_list.html +4 -3
  133. umap/templates/umap/map_table.html +36 -12
  134. umap/templates/umap/messages.html +0 -1
  135. umap/templates/umap/navigation.html +2 -1
  136. umap/templates/umap/password_change.html +5 -1
  137. umap/templates/umap/password_change_done.html +8 -2
  138. umap/templates/umap/search.html +8 -2
  139. umap/templates/umap/search_bar.html +1 -0
  140. umap/templates/umap/team_confirm_delete.html +19 -0
  141. umap/templates/umap/team_detail.html +27 -0
  142. umap/templates/umap/team_form.html +60 -0
  143. umap/templates/umap/user_dashboard.html +7 -9
  144. umap/templates/umap/user_teams.html +51 -0
  145. umap/tests/base.py +8 -1
  146. umap/tests/conftest.py +6 -0
  147. umap/tests/fixtures/test_circles_layer.geojson +219 -0
  148. umap/tests/fixtures/test_upload_georss.xml +20 -0
  149. umap/tests/integration/conftest.py +18 -4
  150. umap/tests/integration/helpers.py +12 -0
  151. umap/tests/integration/test_anonymous_owned_map.py +23 -0
  152. umap/tests/integration/test_basics.py +29 -0
  153. umap/tests/integration/test_caption.py +20 -0
  154. umap/tests/integration/test_circles_layer.py +69 -0
  155. umap/tests/integration/test_draw_polygon.py +110 -13
  156. umap/tests/integration/test_draw_polyline.py +8 -18
  157. umap/tests/integration/test_edit_datalayer.py +1 -1
  158. umap/tests/integration/test_import.py +64 -5
  159. umap/tests/integration/test_owned_map.py +21 -13
  160. umap/tests/integration/test_team.py +47 -0
  161. umap/tests/integration/test_tilelayer.py +19 -2
  162. umap/tests/integration/test_view_marker.py +28 -1
  163. umap/tests/integration/test_websocket_sync.py +5 -5
  164. umap/tests/test_datalayer.py +32 -7
  165. umap/tests/test_datalayer_views.py +1 -1
  166. umap/tests/test_map.py +30 -4
  167. umap/tests/test_map_views.py +2 -2
  168. umap/tests/test_statics.py +40 -0
  169. umap/tests/test_team_views.py +131 -0
  170. umap/tests/test_views.py +15 -1
  171. umap/urls.py +23 -13
  172. umap/views.py +116 -10
  173. {umap_project-2.5.1.dist-info → umap_project-2.6.0b0.dist-info}/METADATA +9 -9
  174. {umap_project-2.5.1.dist-info → umap_project-2.6.0b0.dist-info}/RECORD +177 -170
  175. umap/static/umap/js/umap.datalayer.permissions.js +0 -70
  176. umap/static/umap/js/umap.features.js +0 -1290
  177. umap/static/umap/js/umap.layer.js +0 -1837
  178. umap/static/umap/js/umap.permissions.js +0 -208
  179. umap/static/umap/js/umap.popup.js +0 -341
  180. umap/static/umap/test/TableEditor.js +0 -104
  181. umap/static/umap/vendors/leaflet/leaflet-src.js +0 -14512
  182. umap/static/umap/vendors/leaflet/leaflet-src.js.map +0 -1
  183. umap/static/umap/vendors/leaflet/leaflet.js +0 -6
  184. umap/static/umap/vendors/leaflet/leaflet.js.map +0 -1
  185. umap/static/umap/vendors/markercluster/WhereAreTheJavascriptFiles.txt +0 -5
  186. umap/static/umap/vendors/markercluster/leaflet.markercluster-src.js +0 -2718
  187. umap/static/umap/vendors/markercluster/leaflet.markercluster-src.js.map +0 -1
  188. umap/static/umap/vendors/toolbar/leaflet.toolbar-src.css +0 -117
  189. umap/static/umap/vendors/toolbar/leaflet.toolbar-src.js +0 -365
  190. umap/tests/integration/test_statics.py +0 -47
  191. {umap_project-2.5.1.dist-info → umap_project-2.6.0b0.dist-info}/WHEEL +0 -0
  192. {umap_project-2.5.1.dist-info → umap_project-2.6.0b0.dist-info}/entry_points.txt +0 -0
  193. {umap_project-2.5.1.dist-info → umap_project-2.6.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,131 @@
1
+ import pytest
2
+ from django.urls import reverse
3
+
4
+ from umap.models import Team
5
+
6
+ pytestmark = pytest.mark.django_db
7
+
8
+
9
+ def test_can_see_team_maps(client, map, team):
10
+ map.team = team
11
+ map.save()
12
+ url = reverse("team_maps", args=(team.pk,))
13
+ response = client.get(url)
14
+ assert response.status_code == 200
15
+ assert map.name in response.content.decode()
16
+
17
+
18
+ def test_user_can_see_their_teams(client, team, user):
19
+ user.teams.add(team)
20
+ user.save()
21
+ url = reverse("user_teams")
22
+ client.login(username=user.username, password="123123")
23
+ response = client.get(url)
24
+ assert response.status_code == 200
25
+ assert team.name in response.content.decode()
26
+
27
+
28
+ def test_can_create_a_team(client, user):
29
+ assert not Team.objects.count()
30
+ url = reverse("team_new")
31
+ client.login(username=user.username, password="123123")
32
+ response = client.post(url, {"name": "my new team", "members": [user.pk]})
33
+ assert response.status_code == 302
34
+ assert response["Location"] == "/en/me/teams"
35
+ assert Team.objects.count() == 1
36
+ team = Team.objects.first()
37
+ assert team.name == "my new team"
38
+ assert team in user.teams.all()
39
+
40
+
41
+ def test_can_edit_a_team_name(client, user, team):
42
+ user.teams.add(team)
43
+ user.save()
44
+ assert Team.objects.count() == 1
45
+ url = reverse("team_update", args=(team.pk,))
46
+ client.login(username=user.username, password="123123")
47
+ response = client.post(url, {"name": "my new team", "members": [user.pk]})
48
+ assert response.status_code == 302
49
+ assert response["Location"] == "/en/me/teams"
50
+ assert Team.objects.count() == 1
51
+ modified = Team.objects.first()
52
+ assert modified.name == "my new team"
53
+ assert modified in user.teams.all()
54
+
55
+
56
+ def test_can_add_user_to_team(client, user, user2, team):
57
+ user.teams.add(team)
58
+ user.save()
59
+ assert Team.objects.count() == 1
60
+ url = reverse("team_update", args=(team.pk,))
61
+ client.login(username=user.username, password="123123")
62
+ response = client.post(url, {"name": team.name, "members": [user.pk, user2.pk]})
63
+ assert response.status_code == 302
64
+ assert response["Location"] == "/en/me/teams"
65
+ assert Team.objects.count() == 1
66
+ modified = Team.objects.first()
67
+ assert user in modified.users.all()
68
+ assert user2 in modified.users.all()
69
+
70
+
71
+ def test_can_remove_user_from_team(client, user, user2, team):
72
+ user.teams.add(team)
73
+ user.save()
74
+ user2.teams.add(team)
75
+ user2.save()
76
+ assert Team.objects.count() == 1
77
+ url = reverse("team_update", args=(team.pk,))
78
+ client.login(username=user.username, password="123123")
79
+ response = client.post(url, {"name": team.name, "members": [user.pk]})
80
+ assert response.status_code == 302
81
+ assert response["Location"] == "/en/me/teams"
82
+ assert Team.objects.count() == 1
83
+ modified = Team.objects.first()
84
+ assert user in modified.users.all()
85
+ assert user2 not in modified.users.all()
86
+
87
+
88
+ def test_cannot_edit_a_team_if_not_member(client, user, user2, team):
89
+ user.teams.add(team)
90
+ user.save()
91
+ assert Team.objects.count() == 1
92
+ url = reverse("team_update", args=(team.pk,))
93
+ client.login(username=user2.username, password="456456")
94
+ response = client.post(url, {"name": "my new team", "members": [user.pk]})
95
+ assert response.status_code == 403
96
+
97
+
98
+ def test_can_delete_a_team(client, user, team):
99
+ user.teams.add(team)
100
+ user.save()
101
+ assert Team.objects.count() == 1
102
+ url = reverse("team_delete", args=(team.pk,))
103
+ client.login(username=user.username, password="123123")
104
+ response = client.post(url)
105
+ assert response.status_code == 302
106
+ assert response["Location"] == "/en/me/teams"
107
+ assert Team.objects.count() == 0
108
+
109
+
110
+ def test_cannot_delete_a_team_if_not_member(client, user, user2, team):
111
+ user.teams.add(team)
112
+ user.save()
113
+ assert Team.objects.count() == 1
114
+ url = reverse("team_delete", args=(team.pk,))
115
+ client.login(username=user2.username, password="456456")
116
+ response = client.post(url)
117
+ assert response.status_code == 403
118
+ assert Team.objects.count() == 1
119
+
120
+
121
+ def test_cannot_delete_a_team_if_more_than_one_member(client, user, user2, team):
122
+ user.teams.add(team)
123
+ user.save()
124
+ user2.teams.add(team)
125
+ user2.save()
126
+ assert Team.objects.count() == 1
127
+ url = reverse("team_delete", args=(team.pk,))
128
+ client.login(username=user.username, password="123123")
129
+ response = client.post(url)
130
+ assert response.status_code == 400
131
+ assert Team.objects.count() == 1
umap/tests/test_views.py CHANGED
@@ -288,6 +288,20 @@ def test_user_dashboard_display_user_maps(client, map):
288
288
  assert "Owner only" in body
289
289
 
290
290
 
291
+ @pytest.mark.django_db
292
+ def test_user_dashboard_display_user_team_maps(client, map, team, user):
293
+ user.teams.add(team)
294
+ user.save()
295
+ map.team = team
296
+ map.save()
297
+ client.login(username=user.username, password="123123")
298
+ response = client.get(reverse("user_dashboard"))
299
+ assert response.status_code == 200
300
+ body = response.content.decode()
301
+ assert map.name in body
302
+ assert map.get_absolute_url() in body
303
+
304
+
291
305
  @pytest.mark.django_db
292
306
  def test_user_dashboard_display_user_maps_distinct(client, map):
293
307
  # cf https://github.com/umap-project/umap/issues/1325
@@ -474,7 +488,7 @@ def test_websocket_token_returns_a_valid_token_when_authorized(client, user, map
474
488
 
475
489
  @pytest.mark.django_db
476
490
  def test_websocket_token_is_generated_for_editors(client, user, user2, map):
477
- map.edit_status = Map.EDITORS
491
+ map.edit_status = Map.COLLABORATORS
478
492
  map.editors.add(user2)
479
493
  map.save()
480
494
 
umap/urls.py CHANGED
@@ -16,6 +16,7 @@ from .decorators import (
16
16
  can_edit_map,
17
17
  can_view_map,
18
18
  login_required_if_not_anonymous_allowed,
19
+ team_members_only,
19
20
  )
20
21
  from .utils import decorated_patterns
21
22
 
@@ -24,6 +25,10 @@ admin.autodiscover()
24
25
  urlpatterns = [
25
26
  re_path(r"^admin/", admin.site.urls),
26
27
  re_path("", include("social_django.urls", namespace="social")),
28
+ re_path(
29
+ r"^agnocomplete/",
30
+ include(("agnocomplete.urls", "agnocomplete"), namespace="agnocomplete"),
31
+ ),
27
32
  re_path(r"^m/(?P<pk>\d+)/$", views.MapShortUrl.as_view(), name="map_short_url"),
28
33
  re_path(r"^ajax-proxy/$", cache_page(180)(views.ajax_proxy), name="ajax-proxy"),
29
34
  re_path(
@@ -39,7 +44,6 @@ urlpatterns = [
39
44
  name="password_change_done",
40
45
  ),
41
46
  re_path(r"^i18n/", include("django.conf.urls.i18n")),
42
- re_path(r"^agnocomplete/", include("agnocomplete.urls")),
43
47
  re_path(r"^map/oembed/", views.MapOEmbed.as_view(), name="map_oembed"),
44
48
  re_path(
45
49
  r"^map/(?P<map_id>\d+)/download/",
@@ -96,12 +100,12 @@ i18n_urls += decorated_patterns(
96
100
  )
97
101
  i18n_urls += decorated_patterns(
98
102
  [ensure_csrf_cookie],
99
- re_path(r"^map/$", views.MapPreview.as_view(), name="map_preview"),
100
- re_path(r"^map/new/$", views.MapNew.as_view(), name="map_new"),
103
+ path("map/", views.MapPreview.as_view(), name="map_preview"),
104
+ path("map/new/", views.MapNew.as_view(), name="map_new"),
101
105
  )
102
106
  i18n_urls += decorated_patterns(
103
107
  [login_required_if_not_anonymous_allowed, never_cache],
104
- re_path(r"^map/create/$", views.MapCreate.as_view(), name="map_create"),
108
+ path("map/create/", views.MapCreate.as_view(), name="map_create"),
105
109
  )
106
110
  i18n_urls += decorated_patterns(
107
111
  [login_required],
@@ -110,9 +114,16 @@ i18n_urls += decorated_patterns(
110
114
  views.ToggleMapStarStatus.as_view(),
111
115
  name="map_star",
112
116
  ),
113
- re_path(r"^me$", views.user_dashboard, name="user_dashboard"),
114
- re_path(r"^me/profile$", views.user_profile, name="user_profile"),
115
- re_path(r"^me/download$", views.user_download, name="user_download"),
117
+ path("me", views.user_dashboard, name="user_dashboard"),
118
+ path("me/profile", views.user_profile, name="user_profile"),
119
+ path("me/download", views.user_download, name="user_download"),
120
+ path("me/teams", views.UserTeams.as_view(), name="user_teams"),
121
+ path("team/create/", views.TeamNew.as_view(), name="team_new"),
122
+ )
123
+ i18n_urls += decorated_patterns(
124
+ [login_required, team_members_only],
125
+ path("team/<int:pk>/edit/", views.TeamUpdate.as_view(), name="team_update"),
126
+ path("team/<int:pk>/delete/", views.TeamDelete.as_view(), name="team_delete"),
116
127
  )
117
128
  map_urls = [
118
129
  re_path(
@@ -179,14 +190,13 @@ datalayer_urls = [
179
190
  i18n_urls += decorated_patterns([can_edit_map, never_cache], *map_urls)
180
191
  i18n_urls += decorated_patterns([never_cache], *datalayer_urls)
181
192
  urlpatterns += i18n_patterns(
182
- re_path(r"^$", views.home, name="home"),
183
- re_path(
184
- r"^showcase/$", cache_page(24 * 60 * 60)(views.showcase), name="maps_showcase"
185
- ),
186
- re_path(r"^search/$", views.search, name="search"),
187
- re_path(r"^about/$", views.about, name="about"),
193
+ path("", views.home, name="home"),
194
+ path("showcase/", cache_page(24 * 60 * 60)(views.showcase), name="maps_showcase"),
195
+ path("search/", views.search, name="search"),
196
+ path("about/", views.about, name="about"),
188
197
  re_path(r"^user/(?P<identifier>.+)/stars/$", views.user_stars, name="user_stars"),
189
198
  re_path(r"^user/(?P<identifier>.+)/$", views.user_maps, name="user_maps"),
199
+ path("team/<int:pk>/", views.TeamMaps.as_view(), name="team_maps"),
190
200
  re_path(r"", include(i18n_urls)),
191
201
  )
192
202
  urlpatterns += (
umap/views.py CHANGED
@@ -62,10 +62,11 @@ from .forms import (
62
62
  FlatErrorList,
63
63
  MapSettingsForm,
64
64
  SendLinkForm,
65
+ TeamForm,
65
66
  UpdateMapPermissionsForm,
66
67
  UserProfileForm,
67
68
  )
68
- from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
69
+ from .models import DataLayer, Licence, Map, Pictogram, Star, Team, TileLayer
69
70
  from .utils import (
70
71
  ConflictError,
71
72
  _urls_for_js,
@@ -188,6 +189,70 @@ class About(Home):
188
189
  about = About.as_view()
189
190
 
190
191
 
192
+ class TeamNew(CreateView):
193
+ model = Team
194
+ fields = ["name", "description"]
195
+ success_url = reverse_lazy("user_teams")
196
+
197
+ def form_valid(self, form):
198
+ response = super().form_valid(form)
199
+ self.request.user.teams.add(self.object)
200
+ self.request.user.save()
201
+ return response
202
+
203
+
204
+ class TeamUpdate(UpdateView):
205
+ model = Team
206
+ form_class = TeamForm
207
+ success_url = reverse_lazy("user_teams")
208
+
209
+ def get_initial(self):
210
+ initial = super().get_initial()
211
+ initial["members"] = self.object.users.all()
212
+ return initial
213
+
214
+ def form_valid(self, form):
215
+ actual = self.object.users.all()
216
+ wanted = form.cleaned_data["members"]
217
+ for user in wanted:
218
+ if user not in actual:
219
+ user.teams.add(self.object)
220
+ user.save()
221
+ for user in actual:
222
+ if user not in wanted:
223
+ user.teams.remove(self.object)
224
+ user.save()
225
+ return super().form_valid(form)
226
+
227
+
228
+ class TeamDelete(DeleteView):
229
+ model = Team
230
+ success_url = reverse_lazy("user_teams")
231
+
232
+ def form_valid(self, form):
233
+ if self.object.users.count() > 1:
234
+ return HttpResponseBadRequest(
235
+ _("Cannot delete a team with more than one member")
236
+ )
237
+ messages.info(
238
+ self.request,
239
+ _("Team “%(name)s” has been deleted") % {"name": self.object.name},
240
+ )
241
+ return super().form_valid(form)
242
+
243
+
244
+ class UserTeams(DetailView):
245
+ model = User
246
+ template_name = "umap/user_teams.html"
247
+
248
+ def get_object(self):
249
+ return self.get_queryset().get(pk=self.request.user.pk)
250
+
251
+ def get_context_data(self, **kwargs):
252
+ kwargs.update({"teams": self.object.teams.all()})
253
+ return super().get_context_data(**kwargs)
254
+
255
+
191
256
  class UserProfile(UpdateView):
192
257
  model = User
193
258
  form_class = UserProfileForm
@@ -247,6 +312,21 @@ class UserStars(UserMaps):
247
312
  user_stars = UserStars.as_view()
248
313
 
249
314
 
315
+ class TeamMaps(PaginatorMixin, DetailView):
316
+ model = Team
317
+ list_template_name = "umap/map_list.html"
318
+ context_object_name = "current_team"
319
+
320
+ def get_maps(self):
321
+ return Map.public.filter(team=self.object).order_by("-modified_at")
322
+
323
+ def get_context_data(self, **kwargs):
324
+ kwargs.update(
325
+ {"maps": self.paginate(self.get_maps(), settings.UMAP_MAPS_PER_PAGE)}
326
+ )
327
+ return super().get_context_data(**kwargs)
328
+
329
+
250
330
  class SearchMixin:
251
331
  def get_search_queryset(self, **kwargs):
252
332
  q = self.request.GET.get("q")
@@ -293,7 +373,12 @@ class UserDashboard(PaginatorMixin, DetailView, SearchMixin):
293
373
 
294
374
  def get_maps(self):
295
375
  qs = self.get_search_queryset() or Map.objects.all()
296
- qs = qs.filter(owner=self.object).union(qs.filter(editors=self.object))
376
+ teams = self.object.teams.all()
377
+ qs = (
378
+ qs.filter(owner=self.object)
379
+ .union(qs.filter(editors=self.object))
380
+ .union(qs.filter(team__in=teams))
381
+ )
297
382
  return qs.order_by("-modified_at")
298
383
 
299
384
  def get_context_data(self, **kwargs):
@@ -456,6 +541,23 @@ def simple_json_response(**kwargs):
456
541
  # ############## #
457
542
 
458
543
 
544
+ class SessionMixin:
545
+ def get_user_data(self):
546
+ data = {}
547
+ user = self.request.user
548
+ if hasattr(self, "object"):
549
+ data["is_owner"] = self.object.is_owner(user, self.request)
550
+ if user.is_anonymous:
551
+ return data
552
+ return {
553
+ "id": user.pk,
554
+ "name": str(self.request.user),
555
+ "url": reverse("user_dashboard"),
556
+ "teams": [team.get_metadata() for team in user.teams.all()],
557
+ **data,
558
+ }
559
+
560
+
459
561
  class FormLessEditMixin:
460
562
  http_method_names = [
461
563
  "post",
@@ -470,7 +572,7 @@ class FormLessEditMixin:
470
572
  return self.get_form_class()(**kwargs)
471
573
 
472
574
 
473
- class MapDetailMixin:
575
+ class MapDetailMixin(SessionMixin):
474
576
  model = Map
475
577
  pk_url_kwarg = "map_id"
476
578
 
@@ -522,12 +624,7 @@ class MapDetailMixin:
522
624
  if self.get_short_url():
523
625
  properties["shortUrl"] = self.get_short_url()
524
626
 
525
- if not user.is_anonymous:
526
- properties["user"] = {
527
- "id": user.pk,
528
- "name": str(user),
529
- "url": reverse("user_dashboard"),
530
- }
627
+ properties["user"] = self.get_user_data()
531
628
  return properties
532
629
 
533
630
  def get_context_data(self, **kwargs):
@@ -595,6 +692,8 @@ class PermissionsMixin:
595
692
  {"id": editor.pk, "name": str(editor)}
596
693
  for editor in self.object.editors.all()
597
694
  ]
695
+ if self.object.team:
696
+ permissions["team"] = self.object.team.get_metadata()
598
697
  if not self.object.owner and self.object.is_anonymous_owner(self.request):
599
698
  permissions["anonymous_edit_url"] = self.object.get_anonymous_edit_url()
600
699
  return permissions
@@ -659,6 +758,12 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
659
758
  map_settings["properties"] = {}
660
759
  map_settings["properties"]["name"] = self.object.name
661
760
  map_settings["properties"]["permissions"] = self.get_permissions()
761
+ author = self.object.get_author()
762
+ if author:
763
+ map_settings["properties"]["author"] = {
764
+ "name": str(author),
765
+ "url": author.get_url(),
766
+ }
662
767
  return map_settings
663
768
 
664
769
  def is_starred(self):
@@ -755,7 +860,7 @@ class MapPreview(MapDetailMixin, TemplateView):
755
860
  return properties
756
861
 
757
862
 
758
- class MapCreate(FormLessEditMixin, PermissionsMixin, CreateView):
863
+ class MapCreate(FormLessEditMixin, PermissionsMixin, SessionMixin, CreateView):
759
864
  model = Map
760
865
  form_class = MapSettingsForm
761
866
 
@@ -772,6 +877,7 @@ class MapCreate(FormLessEditMixin, PermissionsMixin, CreateView):
772
877
  id=self.object.pk,
773
878
  url=self.object.get_absolute_url(),
774
879
  permissions=permissions,
880
+ user=self.get_user_data(),
775
881
  )
776
882
  if not self.request.user.is_authenticated:
777
883
  key, value = self.object.signed_cookie_elements
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: umap-project
3
- Version: 2.5.1
3
+ Version: 2.6.0b0
4
4
  Summary: Create maps with OpenStreetMap layers in a minute and embed them in your site.
5
5
  Author-email: Yohan Boniface <yb@enix.org>
6
6
  Maintainer-email: David Larlet <david@larlet.fr>
@@ -19,35 +19,35 @@ Requires-Python: >=3.10
19
19
  Requires-Dist: django-agnocomplete==2.2.0
20
20
  Requires-Dist: django-environ==0.11.2
21
21
  Requires-Dist: django-probes==1.7.0
22
- Requires-Dist: django==5.0.7
22
+ Requires-Dist: django==5.1
23
23
  Requires-Dist: pillow==10.4.0
24
24
  Requires-Dist: psycopg==3.2.1
25
25
  Requires-Dist: pydantic==2.8.2
26
26
  Requires-Dist: rcssmin==1.1.2
27
27
  Requires-Dist: requests==2.32.3
28
28
  Requires-Dist: rjsmin==1.2.2
29
- Requires-Dist: social-auth-app-django==5.4.1
29
+ Requires-Dist: social-auth-app-django==5.4.2
30
30
  Requires-Dist: social-auth-core==4.5.4
31
- Requires-Dist: websockets==12.0
31
+ Requires-Dist: websockets==13.0
32
32
  Provides-Extra: dev
33
33
  Requires-Dist: djlint==1.34.1; extra == 'dev'
34
34
  Requires-Dist: hatch==1.12.0; extra == 'dev'
35
35
  Requires-Dist: isort==5.13.2; extra == 'dev'
36
- Requires-Dist: mkdocs-material==9.5.28; extra == 'dev'
36
+ Requires-Dist: mkdocs-material==9.5.33; extra == 'dev'
37
37
  Requires-Dist: mkdocs-static-i18n==1.2.3; extra == 'dev'
38
38
  Requires-Dist: mkdocs==1.6.0; extra == 'dev'
39
- Requires-Dist: pymdown-extensions==10.8.1; extra == 'dev'
40
- Requires-Dist: ruff==0.5.1; extra == 'dev'
39
+ Requires-Dist: pymdown-extensions==10.9; extra == 'dev'
40
+ Requires-Dist: ruff==0.6.2; extra == 'dev'
41
41
  Requires-Dist: vermin==1.6.0; extra == 'dev'
42
42
  Provides-Extra: docker
43
43
  Requires-Dist: uwsgi==2.0.26; extra == 'docker'
44
44
  Provides-Extra: test
45
- Requires-Dist: factory-boy==3.2.1; extra == 'test'
45
+ Requires-Dist: factory-boy==3.3.1; extra == 'test'
46
46
  Requires-Dist: playwright>=1.39; extra == 'test'
47
47
  Requires-Dist: pytest-django==4.8.0; extra == 'test'
48
48
  Requires-Dist: pytest-playwright==0.5.1; extra == 'test'
49
49
  Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
50
- Requires-Dist: pytest==8.2.2; extra == 'test'
50
+ Requires-Dist: pytest==8.3.2; extra == 'test'
51
51
  Description-Content-Type: text/markdown
52
52
 
53
53
  # uMap project