umap-project 2.0.0a3__py3-none-any.whl → 2.0.3__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 (60) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.po +33 -28
  3. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/fr/LC_MESSAGES/django.po +33 -28
  5. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/hu/LC_MESSAGES/django.po +48 -43
  7. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/it/LC_MESSAGES/django.po +20 -20
  9. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/ms/LC_MESSAGES/django.po +20 -20
  11. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/zh_TW/LC_MESSAGES/django.po +12 -12
  13. umap/management/commands/import_pictograms.py +1 -1
  14. umap/settings/base.py +2 -2
  15. umap/static/umap/base.css +6 -6
  16. umap/static/umap/content.css +14 -14
  17. umap/static/umap/js/umap.forms.js +2 -2
  18. umap/static/umap/js/umap.js +18 -27
  19. umap/static/umap/js/umap.popup.js +1 -1
  20. umap/static/umap/js/umap.share.js +2 -0
  21. umap/static/umap/locale/hu.js +8 -1
  22. umap/static/umap/locale/hu.json +8 -1
  23. umap/static/umap/locale/it.js +37 -30
  24. umap/static/umap/locale/it.json +37 -30
  25. umap/static/umap/locale/ms.js +28 -21
  26. umap/static/umap/locale/ms.json +28 -21
  27. umap/static/umap/locale/zh_TW.js +8 -1
  28. umap/static/umap/locale/zh_TW.json +8 -1
  29. umap/static/umap/map.css +9 -9
  30. umap/static/umap/nav.css +1 -1
  31. umap/static/umap/test/index.html +16 -13
  32. umap/static/umap/vars.css +13 -0
  33. umap/storage.py +3 -2
  34. umap/templates/registration/login.html +40 -40
  35. umap/templates/umap/css.html +1 -1
  36. umap/templates/umap/js.html +26 -15
  37. umap/templates/umap/map_detail.html +5 -7
  38. umap/templates/umap/map_fragment.html +1 -1
  39. umap/templates/umap/map_init.html +10 -3
  40. umap/templates/umap/map_table.html +18 -17
  41. umap/templates/umap/user_dashboard.html +8 -9
  42. umap/tests/conftest.py +0 -2
  43. umap/tests/integration/test_anonymous_owned_map.py +50 -1
  44. umap/tests/integration/test_collaborative_editing.py +57 -1
  45. umap/tests/integration/test_dashboard.py +13 -0
  46. umap/tests/integration/test_map_preview.py +8 -0
  47. umap/tests/integration/test_querystring.py +14 -0
  48. umap/tests/integration/test_share.py +19 -1
  49. umap/tests/integration/test_star.py +27 -0
  50. umap/tests/integration/test_statics.py +6 -3
  51. umap/tests/settings.py +1 -1
  52. umap/tests/test_map_views.py +3 -3
  53. umap/urls.py +1 -1
  54. umap/views.py +16 -10
  55. {umap_project-2.0.0a3.dist-info → umap_project-2.0.3.dist-info}/METADATA +4 -4
  56. {umap_project-2.0.0a3.dist-info → umap_project-2.0.3.dist-info}/RECORD +59 -58
  57. umap/templates/umap/map_messages.html +0 -12
  58. {umap_project-2.0.0a3.dist-info → umap_project-2.0.3.dist-info}/WHEEL +0 -0
  59. {umap_project-2.0.0a3.dist-info → umap_project-2.0.3.dist-info}/entry_points.txt +0 -0
  60. {umap_project-2.0.0a3.dist-info → umap_project-2.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
1
  {% load static %}
2
-
3
2
  <link rel="stylesheet"
4
3
  href="{% static 'umap/vendors/leaflet/leaflet.css' %}" />
5
4
  <link rel="stylesheet"
@@ -22,6 +21,7 @@
22
21
  href="{% static 'umap/vendors/locatecontrol/L.Control.Locate.min.css' %}" />
23
22
  <link rel="stylesheet"
24
23
  href="{% static 'umap/vendors/iconlayers/iconLayers.css' %}" />
24
+ <link rel="stylesheet" href="{% static 'umap/vars.css' %}" />
25
25
  <link rel="stylesheet" href="{% static 'umap/font.css' %}" />
26
26
  <link rel="stylesheet" href="{% static 'umap/base.css' %}" />
27
27
  <link rel="stylesheet" href="{% static 'umap/content.css' %}" />
@@ -1,37 +1,48 @@
1
1
  {% load static %}
2
-
3
- <script type="module" src="{% static 'umap/vendors/leaflet/leaflet-src.esm.js' %}" defer></script>
2
+ <script type="module"
3
+ src="{% static 'umap/vendors/leaflet/leaflet-src.esm.js' %}"
4
+ defer></script>
4
5
  <script type="module" src="{% static 'umap/js/modules/global.js' %}" defer></script>
5
6
  <script src="{% static 'umap/vendors/editable/Path.Drag.js' %}" defer></script>
6
7
  <script src="{% static 'umap/vendors/editable/Leaflet.Editable.js' %}" defer></script>
7
8
  <script src="{% static 'umap/vendors/hash/leaflet-hash.js' %}" defer></script>
8
9
  <script src="{% static 'umap/vendors/i18n/Leaflet.i18n.js' %}" defer></script>
9
- <script src="{% static 'umap/vendors/editinosm/Leaflet.EditInOSM.js' %}" defer></script>
10
- <script src="{% static 'umap/vendors/minimap/Control.MiniMap.min.js' %}" defer></script>
10
+ <script src="{% static 'umap/vendors/editinosm/Leaflet.EditInOSM.js' %}"
11
+ defer></script>
12
+ <script src="{% static 'umap/vendors/minimap/Control.MiniMap.min.js' %}"
13
+ defer></script>
11
14
  <script src="{% static 'umap/vendors/csv2geojson/csv2geojson.js' %}" defer></script>
12
15
  <script src="{% static 'umap/vendors/togeojson/togeojson.umd.js' %}" defer></script>
13
16
  <script src="{% static 'umap/vendors/osmtogeojson/osmtogeojson.js' %}" defer></script>
14
17
  <script src="{% static 'umap/vendors/loading/Control.Loading.js' %}" defer></script>
15
- <script src="{% static 'umap/vendors/markercluster/leaflet.markercluster.js' %}" defer></script>
16
- <script src="{% static 'umap/vendors/contextmenu/leaflet.contextmenu.min.js' %}" defer></script>
18
+ <script src="{% static 'umap/vendors/markercluster/leaflet.markercluster.js' %}"
19
+ defer></script>
20
+ <script src="{% static 'umap/vendors/contextmenu/leaflet.contextmenu.min.js' %}"
21
+ defer></script>
17
22
  <script src="{% static 'umap/vendors/photon/leaflet.photon.js' %}" defer></script>
18
- <script src="{% static 'umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js' %}" defer></script>
23
+ <script src="{% static 'umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js' %}"
24
+ defer></script>
19
25
  <script src="{% static 'umap/vendors/heat/leaflet-heat.js' %}" defer></script>
20
- <script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}" defer></script>
26
+ <script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}"
27
+ defer></script>
21
28
  <script src="{% static 'umap/vendors/toolbar/leaflet.toolbar.js' %}" defer></script>
