umap-project 2.3.1__py3-none-any.whl → 2.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (204) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.po +81 -31
  3. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/fr/LC_MESSAGES/django.po +117 -66
  5. umap/management/commands/run_websocket_server.py +23 -0
  6. umap/models.py +6 -1
  7. umap/settings/base.py +11 -3
  8. umap/static/umap/base.css +64 -184
  9. umap/static/umap/content.css +3 -2
  10. umap/static/umap/css/dialog.css +18 -0
  11. umap/static/umap/css/icon.css +8 -0
  12. umap/static/umap/css/importers.css +51 -0
  13. umap/static/umap/css/panel.css +18 -57
  14. umap/static/umap/css/tooltip.css +59 -0
  15. umap/static/umap/css/window.css +35 -0
  16. umap/static/umap/img/16-white.svg +1 -3
  17. umap/static/umap/img/alert-icon-error.svg +8 -0
  18. umap/static/umap/img/alert-icon-info.svg +4 -0
  19. umap/static/umap/img/alert-icon-success.svg +3 -0
  20. umap/static/umap/img/icon-external-link.svg +3 -0
  21. umap/static/umap/img/importers/communesfr.svg +5 -0
  22. umap/static/umap/img/importers/datasets.svg +13 -0
  23. umap/static/umap/img/importers/geodatamine.svg +10 -0
  24. umap/static/umap/img/importers/overpass.svg +7 -0
  25. umap/static/umap/img/importers/random.svg +18 -0
  26. umap/static/umap/img/importers/random1.svg +4 -0
  27. umap/static/umap/img/importers/random2.svg +4 -0
  28. umap/static/umap/img/source/16-white.svg +2 -4
  29. umap/static/umap/js/components/alerts/alert.css +160 -0
  30. umap/static/umap/js/components/alerts/alert.js +169 -0
  31. umap/static/umap/js/components/base.js +54 -0
  32. umap/static/umap/js/modules/autocomplete.js +347 -0
  33. umap/static/umap/js/modules/browser.js +6 -6
  34. umap/static/umap/js/modules/caption.js +5 -4
  35. umap/static/umap/js/modules/global.js +36 -12
  36. umap/static/umap/js/modules/help.js +255 -0
  37. umap/static/umap/js/modules/importer.js +308 -0
  38. umap/static/umap/js/modules/importers/communesfr.js +44 -0
  39. umap/static/umap/js/modules/importers/datasets.js +42 -0
  40. umap/static/umap/js/modules/importers/geodatamine.js +95 -0
  41. umap/static/umap/js/modules/importers/overpass.js +84 -0
  42. umap/static/umap/js/modules/request.js +12 -14
  43. umap/static/umap/js/modules/rules.js +241 -0
  44. umap/static/umap/js/modules/schema.js +63 -14
  45. umap/static/umap/js/modules/sync/engine.js +93 -0
  46. umap/static/umap/js/modules/sync/updaters.js +109 -0
  47. umap/static/umap/js/modules/sync/websocket.js +25 -0
  48. umap/static/umap/js/modules/ui/dialog.js +52 -0
  49. umap/static/umap/js/modules/{panel.js → ui/panel.js} +25 -14
  50. umap/static/umap/js/modules/ui/tooltip.js +116 -0
  51. umap/static/umap/js/modules/utils.js +25 -18
  52. umap/static/umap/js/umap.controls.js +13 -14
  53. umap/static/umap/js/umap.core.js +1 -324
  54. umap/static/umap/js/umap.features.js +77 -29
  55. umap/static/umap/js/umap.forms.js +9 -13
  56. umap/static/umap/js/umap.js +254 -215
  57. umap/static/umap/js/umap.layer.js +152 -74
  58. umap/static/umap/js/umap.permissions.js +5 -9
  59. umap/static/umap/js/umap.popup.js +1 -1
  60. umap/static/umap/js/umap.tableeditor.js +8 -8
  61. umap/static/umap/locale/am_ET.js +51 -16
  62. umap/static/umap/locale/am_ET.json +51 -16
  63. umap/static/umap/locale/ar.js +51 -16
  64. umap/static/umap/locale/ar.json +51 -16
  65. umap/static/umap/locale/ast.js +51 -16
  66. umap/static/umap/locale/ast.json +51 -16
  67. umap/static/umap/locale/bg.js +51 -16
  68. umap/static/umap/locale/bg.json +51 -16
  69. umap/static/umap/locale/br.js +55 -20
  70. umap/static/umap/locale/br.json +55 -20
  71. umap/static/umap/locale/ca.js +51 -16
  72. umap/static/umap/locale/ca.json +51 -16
  73. umap/static/umap/locale/cs_CZ.js +93 -58
  74. umap/static/umap/locale/cs_CZ.json +93 -58
  75. umap/static/umap/locale/da.js +51 -16
  76. umap/static/umap/locale/da.json +51 -16
  77. umap/static/umap/locale/de.js +56 -21
  78. umap/static/umap/locale/de.json +56 -21
  79. umap/static/umap/locale/el.js +51 -16
  80. umap/static/umap/locale/el.json +51 -16
  81. umap/static/umap/locale/en.js +52 -16
  82. umap/static/umap/locale/en.json +52 -16
  83. umap/static/umap/locale/en_US.json +51 -16
  84. umap/static/umap/locale/es.js +51 -16
  85. umap/static/umap/locale/es.json +51 -16
  86. umap/static/umap/locale/et.js +51 -16
  87. umap/static/umap/locale/et.json +51 -16
  88. umap/static/umap/locale/eu.js +51 -16
  89. umap/static/umap/locale/eu.json +51 -16
  90. umap/static/umap/locale/fa_IR.js +51 -16
  91. umap/static/umap/locale/fa_IR.json +51 -16
  92. umap/static/umap/locale/fi.js +51 -16
  93. umap/static/umap/locale/fi.json +51 -16
  94. umap/static/umap/locale/fr.js +61 -25
  95. umap/static/umap/locale/fr.json +61 -25
  96. umap/static/umap/locale/gl.js +51 -16
  97. umap/static/umap/locale/gl.json +51 -16
  98. umap/static/umap/locale/he.js +51 -16
  99. umap/static/umap/locale/he.json +51 -16
  100. umap/static/umap/locale/hr.js +51 -16
  101. umap/static/umap/locale/hr.json +51 -16
  102. umap/static/umap/locale/hu.js +51 -16
  103. umap/static/umap/locale/hu.json +51 -16
  104. umap/static/umap/locale/id.js +51 -16
  105. umap/static/umap/locale/id.json +51 -16
  106. umap/static/umap/locale/is.js +51 -16
  107. umap/static/umap/locale/is.json +51 -16
  108. umap/static/umap/locale/it.js +51 -16
  109. umap/static/umap/locale/it.json +51 -16
  110. umap/static/umap/locale/ja.js +51 -16
  111. umap/static/umap/locale/ja.json +51 -16
  112. umap/static/umap/locale/ko.js +51 -16
  113. umap/static/umap/locale/ko.json +51 -16
  114. umap/static/umap/locale/lt.js +51 -16
  115. umap/static/umap/locale/lt.json +51 -16
  116. umap/static/umap/locale/ms.js +51 -16
  117. umap/static/umap/locale/ms.json +51 -16
  118. umap/static/umap/locale/nl.js +51 -16
  119. umap/static/umap/locale/nl.json +51 -16
  120. umap/static/umap/locale/no.js +51 -16
  121. umap/static/umap/locale/no.json +51 -16
  122. umap/static/umap/locale/pl.js +93 -58
  123. umap/static/umap/locale/pl.json +93 -58
  124. umap/static/umap/locale/pl_PL.json +51 -16
  125. umap/static/umap/locale/pt.js +215 -180
  126. umap/static/umap/locale/pt.json +215 -180
  127. umap/static/umap/locale/pt_BR.js +51 -16
  128. umap/static/umap/locale/pt_BR.json +51 -16
  129. umap/static/umap/locale/pt_PT.js +51 -16
  130. umap/static/umap/locale/pt_PT.json +51 -16
  131. umap/static/umap/locale/ro.js +51 -16
  132. umap/static/umap/locale/ro.json +51 -16
  133. umap/static/umap/locale/ru.js +51 -16
  134. umap/static/umap/locale/ru.json +51 -16
  135. umap/static/umap/locale/si.js +51 -16
  136. umap/static/umap/locale/si.json +51 -16
  137. umap/static/umap/locale/sk_SK.js +51 -16
  138. umap/static/umap/locale/sk_SK.json +51 -16
  139. umap/static/umap/locale/sl.js +51 -16
  140. umap/static/umap/locale/sl.json +51 -16
  141. umap/static/umap/locale/sr.js +51 -16
  142. umap/static/umap/locale/sr.json +51 -16
  143. umap/static/umap/locale/sv.js +51 -16
  144. umap/static/umap/locale/sv.json +51 -16
  145. umap/static/umap/locale/th_TH.js +51 -16
  146. umap/static/umap/locale/th_TH.json +51 -16
  147. umap/static/umap/locale/tr.js +51 -16
  148. umap/static/umap/locale/tr.json +51 -16
  149. umap/static/umap/locale/uk_UA.js +51 -16
  150. umap/static/umap/locale/uk_UA.json +51 -16
  151. umap/static/umap/locale/vi.js +51 -16
  152. umap/static/umap/locale/vi.json +51 -16
  153. umap/static/umap/locale/vi_VN.json +51 -16
  154. umap/static/umap/locale/zh.js +51 -16
  155. umap/static/umap/locale/zh.json +51 -16
  156. umap/static/umap/locale/zh_CN.json +51 -16
  157. umap/static/umap/locale/zh_TW.Big5.json +51 -16
  158. umap/static/umap/locale/zh_TW.js +51 -16
  159. umap/static/umap/locale/zh_TW.json +51 -16
  160. umap/static/umap/map.css +40 -53
  161. umap/static/umap/unittests/sync.js +105 -0
  162. umap/static/umap/unittests/utils.js +78 -36
  163. umap/static/umap/vars.css +19 -1
  164. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +2 -2
  165. umap/templates/umap/components/alerts/alert.html +89 -0
  166. umap/templates/umap/content.html +4 -3
  167. umap/templates/umap/css.html +4 -0
  168. umap/templates/umap/home.html +3 -0
  169. umap/templates/umap/js.html +0 -3
  170. umap/templates/umap/map_init.html +2 -8
  171. umap/templates/umap/messages.html +9 -11
  172. umap/templates/umap/search.html +3 -0
  173. umap/tests/base.py +2 -0
  174. umap/tests/integration/conftest.py +30 -0
  175. umap/tests/integration/test_anonymous_owned_map.py +8 -13
  176. umap/tests/integration/test_browser.py +77 -4
  177. umap/tests/integration/test_conditional_rules.py +201 -0
  178. umap/tests/integration/test_dashboard.py +1 -1
  179. umap/tests/integration/test_datalayer.py +2 -3
  180. umap/tests/integration/test_edit_datalayer.py +4 -4
  181. umap/tests/integration/test_edit_map.py +1 -1
  182. umap/tests/integration/test_facets_browser.py +3 -3
  183. umap/tests/integration/test_import.py +185 -49
  184. umap/tests/integration/test_map.py +31 -2
  185. umap/tests/integration/{test_collaborative_editing.py → test_optimistic_merge.py} +7 -7
  186. umap/tests/integration/test_owned_map.py +1 -1
  187. umap/tests/integration/test_picto.py +2 -2
  188. umap/tests/integration/test_statics.py +1 -1
  189. umap/tests/integration/test_view_marker.py +2 -2
  190. umap/tests/integration/test_websocket_sync.py +283 -0
  191. umap/tests/settings.py +5 -0
  192. umap/tests/test_datalayer_views.py +0 -1
  193. umap/tests/test_views.py +53 -0
  194. umap/urls.py +5 -0
  195. umap/views.py +40 -11
  196. umap/websocket_server.py +92 -0
  197. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/METADATA +10 -8
  198. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/RECORD +201 -167
  199. umap/static/umap/js/umap.autocomplete.js +0 -341
  200. umap/static/umap/js/umap.importer.js +0 -187
  201. umap/static/umap/js/umap.ui.js +0 -190
  202. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/WHEEL +0 -0
  203. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/entry_points.txt +0 -0
  204. {umap_project-2.3.1.dist-info → umap_project-2.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -61,8 +61,8 @@ L.FormBuilder = L.Evented.extend({
61
61
  getter: function (field) {
62
62
  var path = field.split('.'),
63
63
  value = this.obj;
64
- for (var i = 0, l = path.length; i < l; i++) {
65
- value = value[path[i]];
64
+ for (const prop of path) {
65
+ value = value[prop];
66
66
  }
67
67
  return value;
68
68
  },
@@ -0,0 +1,89 @@
1
+ {% load i18n static %}
2
+
3
+ <style type="text/css">
4
+ @import "{% static 'umap/js/components/alerts/alert.css' %}";
5
+ </style>
6
+
7
+ <template id="umap-alert-template">
8
+ <div role="dialog" class="dark window">
9
+ <div>
10
+ <p role="alert"></p>
11
+ </div>
12
+ <ul class="buttons">
13
+ <li>
14
+ <button class="icon icon-16 icon-close" aria-label="{% translate "Close" %}" data-close></button>
15
+ </li>
16
+ </ul>
17
+ </div>
18
+ </template>
19
+
20
+ <umap-alert></umap-alert>
21
+
22
+ <template id="umap-alert-creation-template">
23
+ <div role="dialog" class="dark window">
24
+ <div>
25
+ <h3 role="alert"></h3>
26
+ {% url "login" as login_url %}
27
+ <p><em>{% blocktranslate %}Pro-tip: to easily find back your maps, <a href="{{ login_url }}" target="_blank">create an account</a> or <a href="{{ login_url }}" target="_blank">log in</a>.{% endblocktranslate %}</em></p>
28
+ <div id="link-wrapper">
29
+ <form>
30
+ <label for="url">{% translate "Here is your secret link to edit the map, please keep it safe:" %}</label>
31
+ <fieldset role="group">
32
+ <input type="url" name="url" id="url">
33
+ <input type="button" value="{% translate "Copy link" %}">
34
+ </fieldset>
35
+ </form>
36
+ </div>
37
+ <div id="form-wrapper" hidden>
38
+ <form>
39
+ <label for="email">{% translate "Enter your email address to receive the secret link:" %}</label>
40
+ <fieldset role="group">
41
+ <input type="email" name="email" id="email" placeholder="{% translate "Email" %}" required>
42
+ <input type="submit" value="{% translate "Send me the link" %}" class="umap-action">
43
+ </fieldset>
44
+ </form>
45
+ </div>
46
+ </div>
47
+ <ul class="buttons">
48
+ <li>
49
+ <button class="icon icon-16 icon-close" aria-label="{% translate "Close" %}" data-close></button>
50
+ </li>
51
+ </ul>
52
+ </div>
53
+ </template>
54
+
55
+ <umap-alert-creation></umap-alert-creation>
56
+
57
+ <template id="umap-alert-conflict-template">
58
+ <div role="dialog" class="dark window">
59
+ <div>
60
+ <p role="alert"></p>
61
+ <div id="conflict-wrapper">
62
+ <form>
63
+ <a href="#" onclick="document.url" target="_blank">{% translate "See their edits in another tab" %}</a>
64
+ <input id="your-changes" type="submit" value="{% translate "Keep your changes and loose theirs" %}">
65
+ <input id="their-changes" type="submit" value="{% translate "Keep their changes and loose yours" %}">
66
+ </form>
67
+ </div>
68
+ </div>
69
+ <ul class="buttons">
70
+ <li>
71
+ <button class="icon icon-16 icon-close" aria-label="{% translate "Close" %}" data-close></button>
72
+ </li>
73
+ </ul>
74
+ </div>
75
+ </template>
76
+
77
+ <umap-alert-conflict></umap-alert-conflict>
78
+
79
+ <script type="module">
80
+ import { register } from '{% static 'umap/js/components/base.js' %}'
81
+ import {
82
+ uMapAlert,
83
+ uMapAlertCreation,
84
+ uMapAlertConflict
85
+ } from '{% static 'umap/js/components/alerts/alert.js' %}'
86
+ register(uMapAlert, 'umap-alert')
87
+ register(uMapAlertCreation, 'umap-alert-creation')
88
+ register(uMapAlertConflict, 'umap-alert-conflict')
89
+ </script>
@@ -12,7 +12,9 @@
12
12
  <header class="wrapper row">
13
13
  {% include "umap/navigation.html" with title=SITE_NAME %}
14
14
  </header>
15
- {% include "umap/messages.html" with title=SITE_NAME %}
15
+ {% block messages %}
16
+ {% include "umap/messages.html" with title=SITE_NAME %}
17
+ {% endblock messages %}
16
18
  {% endblock header %}
17
19
  {% block content %}
18
20
  {% if UMAP_READONLY %}
@@ -38,8 +40,7 @@
38
40
  {{ block.super }}
39
41
  <script type="text/javascript">
40
42
  window.addEventListener('DOMContentLoaded', event => {
41
- const ui = new U.UI(document.querySelector('header'))
42
- const server = new U.ServerRequest(ui)
43
+ const server = new U.ServerRequest()
43
44
  const getMore = async function (e) {
44
45
  L.DomEvent.stop(e)
45
46
  const [{html}, response, error] = await server.get(this.href)
@@ -29,4 +29,8 @@
29
29
  <link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
30
30
  <link rel="stylesheet" href="{% static 'umap/map.css' %}" />
31
31
  <link rel="stylesheet" href="{% static 'umap/css/panel.css' %}" />
32
+ <link rel="stylesheet" href="{% static 'umap/css/window.css' %}" />
33
+ <link rel="stylesheet" href="{% static 'umap/css/tooltip.css' %}" />
34
+ <link rel="stylesheet" href="{% static 'umap/css/dialog.css' %}" />
35
+ <link rel="stylesheet" href="{% static 'umap/css/importers.css' %}" />
32
36
  <link rel="stylesheet" href="{% static 'umap/theme.css' %}" />
@@ -1,5 +1,8 @@
1
1
  {% extends "umap/content.html" %}
2
2
  {% load umap_tags i18n %}
3
+ {% block messages %}
4
+ {# We don't want maps in the list to display errors. #}
5
+ {% endblock messages %}
3
6
  {% block maincontent %}
4
7
  {% include "umap/search_bar.html" %}
5
8
  {% include "umap/about_summary.html" %}
@@ -46,7 +46,6 @@
46
46
  <script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}"
47
47
  defer></script>
48
48
  <script src="{% static 'umap/js/umap.core.js' %}" defer></script>
49
- <script src="{% static 'umap/js/umap.autocomplete.js' %}" defer></script>
50
49
  <script src="{% static 'umap/js/umap.popup.js' %}" defer></script>
51
50
  <script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
52
51
  <script src="{% static 'umap/js/umap.icon.js' %}" defer></script>
@@ -57,8 +56,6 @@
57
56
  <script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
58
57
  <script src="{% static 'umap/js/umap.slideshow.js' %}" defer></script>
59
58
  <script src="{% static 'umap/js/umap.tableeditor.js' %}" defer></script>
60
- <script src="{% static 'umap/js/umap.importer.js' %}" defer></script>
61
59
  <script src="{% static 'umap/js/umap.share.js' %}" defer></script>
62
60
  <script src="{% static 'umap/js/umap.js' %}" defer></script>
63
- <script src="{% static 'umap/js/umap.ui.js' %}" defer></script>
64
61
  <script src="{% static 'umap/js/components/fragment.js' %}" defer></script>
@@ -1,17 +1,11 @@
1
1
  {% load umap_tags %}
2
+
3
+ {% include "umap/messages.html" %}
2
4
  <div id="map"></div>
3
5
  <!-- djlint:off -->
4
6
  <script defer type="text/javascript">
5
7
  window.addEventListener('DOMContentLoaded', (event) => {
6
8
  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
10
  </script>
17
11
  <!-- djlint:on -->
@@ -1,11 +1,9 @@
1
- <div class="wrapper">
2
- <div class="row">
3
- {% if messages %}
4
- <ul class="messages">
5
- {% for message in messages %}
6
- <li {% if message.tags %}class="{{ message.tags }}"{% endif %}>{{ message }}</li>
7
- {% endfor %}
8
- </ul>
9
- {% endif %}
10
- </div>
11
- </div>
1
+ {% load i18n %}
2
+
3
+ {% include "umap/components/alerts/alert.html" %}
4
+
5
+ {% for message in messages %}
6
+ <script type="module" defer>
7
+ U.Alert.success("{{ message }}")
8
+ </script>
9
+ {% endfor %}
@@ -1,5 +1,8 @@
1
1
  {% extends "umap/content.html" %}
2
2
  {% load i18n %}
3
+ {% block messages %}
4
+ {# We don't want maps from the results list to display errors in the main page. #}
5
+ {% endblock messages %}
3
6
  {% block maincontent %}
4
7
  {% include "umap/search_bar.html" %}
5
8
  <div class="wrapper">
umap/tests/base.py CHANGED
@@ -127,6 +127,8 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
127
127
  data.setdefault("_umap_options", {})
128
128
  kwargs["settings"]["name"] = kwargs["name"]
129
129
  data["_umap_options"]["name"] = kwargs["name"]
130
+ data.setdefault("type", "FeatureCollection")
131
+ data.setdefault("features", [])
130
132
  kwargs["geojson"] = ContentFile(json.dumps(data), "foo.json")
131
133
  return kwargs
132
134
 
@@ -1,9 +1,17 @@
1
1
  import os
2
+ import subprocess
3
+ import time
4
+ from pathlib import Path
2
5
 
3
6
  import pytest
4
7
  from playwright.sync_api import expect
5
8
 
6
9
 
10
+ @pytest.fixture(scope="session")
11
+ def browser_context_args(browser_context_args):
12
+ return {**browser_context_args, "locale": "en-GB", "timezone_id": "Europe/Paris"}
13
+
14
+
7
15
  @pytest.fixture(autouse=True)
8
16
  def set_timeout(context):
9
17
  timeout = int(os.environ.get("PLAYWRIGHT_TIMEOUT", 7500))
@@ -33,3 +41,25 @@ def login(context, settings, live_server):
33
41
  return page
34
42
 
35
43
  return do_login
44
+
45
+
46
+ @pytest.fixture
47
+ def websocket_server():
48
+ # Find the test-settings, and put them in the current environment
49
+ settings_path = (Path(__file__).parent.parent / "settings.py").absolute().as_posix()
50
+ os.environ["UMAP_SETTINGS"] = settings_path
51
+
52
+ ds_proc = subprocess.Popen(
53
+ [
54
+ "umap",
55
+ "run_websocket_server",
56
+ ],
57
+ stdout=subprocess.PIPE,
58
+ stderr=subprocess.STDOUT,
59
+ )
60
+ time.sleep(2)
61
+ # Ensure it started properly before yielding
62
+ assert not ds_proc.poll(), ds_proc.stdout.read().decode("utf-8")
63
+ yield ds_proc
64
+ # Shut it down at the end of the pytest session
65
+ ds_proc.terminate()
@@ -164,17 +164,14 @@ def test_alert_message_after_create(
164
164
  page.goto(f"{live_server.url}/en/map/new")
165
165
  save = page.get_by_role("button", name="Save")
166
166
  expect(save).to_be_visible()
167
- alert = page.locator(".umap-alert")
167
+ alert = page.locator('umap-alert-creation div[role="dialog"]')
168
168
  expect(alert).to_be_hidden()
169
169
  with page.expect_response(re.compile(r".*/map/create/")):
170
170
  save.click()
171
171
  new_map = Map.objects.last()
172
172
  expect(alert).to_be_visible()
173
173
  expect(
174
- alert.get_by_text(
175
- "Your map has been created! As you are not logged in, here is your secret "
176
- "link to edit the map, please keep it safe:"
177
- )
174
+ alert.get_by_text("Your map has been created with an anonymous account!")
178
175
  ).to_be_visible()
179
176
  expect(alert.get_by_role("button", name="Copy")).to_be_visible()
180
177
  expect(alert.get_by_role("button", name="Send me the link")).to_be_visible()
@@ -194,14 +191,15 @@ def test_alert_message_after_create(
194
191
 
195
192
  def test_email_sending_error_are_catched(tilelayer, page, live_server):
196
193
  page.goto(f"{live_server.url}/en/map/new")
197
- alert = page.locator(".umap-alert")
194
+ alert_creation = page.locator('umap-alert-creation div[role="dialog"]')
198
195
  with page.expect_response(re.compile(r".*/map/create/")):
199
196
  page.get_by_role("button", name="Save").click()
200
- alert.get_by_placeholder("Email").fill("foo@bar.com")
197
+ alert_creation.get_by_placeholder("Email").fill("foo@bar.com")
201
198
  with patch("umap.views.send_mail", side_effect=SMTPException) as patched:
202
199
  with page.expect_response(re.compile("/en/map/.*/send-edit-link/")):
203
- alert.get_by_role("button", name="Send me the link").click()
200
+ alert_creation.get_by_role("button", name="Send me the link").click()
204
201
  assert patched.called
202
+ alert = page.locator('umap-alert div[role="dialog"]')
205
203
  expect(alert.get_by_text("Can't send email to foo@bar.com")).to_be_visible()
206
204
 
207
205
 
@@ -214,13 +212,10 @@ def test_alert_message_after_create_show_link_even_without_mail(
214
212
  page.goto(f"{live_server.url}/en/map/new")
215
213
  with page.expect_response(re.compile(r".*/map/create/")):
216
214
  page.get_by_role("button", name="Save").click()
217
- alert = page.locator(".umap-alert")
215
+ alert = page.locator('umap-alert-creation div[role="dialog"]')
218
216
  expect(alert).to_be_visible()
219
217
  expect(
220
- alert.get_by_text(
221
- "Your map has been created! As you are not logged in, here is your secret "
222
- "link to edit the map, please keep it safe:"
223
- )
218
+ alert.get_by_text("Your map has been created with an anonymous account!")
224
219
  ).to_be_visible()
225
220
  expect(alert.get_by_role("button", name="Copy")).to_be_visible()
226
221
  expect(alert.get_by_role("button", name="Send me the link")).to_be_hidden()
@@ -15,12 +15,16 @@ DATALAYER_DATA = {
15
15
  "features": [
16
16
  {
17
17
  "type": "Feature",
18
- "properties": {"name": "one point in france", "foo": "point"},
18
+ "properties": {"name": "one point in france", "foo": "point", "bar": "one"},
19
19
  "geometry": {"type": "Point", "coordinates": [3.339844, 46.920255]},
20
20
  },
21
21
  {
22
22
  "type": "Feature",
23
- "properties": {"name": "one polygon in greenland", "foo": "polygon"},
23
+ "properties": {
24
+ "name": "one polygon in greenland",
25
+ "foo": "polygon",
26
+ "bar": "two",
27
+ },
24
28
  "geometry": {
25
29
  "type": "Polygon",
26
30
  "coordinates": [
@@ -36,7 +40,11 @@ DATALAYER_DATA = {
36
40
  },
37
41
  {
38
42
  "type": "Feature",
39
- "properties": {"name": "one line in new zeland", "foo": "line"},
43
+ "properties": {
44
+ "name": "one line in new zeland",
45
+ "foo": "line",
46
+ "bar": "three",
47
+ },
40
48
  "geometry": {
41
49
  "type": "LineString",
42
50
  "coordinates": [
@@ -103,6 +111,71 @@ def test_data_browser_should_be_filterable(live_server, page, bootstrap, map):
103
111
  expect(paths).to_have_count(0)
104
112
 
105
113
 
114
+ def test_filter_uses_layer_setting_if_any(live_server, page, bootstrap, map):
115
+ datalayer = map.datalayer_set.first()
116
+ datalayer.settings["labelKey"] = "foo"
117
+ datalayer.save()
118
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
119
+ expect(page.get_by_title("Features in this layer: 3")).to_be_visible()
120
+ markers = page.locator(".leaflet-marker-icon")
121
+ paths = page.locator(".leaflet-overlay-pane path")
122
+ expect(markers).to_have_count(1)
123
+ expect(paths).to_have_count(2)
124
+ expect(page.get_by_text("point")).to_be_visible()
125
+ expect(page.get_by_text("polygon")).to_be_visible()
126
+ expect(page.get_by_text("line")).to_be_visible()
127
+ page.locator(".filters summary").click()
128
+ filter_ = page.locator("input[name='filter']")
129
+ expect(filter_).to_be_visible()
130
+ filter_.type("po")
131
+ expect(page.get_by_title("Features in this layer: 2/3")).to_be_visible()
132
+ expect(page.get_by_title("Features in this layer: 2/3")).to_have_text("(2/3)")
133
+ expect(page.get_by_text("line")).to_be_hidden()
134
+ expect(page.get_by_text("point")).to_be_visible()
135
+ expect(page.get_by_text("polygon")).to_be_visible()
136
+ expect(markers).to_have_count(1)
137
+ expect(paths).to_have_count(1) # Only polygon
138
+ # Empty the filter
139
+ filter_.fill("")
140
+ filter_.blur()
141
+ expect(markers).to_have_count(1)
142
+ expect(paths).to_have_count(2)
143
+ filter_.type("point")
144
+ expect(page.get_by_text("point")).to_be_visible()
145
+ expect(page.get_by_text("line")).to_be_hidden()
146
+ expect(page.get_by_text("polygon")).to_be_hidden()
147
+ expect(markers).to_have_count(1)
148
+ expect(paths).to_have_count(0)
149
+
150
+
151
+ def test_filter_works_with_variable_in_labelKey(live_server, page, map):
152
+ map.settings["properties"]["onLoadPanel"] = "databrowser"
153
+ map.save()
154
+ data = deepcopy(DATALAYER_DATA)
155
+ data["_umap_options"]["labelKey"] = "{name} ({bar})"
156
+ DataLayerFactory(map=map, data=data)
157
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
158
+ expect(page.get_by_title("Features in this layer: 3")).to_be_visible()
159
+ markers = page.locator(".leaflet-marker-icon")
160
+ paths = page.locator(".leaflet-overlay-pane path")
161
+ expect(markers).to_have_count(1)
162
+ expect(paths).to_have_count(2)
163
+ expect(page.get_by_text("one point in france (one)")).to_be_visible()
164
+ expect(page.get_by_text("one line in new zeland (three)")).to_be_visible()
165
+ expect(page.get_by_text("one polygon in greenland (two)")).to_be_visible()
166
+ page.locator(".filters summary").click()
167
+ filter_ = page.locator("input[name='filter']")
168
+ expect(filter_).to_be_visible()
169
+ filter_.type("two")
170
+ expect(page.get_by_title("Features in this layer: 1/3")).to_be_visible()
171
+ expect(page.get_by_title("Features in this layer: 1/3")).to_have_text("(1/3)")
172
+ expect(page.get_by_text("one polygon in greenland (two)")).to_be_visible()
173
+ expect(page.get_by_text("one line in new zeland (three)")).to_be_hidden()
174
+ expect(page.get_by_text("one point in france (one)")).to_be_hidden()
175
+ expect(markers).to_have_count(0)
176
+ expect(paths).to_have_count(1) # Only polygon
177
+
178
+
106
179
  def test_data_browser_can_show_only_visible_features(live_server, page, bootstrap, map):
107
180
  # Zoom on France
108
181
  page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
@@ -178,7 +251,7 @@ def test_data_browser_bbox_filter_should_be_persistent(
178
251
  expect(browser.get_by_text("one point in france")).to_be_hidden()
179
252
  expect(browser.get_by_text("one line in new zeland")).to_be_hidden()
180
253
  expect(browser.get_by_text("one polygon in greenland")).to_be_hidden()
181
- page.get_by_title("See layers").click()
254
+ page.get_by_title("Open browser").click()
182
255
  expect(browser.get_by_text("one point in france")).to_be_visible()
183
256
  expect(browser.get_by_text("one line in new zeland")).to_be_hidden()
184
257
  expect(browser.get_by_text("one polygon in greenland")).to_be_hidden()
@@ -0,0 +1,201 @@
1
+ import pytest
2
+ from playwright.sync_api import expect
3
+
4
+ from ..base import DataLayerFactory
5
+
6
+ pytestmark = pytest.mark.django_db
7
+
8
+
9
+ def getColors(elements):
10
+ return [
11
+ el.evaluate("e => window.getComputedStyle(e).backgroundColor")
12
+ for el in elements.all()
13
+ ]
14
+
15
+
16
+ DATALAYER_DATA1 = {
17
+ "type": "FeatureCollection",
18
+ "features": [
19
+ {
20
+ "type": "Feature",
21
+ "properties": {
22
+ "mytype": "even",
23
+ "name": "Point 2",
24
+ "mynumber": 10,
25
+ "myboolean": True,
26
+ "mydate": "2024/04/14 12:19:17",
27
+ },
28
+ "geometry": {"type": "Point", "coordinates": [0.065918, 48.385442]},
29
+ },
30
+ {
31
+ "type": "Feature",
32
+ "properties": {
33
+ "mytype": "odd",
34
+ "name": "Point 1",
35
+ "mynumber": 12,
36
+ "myboolean": False,
37
+ "mydate": "2024/03/13 12:20:20",
38
+ },
39
+ "geometry": {"type": "Point", "coordinates": [3.55957, 49.767074]},
40
+ },
41
+ ],
42
+ "_umap_options": {
43
+ "name": "Calque 1",
44
+ },
45
+ }
46
+
47
+
48
+ DATALAYER_DATA2 = {
49
+ "type": "FeatureCollection",
50
+ "features": [
51
+ {
52
+ "type": "Feature",
53
+ "properties": {
54
+ "mytype": "even",
55
+ "name": "Point 4",
56
+ "mynumber": 10,
57
+ "myboolean": "true",
58
+ "mydate": "2024/08/18 13:14:15",
59
+ },
60
+ "geometry": {"type": "Point", "coordinates": [0.856934, 45.290347]},
61
+ },
62
+ {
63
+ "type": "Feature",
64
+ "properties": {
65
+ "mytype": "odd",
66
+ "name": "Point 3",
67
+ "mynumber": 14,
68
+ "mydate": "2024-04-14T10:19:17.000Z",
69
+ },
70
+ "geometry": {"type": "Point", "coordinates": [4.372559, 47.945786]},
71
+ },
72
+ ],
73
+ "_umap_options": {
74
+ "name": "Calque 2",
75
+ },
76
+ }
77
+
78
+
79
+ def test_simple_equal_rule_at_load(live_server, page, map):
80
+ map.settings["properties"]["rules"] = [
81
+ {"condition": "mytype=odd", "options": {"color": "aliceblue"}}
82
+ ]
83
+ map.save()
84
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
85
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
86
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
87
+ markers = page.locator(".leaflet-marker-icon .icon_container")
88
+ expect(markers).to_have_count(4)
89
+ colors = getColors(markers)
90
+ assert colors.count("rgb(240, 248, 255)") == 2
91
+
92
+
93
+ def test_simple_not_equal_rule_at_load(live_server, page, map):
94
+ map.settings["properties"]["rules"] = [
95
+ {"condition": "mytype!=even", "options": {"color": "aliceblue"}}
96
+ ]
97
+ map.save()
98
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
99
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
100
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
101
+ markers = page.locator(".leaflet-marker-icon .icon_container")
102
+ expect(markers).to_have_count(4)
103
+ colors = getColors(markers)
104
+ assert colors.count("rgb(240, 248, 255)") == 2
105
+
106
+
107
+ def test_gt_rule_with_number_at_load(live_server, page, map):
108
+ map.settings["properties"]["rules"] = [
109
+ {"condition": "mynumber>10", "options": {"color": "aliceblue"}}
110
+ ]
111
+ map.save()
112
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
113
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
114
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
115
+ markers = page.locator(".leaflet-marker-icon .icon_container")
116
+ expect(markers).to_have_count(4)
117
+ colors = getColors(markers)
118
+ assert colors.count("rgb(240, 248, 255)") == 2
119
+
120
+
121
+ def test_lt_rule_with_number_at_load(live_server, page, map):
122
+ map.settings["properties"]["rules"] = [
123
+ {"condition": "mynumber<14", "options": {"color": "aliceblue"}}
124
+ ]
125
+ map.save()
126
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
127
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
128
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
129
+ markers = page.locator(".leaflet-marker-icon .icon_container")
130
+ expect(markers).to_have_count(4)
131
+ colors = getColors(markers)
132
+ assert colors.count("rgb(240, 248, 255)") == 3
133
+
134
+
135
+ def test_lt_rule_with_float_at_load(live_server, page, map):
136
+ map.settings["properties"]["rules"] = [
137
+ {"condition": "mynumber<12.3", "options": {"color": "aliceblue"}}
138
+ ]
139
+ map.save()
140
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
141
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
142
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
143
+ markers = page.locator(".leaflet-marker-icon .icon_container")
144
+ expect(markers).to_have_count(4)
145
+ colors = getColors(markers)
146
+ assert colors.count("rgb(240, 248, 255)") == 3
147
+
148
+
149
+ def test_equal_rule_with_boolean_at_load(live_server, page, map):
150
+ map.settings["properties"]["rules"] = [
151
+ {"condition": "myboolean=true", "options": {"color": "aliceblue"}}
152
+ ]
153
+ map.save()
154
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
155
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
156
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
157
+ markers = page.locator(".leaflet-marker-icon .icon_container")
158
+ expect(markers).to_have_count(4)
159
+ colors = getColors(markers)
160
+ assert colors.count("rgb(240, 248, 255)") == 2
161
+
162
+
163
+ def test_can_create_new_rule(live_server, page, openmap):
164
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA1)
165
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA2)
166
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}#6/48.948/1.670")
167
+ markers = page.locator(".leaflet-marker-icon .icon_container")
168
+ expect(markers).to_have_count(4)
169
+ page.get_by_role("button", name="Edit").click()
170
+ page.get_by_role("link", name="Map advanced properties").click()
171
+ page.get_by_text("Conditional style rules").click()
172
+ page.get_by_role("button", name="Add rule").click()
173
+ page.locator("input[name=condition]").click()
174
+ page.locator("input[name=condition]").fill("mytype=odd")
175
+ page.locator(".umap-field-color .define").first.click()
176
+ page.get_by_title("AliceBlue").first.click()
177
+ colors = getColors(markers)
178
+ assert colors.count("rgb(240, 248, 255)") == 2
179
+
180
+
181
+ def test_can_deactive_rule_from_list(live_server, page, openmap):
182
+ openmap.settings["properties"]["rules"] = [
183
+ {"condition": "mytype=odd", "options": {"color": "aliceblue"}}
184
+ ]
185
+ openmap.save()
186
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA1)
187
+ DataLayerFactory(map=openmap, data=DATALAYER_DATA2)
188
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}#6/48.948/1.670")
189
+ markers = page.locator(".leaflet-marker-icon .icon_container")
190
+ expect(markers).to_have_count(4)
191
+ colors = getColors(markers)
192
+ assert colors.count("rgb(240, 248, 255)") == 2
193
+ page.get_by_role("button", name="Edit").click()
194
+ page.get_by_role("link", name="Map advanced properties").click()
195
+ page.get_by_text("Conditional style rules").click()
196
+ page.get_by_role("button", name="Show/hide layer").click()
197
+ colors = getColors(markers)
198
+ assert colors.count("rgb(240, 248, 255)") == 0
199
+ page.get_by_role("button", name="Show/hide layer").click()
200
+ colors = getColors(markers)
201
+ assert colors.count("rgb(240, 248, 255)") == 2
@@ -28,7 +28,7 @@ def test_owner_can_delete_map_after_confirmation(map, live_server, login):
28
28
  def test_dashboard_map_preview(map, live_server, datalayer, login):
29
29
  page = login(map.owner)
30
30
  page.goto(f"{live_server.url}/en/me")
31
- dialog = page.locator("dialog")
31
+ dialog = page.get_by_role("dialog")
32
32
  expect(dialog).to_be_hidden()
33
33
  button = page.get_by_role("button", name="Open preview")
34
34
  expect(button).to_be_visible()