22
- <script src="{% static 'umap/vendors/formbuilder/Leaflet.FormBuilder.js' %}" defer></script>
23
- <script src="{% static 'umap/vendors/measurable/Leaflet.Measurable.js' %}" defer></script>
29
+ <script src="{% static 'umap/vendors/formbuilder/Leaflet.FormBuilder.js' %}"
30
+ defer></script>
31
+ <script src="{% static 'umap/vendors/measurable/Leaflet.Measurable.js' %}"
32
+ defer></script>
24
33
  <script src="{% static 'umap/vendors/togpx/togpx.js' %}" defer></script>
25
34
  <script src="{% static 'umap/vendors/iconlayers/iconLayers.js' %}" defer></script>
26
35
  <script src="{% static 'umap/vendors/tokml/tokml.js' %}" defer></script>
27
- <script src="{% static 'umap/vendors/locatecontrol/L.Control.Locate.min.js' %}" defer></script>
36
+ <script src="{% static 'umap/vendors/locatecontrol/L.Control.Locate.min.js' %}"
37
+ defer></script>
28
38
  <script src="{% static 'umap/vendors/dompurify/purify.min.js' %}" defer></script>
29
39
  <script src="{% static 'umap/vendors/colorbrewer/colorbrewer.js' %}" defer></script>
30
- <script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}" defer></script>
40
+ <script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}"
41
+ defer></script>
31
42
  {% if locale %}
32
- {% with "umap/locale/"|add:locale|add:".js" as path %}
33
- <script src="{% static path %}" defer></script>
34
- {% endwith %}
43
+ {% with "umap/locale/"|add:locale|add:".js" as path %}
44
+ <script src="{% static path %}" defer></script>
45
+ {% endwith %}
35
46
  {% endif %}
36
47
  <script src="{% static 'umap/js/umap.core.js' %}" defer></script>
37
48
  <script src="{% static 'umap/js/umap.autocomplete.js' %}" defer></script>
@@ -8,21 +8,19 @@
8
8
  {% endblock body_class %}
9
9
  {% block extra_head %}
10
10
  {% if preconnect_domains %}
11
- {% for domain in preconnect_domains %}
12
- <link rel="preconnect" href="{{ domain }}" />
13
- {% endfor %}
11
+ {% for domain in preconnect_domains %}<link rel="preconnect" href="{{ domain }}" />{% endfor %}
14
12
  {% endif %}
15
13
  {% umap_css %}
16
14
  {{ block.super }}
17
15
  {% umap_js locale=locale %}
18
16
  {% if object.share_status != object.PUBLIC %}<meta name="robots" content="noindex">{% endif %}
19
- <link rel="alternate" type="application/json+oembed"
20
- href="{{ oembed_absolute_uri }}?url={{ quoted_absolute_uri }}&format=json"
21
- title="{{ map.name }} oEmbed URL" />
17
+ <link rel="alternate"
18
+ type="application/json+oembed"
19
+ href="{{ oembed_absolute_uri }}?url={{ quoted_absolute_uri }}&format=json"
20
+ title="{{ map.name }} oEmbed URL" />
22
21
  {% endblock extra_head %}
23
22
  {% block content %}
24
23
  {% block map_init %}
25
24
  {% include "umap/map_init.html" %}
26
25
  {% endblock map_init %}
27
- {% include "umap/map_messages.html" %}
28
26
  {% endblock content %}
@@ -1,4 +1,4 @@
1
1
  {% load umap_tags %}
2
2
  <umap-fragment data-settings='{{ map_settings|escape }}'>
3
- <div id="{{ unique_id }}" class="map_fragment"></div>
3
+ <div id="{{ unique_id }}" class="map_fragment"></div>
4
4
  </umap-fragment>
@@ -2,9 +2,16 @@
2
2
  <div id="map"></div>
3
3
  <!-- djlint:off -->
4
4
  <script defer type="text/javascript">
5
- let MAP
6
5
  window.addEventListener('DOMContentLoaded', (event) => {
7
- MAP = new U.Map("map", {{ map_settings|notag|safe }});
8
- });
6
+ U.MAP = new U.Map("map", {{ map_settings|notag|safe }})
7
+ {% for m in messages %}
8
+ {# We have just one, but we need to loop, as for messages API #}
9
+ U.MAP.ui.alert({
10
+ content: "{{ m }}",
11
+ level: "{{ m.tags }}",
12
+ duration: 100000
13
+ })
14
+ {% endfor %}
15
+ })
9
16
  </script>
10
17
  <!-- djlint:on -->
@@ -21,8 +21,9 @@
21
21
  </th>
22
22
  <td>
23
23
  {{ map_inst.preview_settings|json_script:unique_id }}
24
- <button class="map-icon map-opener" data-map-id="{{ unique_id }}"
25
- title="{% translate "Open preview" %}">
24
+ <button class="map-icon map-opener"
25
+ data-map-id="{{ unique_id }}"
26
+ title="{% translate "Open preview" %}">
26
27
  <span class="icon-dashboard icon-view"></span>
27
28
  <span class="sr-only">{% translate "Open preview" %}</span>
28
29
  </button>
@@ -42,35 +43,37 @@
42
43
  <a href="{{ map_inst.owner.get_url }}">{{ map_inst.owner }}</a>
43
44
  </td>
44
45
  <td>
45
- <a href="{{ map_inst.get_absolute_url }}?share" class="icon-link"
46
- title="{% translate "Share" %}">
46
+ <a href="{{ map_inst.get_absolute_url }}?share"
47
+ class="icon-link"
48
+ title="{% translate "Share" %}">
47
49
  <span class="icon-dashboard icon-share"></span>
48
50
  <span class="sr-only">{% translate "Share" %}</span>
49
51
  </a>
50
- <a href="{{ map_inst.get_absolute_url }}?edit" class="icon-link"
51
- title="{% translate "Edit" %}">
52
+ <a href="{{ map_inst.get_absolute_url }}?edit"
53
+ class="icon-link"
54
+ title="{% translate "Edit" %}">
52
55
  <span class="icon-dashboard icon-edit"></span>
53
56
  <span class="sr-only">{% translate "Edit" %}</span>
54
57
  </a>
55
- <a href="{% url 'map_download' map_inst.pk %}" class="icon-link"
56
- title="{% translate "Download" %}">
58
+ <a href="{% url 'map_download' map_inst.pk %}"
59
+ class="icon-link"
60
+ title="{% translate "Download" %}">
57
61
  <span class="icon-dashboard icon-download"></span>
58
62
  <span class="sr-only">{% translate "Download" %}</span>
59
63
  </a>
60
64
  <form action="{% url 'map_clone' map_inst.pk %}" method="post">
61
65
  {% csrf_token %}
62
- <button class="map-icon" type="submit"
63
- title="{% translate "Clone" %}">
66
+ <button class="map-icon" type="submit" title="{% translate "Clone" %}">
64
67
  <span class="icon-dashboard icon-duplicate"></span>
65
68
  <span class="sr-only">{% translate "Clone" %}</span>
66
69
  </button>
67
70
  </form>
68
71
  <form action="{% url 'map_delete' map_inst.pk %}"
69
- method="post" class="map-delete">
72
+ method="post"
73
+ class="map-delete">
70
74
  {% csrf_token %}
71
75
  <input type="hidden" name="next" value="{% url 'user_dashboard' %}">
72
- <button class="map-icon" type="submit"
73
- title="{% translate "Delete" %}">
76
+ <button class="map-icon" type="submit" title="{% translate "Delete" %}">
74
77
  <span class="icon-dashboard icon-delete"></span>
75
78
  <span class="sr-only">{% translate "Delete" %}</span>
76
79
  </button>
@@ -93,13 +96,11 @@
93
96
  <span></span>
94
97
  {# djlint:on #}
95
98
  {% endif %}
96
-
97
99
  <span class="current">
98
100
  {% blocktranslate with maps_number=maps.number num_pages=maps.paginator.num_pages trimmed %}
99
101
  Page {{ maps_number }} of {{ num_pages }}
100
102
  {% endblocktranslate %}
101
103
  </span>
102
-
103
104
  {% if maps.has_next %}
104
105
  <a href="?p={{ maps.next_page_number }}{% if q %}&q={{ q }}{% endif %}">{% translate "next" %} ›</a>
105
106
  <a href="?p={{ maps.paginator.num_pages }}{% if q %}&q={{ q }}{% endif %}">{% translate "last" %} »</a>
@@ -111,12 +112,12 @@
111
112
  {% endif %}
112
113
  <span>
113
114
  {% blocktranslate with per_page=maps.paginator.per_page trimmed %}
114
- Lines per page: {{ per_page }}
115
+ Lines per page: {{ per_page }}
115
116
  {% endblocktranslate %}
116
117
  </span>
117
118
  <span>
118
119
  {% blocktranslate with count=maps.paginator.count trimmed %}
119
- {{ count }} maps
120
+ {{ count }} maps
120
121
  {% endblocktranslate %}
121
122
  </span>
122
123
  {% endif %}
@@ -7,8 +7,7 @@
7
7
  {% trans "Search my maps" as placeholder %}
8
8
  <div class="row">
9
9
  <h2 class="section tabs">
10
- <a class="selected" href="{% url 'user_dashboard' %}"
11
- >{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}
10
+ <a class="selected" href="{% url 'user_dashboard' %}">{% blocktranslate with count=maps.paginator.count %}My Maps ({{ count }}){% endblocktranslate %}
12
11
  </a>
13
12
  <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
14
13
  </h2>
@@ -19,17 +18,18 @@
19
18
  <form action="{{ request.get_full_path }}" method="get">
20
19
  <span>
21
20
  <label class="sr-only" for="q">{% translate "Map’s title" %}</label>
22
- <input id="q" name="q" type="search"
21
+ <input id="q"
22
+ name="q"
23
+ type="search"
23
24
  placeholder="{% translate "Map’s title" %}"
24
25
  value="{{ request.GET.q|default:"" }}" />
25
26
  </span>
26
27
  <input type="submit" value="{% trans "Search my maps" %}" />
27
28
  </form>
28
29
  {% if maps.object_list|length > 1 %}
29
- <a href="{% url 'user_download' %}?{% spaceless %}
30
- {% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %}
31
- {% endspaceless %}" class="button button-download"
32
- >{% blocktranslate with count=maps.object_list|length trimmed %}
30
+ <a href="{% url 'user_download' %}?{% spaceless %} {% for map_inst in maps %}map_id={{ map_inst.pk }}{% if not forloop.last %}&{% endif %}{% endfor %} {% endspaceless %}"
31
+ class="button button-download">
32
+ {% blocktranslate with count=maps.object_list|length trimmed %}
33
33
  Download {{ count }} maps
34
34
  {% endblocktranslate %}
35
35
  </a>
@@ -45,7 +45,6 @@
45
45
  </div>
46
46
  </div>
47
47
  {% endblock maincontent %}
48
-
49
48
  {% block bottom_js %}
50
49
  {{ block.super }}
51
50
  <script type="text/javascript">
@@ -58,7 +57,7 @@
58
57
  const mapId = button.dataset.mapId
59
58
  if (!document.querySelector(`#${mapId}_target`).hasChildNodes()) {
60
59
  const previewSettings = JSON.parse(document.getElementById(mapId).textContent)
61
- const map = new L.U.Map(`${mapId}_target`, previewSettings)
60
+ const map = new U.Map(`${mapId}_target`, previewSettings)
62
61
  CACHE[mapId] = map
63
62
  } else {
64
63
  CACHE[mapId].invalidateSize()
umap/tests/conftest.py CHANGED
@@ -5,8 +5,6 @@ import pytest
5
5
  from django.core.cache import cache
6
6
  from django.core.signing import get_cookie_signer
7
7
 
8
- from umap.models import Map
9
-
10
8
  from .base import (
11
9
  DataLayerFactory,
12
10
  LicenceFactory,
@@ -1,10 +1,12 @@
1
1
  import re
2
+ from smtplib import SMTPException
3
+ from unittest.mock import patch
2
4
 
3
5
  import pytest
4
6
  from django.core.signing import get_cookie_signer
5
7
  from playwright.sync_api import expect
6
8
 
7
- from umap.models import DataLayer
9
+ from umap.models import DataLayer, Map
8
10
 
9
11
  from ..base import DataLayerFactory
10
12
 
@@ -150,3 +152,50 @@ def test_can_change_perms_after_create(tilelayer, live_server, page):
150
152
  ".datalayer-permissions select[name='edit_status'] option:checked"
151
153
  )
152
154
  expect(option).to_have_text("Inherit")
155
+
156
+
157
+ def test_alert_message_after_create(
158
+ tilelayer, live_server, page, monkeypatch, settings
159
+ ):
160
+ page.goto(f"{live_server.url}/en/map/new")
161
+ save = page.get_by_role("button", name="Save")
162
+ expect(save).to_be_visible()
163
+ alert = page.locator(".umap-alert")
164
+ expect(alert).to_be_hidden()
165
+ with page.expect_response(re.compile(r".*/map/create/")):
166
+ save.click()
167
+ new_map = Map.objects.last()
168
+ expect(alert).to_be_visible()
169
+ expect(
170
+ alert.get_by_text(
171
+ "Your map has been created! As you are not logged in, here is your secret "
172
+ "link to edit the map, please keep it safe:"
173
+ )
174
+ ).to_be_visible()
175
+ expect(alert.get_by_role("button", name="Copy")).to_be_visible()
176
+ expect(alert.get_by_role("button", name="Send me the link")).to_be_visible()
177
+ alert.get_by_placeholder("Email").fill("foo@bar.com")
178
+ with patch("umap.views.send_mail") as patched:
179
+ with page.expect_response(re.compile("/en/map/.*/send-edit-link/")):
180
+ alert.get_by_role("button", name="Send me the link").click()
181
+ assert patched.called
182
+ patched.assert_called_with(
183
+ "The uMap edit link for your map: Untitled map",
184
+ f"Here is your secret edit link: {new_map.get_anonymous_edit_url()}",
185
+ "test@test.org",
186
+ ["foo@bar.com"],
187
+ fail_silently=False,
188
+ )
189
+
190
+
191
+ def test_email_sending_error_are_catched(tilelayer, page, live_server):
192
+ page.goto(f"{live_server.url}/en/map/new")
193
+ alert = page.locator(".umap-alert")
194
+ with page.expect_response(re.compile(r".*/map/create/")):
195
+ page.get_by_role("button", name="Save").click()
196
+ alert.get_by_placeholder("Email").fill("foo@bar.com")
197
+ with patch("umap.views.send_mail", side_effect=SMTPException) as patched:
198
+ with page.expect_response(re.compile("/en/map/.*/send-edit-link/")):
199
+ alert.get_by_role("button", name="Send me the link").click()
200
+ assert patched.called
201
+ expect(alert.get_by_text("Can't send email to foo@bar.com")).to_be_visible()
@@ -38,7 +38,7 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
38
38
 
39
39
  with page_one.expect_response(DATALAYER_UPDATE):
40
40
  save_p1.click()
41
- # Prefent two layers to be saved on the same second, as we compare them based
41
+ # Prevent two layers to be saved on the same second, as we compare them based
42
42
  # on time in case of conflict. FIXME do not use time for comparison.
43
43
  sleep(1)
44
44
  assert DataLayer.objects.get(pk=datalayer.pk).settings == {
@@ -140,3 +140,59 @@ def test_collaborative_editing_create_markers(context, live_server, tilelayer):
140
140
  "permissions": {"edit_status": 1},
141
141
  }
142
142
  expect(marker_pane_p2).to_have_count(5)
143
+
144
+
145
+ def test_empty_datalayers_can_be_merged(context, live_server, tilelayer):
146
+ # Let's create a new map with an empty datalayer
147
+ map = MapFactory(name="collaborative editing")
148
+ DataLayerFactory(map=map, edit_status=DataLayer.ANONYMOUS, data={})
149
+
150
+ # Open two tabs at the same time, on the same empty map
151
+ page_one = context.new_page()
152
+ page_one.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
153
+
154
+ page_two = context.new_page()
155
+ page_two.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
156
+
157
+ save_p1 = page_one.get_by_role("button", name="Save")
158
+ expect(save_p1).to_be_visible()
159
+
160
+ # Click on the Draw a marker button on a new map.
161
+ create_marker_p1 = page_one.get_by_title("Draw a marker")
162
+ expect(create_marker_p1).to_be_visible()
163
+ create_marker_p1.click()
164
+
165
+ # Check no marker is present by default.
166
+ marker_pane_p1 = page_one.locator(".leaflet-marker-pane > div")
167
+ expect(marker_pane_p1).to_have_count(0)
168
+
169
+ # Click on the map, it will place a marker at the given position.
170
+ map_el_p1 = page_one.locator("#map")
171
+ map_el_p1.click(position={"x": 200, "y": 200})
172
+ expect(marker_pane_p1).to_have_count(1)
173
+
174
+ with page_one.expect_response(DATALAYER_UPDATE):
175
+ save_p1.click()
176
+ sleep(1)
177
+
178
+ save_p2 = page_two.get_by_role("button", name="Save")
179
+ expect(save_p2).to_be_visible()
180
+
181
+ # Click on the Draw a marker button on a new map.
182
+ create_marker_p2 = page_two.get_by_title("Draw a marker")
183
+ expect(create_marker_p2).to_be_visible()
184
+ create_marker_p2.click()
185
+
186
+ marker_pane_p2 = page_two.locator(".leaflet-marker-pane > div")
187
+
188
+ # Click on the map, it will place a marker at the given position.
189
+ map_el_p2 = page_two.locator("#map")
190
+ map_el_p2.click(position={"x": 220, "y": 220})
191
+ expect(marker_pane_p2).to_have_count(1)
192
+
193
+ # Save p1 and p2 at the same time
194
+ with page_two.expect_response(DATALAYER_UPDATE):
195
+ save_p2.click()
196
+ sleep(1)
197
+
198
+ expect(marker_pane_p2).to_have_count(2)
@@ -23,3 +23,16 @@ def test_owner_can_delete_map_after_confirmation(map, live_server, login):
23
23
  delete_button.click()
24
24
  assert dialog_shown
25
25
  assert Map.objects.all().count() == 0
26
+
27
+
28
+ def test_dashboard_map_preview(map, live_server, datalayer, login):
29
+ page = login(map.owner)
30
+ page.goto(f"{live_server.url}/en/me")
31
+ dialog = page.locator("dialog")
32
+ expect(dialog).to_be_hidden()
33
+ button = page.get_by_role("button", name="Open preview")
34
+ expect(button).to_be_visible()
35
+ button.click()
36
+ expect(dialog).to_be_visible()
37
+ # Let's check we have a marker on it, so we can guess the map loaded correctly
38
+ expect(dialog.locator(".leaflet-marker-icon")).to_be_visible()
@@ -73,3 +73,11 @@ def test_map_preview_can_change_styling_from_querystring(page, live_server, tile
73
73
  markers = page.locator(".leaflet-marker-icon .icon_container")
74
74
  expect(markers).to_have_count(1)
75
75
  expect(markers).to_have_css("background-color", "rgb(139, 0, 0)")
76
+
77
+
78
+ def test_can_open_feature_on_load(page, live_server, tilelayer):
79
+ page.goto(
80
+ f"{live_server.url}/map/?data={quote(json.dumps(GEOJSON))}&feature=Niagara Falls"
81
+ )
82
+ # Popup is open.
83
+ expect(page.get_by_text("Niagara Falls")).to_be_visible()
@@ -50,3 +50,17 @@ def test_can_deactivate_wheel_from_query_string(map, live_server, page):
50
50
  expect(page).to_have_url(re.compile(r".*#7/.+"))
51
51
  page.mouse.wheel(0, 1)
52
52
  expect(page).to_have_url(re.compile(r".*#7/.+"))
53
+
54
+
55
+ def test_zoom_control(map, live_server, datalayer, page):
56
+ control = page.locator(".leaflet-control-zoom")
57
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
58
+ expect(control).to_be_visible()
59
+ page.goto(f"{live_server.url}{map.get_absolute_url()}?zoomControl=false")
60
+ expect(control).to_be_hidden()
61
+ page.goto(f"{live_server.url}{map.get_absolute_url()}?zoomControl=true")
62
+ expect(control).to_be_visible()
63
+ page.goto(f"{live_server.url}{map.get_absolute_url()}?zoomControl=null")
64
+ expect(control).to_be_hidden()
65
+ page.get_by_title("More controls").click()
66
+ expect(control).to_be_visible()
@@ -6,7 +6,7 @@ from playwright.sync_api import expect
6
6
  pytestmark = pytest.mark.django_db
7
7
 
8
8
 
9
- def test_iframe_code(map, live_server, datalayer, page):
9
+ def test_iframe_code_can_contain_datalayers(map, live_server, datalayer, page):
10
10
  page.goto(f"{live_server.url}{map.get_absolute_url()}?share")
11
11
  textarea = page.locator(".umap-share-iframe")
12
12
  expect(textarea).to_be_visible()
@@ -20,3 +20,21 @@ def test_iframe_code(map, live_server, datalayer, page):
20
20
  page.get_by_text("Embed and link options").click()
21
21
  page.get_by_title("Keep current visible layers").click()
22
22
  expect(textarea).to_have_text(re.compile(f"datalayers={datalayer.pk}"))
23
+ # Now click again
24
+ page.get_by_title("Keep current visible layers").click()
25
+ expect(textarea).not_to_have_text(re.compile(f"datalayers={datalayer.pk}"))
26
+
27
+
28
+ def test_iframe_code_can_contain_feature(map, live_server, datalayer, page):
29
+ page.goto(f"{live_server.url}{map.get_absolute_url()}?share")
30
+ page.locator(".icon_container").click()
31
+ textarea = page.locator(".umap-share-iframe")
32
+ expect(textarea).to_be_visible()
33
+ expect(textarea).not_to_have_text(re.compile("feature=Here"))
34
+ # Open options
35
+ page.get_by_text("Embed and link options").click()
36
+ page.get_by_title("Open current feature on load").click()
37
+ expect(textarea).to_have_text(re.compile("feature=Here"))
38
+ # Click again to deactivate it
39
+ page.get_by_title("Open current feature on load").click()
40
+ expect(textarea).not_to_have_text(re.compile("feature=Here"))
@@ -0,0 +1,27 @@
1
+ import re
2
+
3
+ import pytest
4
+ from playwright.sync_api import expect
5
+
6
+ from umap.models import Star
7
+
8
+ pytestmark = pytest.mark.django_db
9
+
10
+
11
+ def test_star_control_is_visible_if_logged_in(map, live_server, page, login, user):
12
+ login(user)
13
+ assert not Star.objects.count()
14
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
15
+ page.get_by_title("More controls").click()
16
+ control = page.locator(".leaflet-control-star")
17
+ expect(control).to_be_visible()
18
+ with page.expect_response(re.compile(".*/star/")):
19
+ control.click()
20
+ assert Star.objects.count() == 1
21
+
22
+
23
+ def test_no_star_control_if_not_logged_in(map, live_server, page):
24
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
25
+ page.get_by_title("More controls").click()
26
+ control = page.locator(".leaflet-control-star")
27
+ expect(control).to_be_hidden()
@@ -1,6 +1,7 @@
1
1
  import re
2
2
  import shutil
3
3
  import tempfile
4
+ from copy import deepcopy
4
5
 
5
6
  import pytest
6
7
  from django.core.management import call_command
@@ -12,6 +13,11 @@ from playwright.sync_api import expect
12
13
  def staticfiles(settings):
13
14
  static_root = tempfile.mkdtemp(prefix="test_static")
14
15
  settings.STATIC_ROOT = static_root
16
+ # Make sure settings are properly reset after the test
17
+ settings.STORAGES = deepcopy(settings.STORAGES)
18
+ settings.STORAGES["staticfiles"][
19
+ "BACKEND"
20
+ ] = "umap.storage.UmapManifestStaticFilesStorage"
15
21
  try:
16
22
  call_command("collectstatic", "--noinput")
17
23
  yield
@@ -22,9 +28,6 @@ def staticfiles(settings):
22
28
  def test_javascript_have_been_loaded(
23
29
  map, live_server, datalayer, page, settings, staticfiles
24
30
  ):
25
- settings.STORAGES["staticfiles"][
26
- "BACKEND"
27
- ] = "umap.storage.UmapManifestStaticFilesStorage"
28
31
  datalayer.settings["displayOnLoad"] = False
29
32
  datalayer.save()
30
33
  map.settings["properties"]["defaultView"] = "latest"
umap/tests/settings.py CHANGED
@@ -3,7 +3,7 @@ import os
3
3
  from umap.settings.base import * # pylint: disable=W0614,W0401
4
4
 
5
5
  SECRET_KEY = "justfortests"
6
- FROM_EMAIL = "test@test.org"
6
+ DEFAULT_FROM_EMAIL = "test@test.org"
7
7
  EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
8
8
  STORAGES["staticfiles"][
9
9
  "BACKEND"
@@ -818,10 +818,10 @@ def test_oembed_map(client, map, datalayer):
818
818
  def test_oembed_link(client, map, datalayer):
819
819
  response = client.get(map.get_absolute_url())
820
820
  assert response.status_code == 200
821
+
821
822
  assert (
822
- '<link rel="alternate" type="application/json+oembed"'
823
- in response.content.decode()
824
- )
823
+ '<link rel="alternate"\n type="application/json+oembed"'
824
+ ) in response.content.decode()
825
825
  assert (
826
826
  'href="http://testserver/map/oembed/'
827
827
  f'?url=http%3A%2F%2Ftestserver%2Fen%2Fmap%2Ftest-map_{map.id}&format=json"'
umap/urls.py CHANGED
@@ -156,7 +156,7 @@ map_urls = [
156
156
  name="datalayer_permissions",
157
157
  ),
158
158
  ]
159
- if settings.FROM_EMAIL:
159
+ if settings.DEFAULT_FROM_EMAIL:
160
160
  map_urls.append(
161
161
  re_path(
162
162
  r"^map/(?P<map_id>[\d]+)/send-edit-link/$",
umap/views.py CHANGED
@@ -9,6 +9,7 @@ from datetime import datetime, timedelta
9
9
  from http.client import InvalidURL
10
10
  from io import BytesIO
11
11
  from pathlib import Path
12
+ from smtplib import SMTPException
12
13
  from urllib.error import HTTPError, URLError
13
14
  from urllib.parse import quote, quote_plus, urlparse
14
15
  from urllib.request import Request, build_opener
@@ -836,16 +837,19 @@ class SendEditLink(FormLessEditMixin, FormView):
836
837
  return HttpResponseBadRequest("Invalid")
837
838
  link = self.object.get_anonymous_edit_url()
838
839
 
839
- send_mail(
840
- _(
841
- "The uMap edit link for your map: %(map_name)s"
842
- % {"map_name": self.object.name}
843
- ),
844
- _("Here is your secret edit link: %(link)s" % {"link": link}),
845
- settings.FROM_EMAIL,
846
- [email],
847
- fail_silently=False,
840
+ subject = _(
841
+ "The uMap edit link for your map: %(map_name)s"
842
+ % {"map_name": self.object.name}
848
843
  )
844
+ body = _("Here is your secret edit link: %(link)s" % {"link": link})
845
+ try:
846
+ send_mail(
847
+ subject, body, settings.DEFAULT_FROM_EMAIL, [email], fail_silently=False
848
+ )
849
+ except SMTPException:
850
+ return simple_json_response(
851
+ error=_("Can't send email to %(email)s" % {"email": email})
852
+ )
849
853
  return simple_json_response(
850
854
  info=_("Email sent to %(email)s" % {"email": email})
851
855
  )
@@ -1076,7 +1080,9 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
1076
1080
 
1077
1081
  try:
1078
1082
  merged_features = merge_features(
1079
- reference["features"], latest["features"], entrant["features"]
1083
+ reference.get("features", []),
1084
+ latest.get("features", []),
1085
+ entrant.get("features", []),
1080
1086
  )
1081
1087
  latest["features"] = merged_features
1082
1088
  return latest