umap-project 2.7.3__py3-none-any.whl → 2.8.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.
Files changed (292) hide show
  1. umap/__init__.py +1 -1
  2. umap/forms.py +4 -14
  3. umap/locale/am_ET/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/am_ET/LC_MESSAGES/django.po +278 -151
  5. umap/locale/ar/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/ar/LC_MESSAGES/django.po +335 -141
  7. umap/locale/bg/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/bg/LC_MESSAGES/django.po +279 -152
  9. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/br/LC_MESSAGES/django.po +95 -79
  11. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/ca/LC_MESSAGES/django.po +85 -68
  13. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/cs_CZ/LC_MESSAGES/django.po +78 -66
  15. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/da/LC_MESSAGES/django.po +280 -153
  17. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/de/LC_MESSAGES/django.po +80 -64
  19. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/el/LC_MESSAGES/django.po +82 -66
  21. umap/locale/en/LC_MESSAGES/django.po +73 -61
  22. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/es/LC_MESSAGES/django.po +75 -63
  24. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/et/LC_MESSAGES/django.po +280 -153
  26. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/eu/LC_MESSAGES/django.po +82 -66
  28. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/fa_IR/LC_MESSAGES/django.po +80 -64
  30. umap/locale/fi/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/fi/LC_MESSAGES/django.po +278 -151
  32. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/fr/LC_MESSAGES/django.po +75 -63
  34. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/gl/LC_MESSAGES/django.po +280 -153
  36. umap/locale/he/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/he/LC_MESSAGES/django.po +281 -154
  38. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/hu/LC_MESSAGES/django.po +80 -64
  40. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  41. umap/locale/is/LC_MESSAGES/django.po +280 -153
  42. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  43. umap/locale/it/LC_MESSAGES/django.po +82 -66
  44. umap/locale/ja/LC_MESSAGES/django.mo +0 -0
  45. umap/locale/ja/LC_MESSAGES/django.po +280 -153
  46. umap/locale/ko/LC_MESSAGES/django.mo +0 -0
  47. umap/locale/ko/LC_MESSAGES/django.po +280 -153
  48. umap/locale/lt/LC_MESSAGES/django.mo +0 -0
  49. umap/locale/lt/LC_MESSAGES/django.po +280 -153
  50. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  51. umap/locale/ms/LC_MESSAGES/django.po +82 -66
  52. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  53. umap/locale/nl/LC_MESSAGES/django.po +280 -153
  54. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  55. umap/locale/pl/LC_MESSAGES/django.po +82 -66
  56. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  57. umap/locale/pt/LC_MESSAGES/django.po +75 -63
  58. umap/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  59. umap/locale/pt_BR/LC_MESSAGES/django.po +280 -153
  60. umap/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  61. umap/locale/pt_PT/LC_MESSAGES/django.po +280 -153
  62. umap/locale/ru/LC_MESSAGES/django.mo +0 -0
  63. umap/locale/ru/LC_MESSAGES/django.po +280 -153
  64. umap/locale/sk_SK/LC_MESSAGES/django.mo +0 -0
  65. umap/locale/sk_SK/LC_MESSAGES/django.po +280 -153
  66. umap/locale/sl/LC_MESSAGES/django.mo +0 -0
  67. umap/locale/sl/LC_MESSAGES/django.po +280 -153
  68. umap/locale/sr/LC_MESSAGES/django.mo +0 -0
  69. umap/locale/sr/LC_MESSAGES/django.po +280 -153
  70. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  71. umap/locale/sv/LC_MESSAGES/django.po +81 -65
  72. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  73. umap/locale/th_TH/LC_MESSAGES/django.po +257 -185
  74. umap/locale/tr/LC_MESSAGES/django.mo +0 -0
  75. umap/locale/tr/LC_MESSAGES/django.po +280 -153
  76. umap/locale/uk_UA/LC_MESSAGES/django.mo +0 -0
  77. umap/locale/uk_UA/LC_MESSAGES/django.po +280 -153
  78. umap/locale/vi/LC_MESSAGES/django.mo +0 -0
  79. umap/locale/vi/LC_MESSAGES/django.po +278 -151
  80. umap/locale/zh/LC_MESSAGES/django.mo +0 -0
  81. umap/locale/zh/LC_MESSAGES/django.po +278 -151
  82. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  83. umap/locale/zh_TW/LC_MESSAGES/django.po +97 -81
  84. umap/management/commands/empty_trash.py +35 -0
  85. umap/management/commands/migrate_to_S3.py +29 -0
  86. umap/migrations/0023_alter_datalayer_uuid.py +19 -0
  87. umap/migrations/0024_alter_map_share_status.py +30 -0
  88. umap/migrations/0025_alter_datalayer_geojson.py +24 -0
  89. umap/models.py +68 -116
  90. umap/settings/base.py +23 -3
  91. umap/settings/local_s3.py +45 -0
  92. umap/static/umap/base.css +3 -603
  93. umap/static/umap/content.css +5 -3
  94. umap/static/umap/css/bar.css +202 -0
  95. umap/static/umap/css/form.css +620 -0
  96. umap/static/umap/css/icon.css +21 -1
  97. umap/static/umap/css/popup.css +125 -0
  98. umap/static/umap/img/16-white.svg +16 -4
  99. umap/static/umap/img/16.svg +1 -1
  100. umap/static/umap/img/source/16-white.svg +46 -45
  101. umap/static/umap/img/source/16.svg +1 -753
  102. umap/static/umap/js/components/fragment.js +3 -1
  103. umap/static/umap/js/modules/browser.js +20 -19
  104. umap/static/umap/js/modules/caption.js +21 -22
  105. umap/static/umap/js/modules/data/features.js +120 -78
  106. umap/static/umap/js/modules/data/layer.js +195 -153
  107. umap/static/umap/js/modules/facets.js +9 -9
  108. umap/static/umap/js/modules/formatter.js +5 -5
  109. umap/static/umap/js/modules/global.js +4 -52
  110. umap/static/umap/js/modules/help.js +18 -21
  111. umap/static/umap/js/modules/importer.js +133 -56
  112. umap/static/umap/js/modules/importers/cadastrefr.js +4 -0
  113. umap/static/umap/js/modules/importers/geodatamine.js +3 -3
  114. umap/static/umap/js/modules/importers/overpass.js +5 -0
  115. umap/static/umap/js/modules/permissions.js +85 -87
  116. umap/static/umap/js/modules/rendering/icon.js +2 -1
  117. umap/static/umap/js/modules/rendering/layers/base.js +15 -15
  118. umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
  119. umap/static/umap/js/modules/rendering/layers/cluster.js +1 -1
  120. umap/static/umap/js/modules/rendering/layers/heat.js +1 -1
  121. umap/static/umap/js/modules/rendering/map.js +390 -0
  122. umap/static/umap/js/modules/rendering/popup.js +19 -19
  123. umap/static/umap/js/modules/rendering/template.js +88 -21
  124. umap/static/umap/js/modules/rendering/ui.js +63 -14
  125. umap/static/umap/js/modules/request.js +2 -2
  126. umap/static/umap/js/modules/rules.js +22 -25
  127. umap/static/umap/js/modules/saving.js +47 -0
  128. umap/static/umap/js/modules/schema.js +6 -0
  129. umap/static/umap/js/modules/share.js +21 -24
  130. umap/static/umap/js/modules/slideshow.js +24 -20
  131. umap/static/umap/js/modules/sync/updaters.js +7 -9
  132. umap/static/umap/js/modules/tableeditor.js +20 -19
  133. umap/static/umap/js/modules/ui/bar.js +196 -0
  134. umap/static/umap/js/modules/ui/dialog.js +5 -0
  135. umap/static/umap/js/modules/ui/panel.js +10 -9
  136. umap/static/umap/js/modules/umap.js +1691 -0
  137. umap/static/umap/js/modules/urls.js +2 -2
  138. umap/static/umap/js/modules/utils.js +22 -6
  139. umap/static/umap/js/umap.controls.js +81 -305
  140. umap/static/umap/js/umap.core.js +29 -50
  141. umap/static/umap/js/umap.forms.js +78 -27
  142. umap/static/umap/keycloak.png +0 -0
  143. umap/static/umap/locale/am_ET.js +26 -10
  144. umap/static/umap/locale/am_ET.json +26 -10
  145. umap/static/umap/locale/ar.js +26 -10
  146. umap/static/umap/locale/ar.json +26 -10
  147. umap/static/umap/locale/ast.js +26 -10
  148. umap/static/umap/locale/ast.json +26 -10
  149. umap/static/umap/locale/bg.js +26 -10
  150. umap/static/umap/locale/bg.json +26 -10
  151. umap/static/umap/locale/br.js +27 -20
  152. umap/static/umap/locale/br.json +27 -20
  153. umap/static/umap/locale/ca.js +32 -29
  154. umap/static/umap/locale/ca.json +32 -29
  155. umap/static/umap/locale/cs_CZ.js +24 -17
  156. umap/static/umap/locale/cs_CZ.json +24 -17
  157. umap/static/umap/locale/da.js +26 -10
  158. umap/static/umap/locale/da.json +26 -10
  159. umap/static/umap/locale/de.js +21 -14
  160. umap/static/umap/locale/de.json +21 -14
  161. umap/static/umap/locale/el.js +28 -12
  162. umap/static/umap/locale/el.json +28 -12
  163. umap/static/umap/locale/en.js +14 -9
  164. umap/static/umap/locale/en.json +14 -9
  165. umap/static/umap/locale/en_US.json +26 -10
  166. umap/static/umap/locale/es.js +16 -13
  167. umap/static/umap/locale/es.json +16 -13
  168. umap/static/umap/locale/et.js +26 -10
  169. umap/static/umap/locale/et.json +26 -10
  170. umap/static/umap/locale/eu.js +16 -9
  171. umap/static/umap/locale/eu.json +16 -9
  172. umap/static/umap/locale/fa_IR.js +16 -9
  173. umap/static/umap/locale/fa_IR.json +16 -9
  174. umap/static/umap/locale/fi.js +26 -10
  175. umap/static/umap/locale/fi.json +26 -10
  176. umap/static/umap/locale/fr.js +14 -9
  177. umap/static/umap/locale/fr.json +14 -9
  178. umap/static/umap/locale/gl.js +26 -10
  179. umap/static/umap/locale/gl.json +26 -10
  180. umap/static/umap/locale/he.js +26 -10
  181. umap/static/umap/locale/he.json +26 -10
  182. umap/static/umap/locale/hr.js +26 -10
  183. umap/static/umap/locale/hr.json +26 -10
  184. umap/static/umap/locale/hu.js +16 -9
  185. umap/static/umap/locale/hu.json +16 -9
  186. umap/static/umap/locale/id.js +26 -10
  187. umap/static/umap/locale/id.json +26 -10
  188. umap/static/umap/locale/is.js +26 -10
  189. umap/static/umap/locale/is.json +26 -10
  190. umap/static/umap/locale/it.js +26 -10
  191. umap/static/umap/locale/it.json +26 -10
  192. umap/static/umap/locale/ja.js +26 -10
  193. umap/static/umap/locale/ja.json +26 -10
  194. umap/static/umap/locale/ko.js +26 -10
  195. umap/static/umap/locale/ko.json +26 -10
  196. umap/static/umap/locale/lt.js +26 -10
  197. umap/static/umap/locale/lt.json +26 -10
  198. umap/static/umap/locale/ms.js +28 -12
  199. umap/static/umap/locale/ms.json +28 -12
  200. umap/static/umap/locale/nl.js +28 -12
  201. umap/static/umap/locale/nl.json +28 -12
  202. umap/static/umap/locale/no.js +26 -10
  203. umap/static/umap/locale/no.json +26 -10
  204. umap/static/umap/locale/pl.js +28 -12
  205. umap/static/umap/locale/pl.json +28 -12
  206. umap/static/umap/locale/pl_PL.json +26 -10
  207. umap/static/umap/locale/pt.js +16 -9
  208. umap/static/umap/locale/pt.json +16 -9
  209. umap/static/umap/locale/pt_BR.js +26 -10
  210. umap/static/umap/locale/pt_BR.json +26 -10
  211. umap/static/umap/locale/pt_PT.js +16 -9
  212. umap/static/umap/locale/pt_PT.json +16 -9
  213. umap/static/umap/locale/ro.js +26 -10
  214. umap/static/umap/locale/ro.json +26 -10
  215. umap/static/umap/locale/ru.js +26 -10
  216. umap/static/umap/locale/ru.json +26 -10
  217. umap/static/umap/locale/si.js +7 -7
  218. umap/static/umap/locale/si.json +7 -7
  219. umap/static/umap/locale/sk_SK.js +26 -10
  220. umap/static/umap/locale/sk_SK.json +26 -10
  221. umap/static/umap/locale/sl.js +26 -10
  222. umap/static/umap/locale/sl.json +26 -10
  223. umap/static/umap/locale/sr.js +26 -10
  224. umap/static/umap/locale/sr.json +26 -10
  225. umap/static/umap/locale/sv.js +27 -11
  226. umap/static/umap/locale/sv.json +27 -11
  227. umap/static/umap/locale/th_TH.js +28 -12
  228. umap/static/umap/locale/th_TH.json +28 -12
  229. umap/static/umap/locale/tr.js +26 -10
  230. umap/static/umap/locale/tr.json +26 -10
  231. umap/static/umap/locale/uk_UA.js +26 -10
  232. umap/static/umap/locale/uk_UA.json +26 -10
  233. umap/static/umap/locale/vi.js +26 -10
  234. umap/static/umap/locale/vi.json +26 -10
  235. umap/static/umap/locale/vi_VN.json +26 -10
  236. umap/static/umap/locale/zh.js +26 -10
  237. umap/static/umap/locale/zh.json +26 -10
  238. umap/static/umap/locale/zh_CN.json +26 -10
  239. umap/static/umap/locale/zh_TW.Big5.json +26 -10
  240. umap/static/umap/locale/zh_TW.js +34 -27
  241. umap/static/umap/locale/zh_TW.json +34 -27
  242. umap/static/umap/map.css +39 -530
  243. umap/static/umap/unittests/URLs.js +15 -15
  244. umap/static/umap/unittests/utils.js +23 -1
  245. umap/static/umap/vars.css +2 -1
  246. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +5 -1
  247. umap/storage/__init__.py +3 -0
  248. umap/storage/fs.py +101 -0
  249. umap/storage/s3.py +61 -0
  250. umap/templates/base.html +2 -0
  251. umap/templates/registration/login.html +7 -6
  252. umap/templates/umap/components/alerts/alert.html +4 -0
  253. umap/templates/umap/css.html +6 -0
  254. umap/templates/umap/js.html +3 -2
  255. umap/templates/umap/map_init.html +6 -5
  256. umap/templates/umap/user_dashboard.html +20 -19
  257. umap/tests/base.py +5 -1
  258. umap/tests/fixtures/test_upload_simple_marker.json +19 -0
  259. umap/tests/integration/conftest.py +2 -1
  260. umap/tests/integration/test_anonymous_owned_map.py +18 -10
  261. umap/tests/integration/test_browser.py +16 -1
  262. umap/tests/integration/test_dashboard.py +1 -1
  263. umap/tests/integration/test_edit_datalayer.py +29 -7
  264. umap/tests/integration/test_import.py +28 -6
  265. umap/tests/integration/test_optimistic_merge.py +31 -8
  266. umap/tests/integration/test_owned_map.py +22 -16
  267. umap/tests/integration/test_popup.py +44 -0
  268. umap/tests/integration/test_save.py +35 -0
  269. umap/tests/integration/test_view_marker.py +12 -0
  270. umap/tests/integration/test_view_polyline.py +257 -0
  271. umap/tests/integration/test_websocket_sync.py +81 -9
  272. umap/tests/test_dashboard.py +82 -0
  273. umap/tests/test_datalayer.py +6 -7
  274. umap/tests/test_datalayer_s3.py +135 -0
  275. umap/tests/test_datalayer_views.py +28 -10
  276. umap/tests/test_empty_trash.py +34 -0
  277. umap/tests/test_map.py +12 -3
  278. umap/tests/test_map_views.py +69 -37
  279. umap/tests/test_statics.py +1 -1
  280. umap/tests/test_team_views.py +35 -1
  281. umap/tests/test_views.py +31 -52
  282. umap/urls.py +3 -3
  283. umap/views.py +126 -90
  284. {umap_project-2.7.3.dist-info → umap_project-2.8.0.dist-info}/METADATA +16 -13
  285. {umap_project-2.7.3.dist-info → umap_project-2.8.0.dist-info}/RECORD +289 -269
  286. umap/management/commands/purge_purgatory.py +0 -28
  287. umap/static/umap/js/umap.js +0 -1903
  288. umap/tests/test_purge_purgatory.py +0 -25
  289. /umap/{storage.py → storage/staticfiles.py} +0 -0
  290. {umap_project-2.7.3.dist-info → umap_project-2.8.0.dist-info}/WHEEL +0 -0
  291. {umap_project-2.7.3.dist-info → umap_project-2.8.0.dist-info}/entry_points.txt +0 -0
  292. {umap_project-2.7.3.dist-info → umap_project-2.8.0.dist-info}/licenses/LICENSE +0 -0
umap/views.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import io
2
2
  import json
3
3
  import mimetypes
4
- import os
5
4
  import re
6
5
  import socket
7
6
  import zipfile
@@ -318,7 +317,11 @@ class TeamMaps(PaginatorMixin, DetailView):
318
317
  context_object_name = "current_team"
319
318
 
320
319
  def get_maps(self):
321
- return Map.public.filter(team=self.object).order_by("-modified_at")
320
+ qs = Map.public
321
+ user = self.request.user
322
+ if user.is_authenticated and user in self.object.users.all():
323
+ qs = Map.objects
324
+ return qs.filter(team=self.object).order_by("-modified_at")
322
325
 
323
326
  def get_context_data(self, **kwargs):
324
327
  kwargs.update(
@@ -373,6 +376,7 @@ class UserDashboard(PaginatorMixin, DetailView, SearchMixin):
373
376
 
374
377
  def get_maps(self):
375
378
  qs = self.get_search_queryset() or Map.objects.all()
379
+ qs = qs.exclude(share_status__in=[Map.DELETED, Map.BLOCKED])
376
380
  teams = self.object.teams.all()
377
381
  qs = (
378
382
  qs.filter(owner=self.object)
@@ -452,27 +456,27 @@ showcase = MapsShowCase.as_view()
452
456
 
453
457
 
454
458
  def validate_url(request):
455
- assert request.method == "GET"
459
+ assert request.method == "GET", "Wrong HTTP method"
456
460
  url = request.GET.get("url")
457
- assert url
461
+ assert url, "Missing URL"
458
462
  try:
459
463
  URLValidator(url)
460
- except ValidationError:
461
- raise AssertionError()
462
- assert "HTTP_REFERER" in request.META
464
+ except ValidationError as err:
465
+ raise AssertionError(err)
466
+ assert "HTTP_REFERER" in request.META, "Missing HTTP_REFERER"
463
467
  referer = urlparse(request.META.get("HTTP_REFERER"))
464
468
  toproxy = urlparse(url)
465
469
  local = urlparse(settings.SITE_URL)
466
- assert toproxy.hostname
467
- assert referer.hostname == local.hostname
468
- assert toproxy.hostname != "localhost"
469
- assert toproxy.netloc != local.netloc
470
+ assert toproxy.hostname, "No hostname"
471
+ assert referer.hostname == local.hostname, f"{referer.hostname} != {local.hostname}"
472
+ assert toproxy.hostname != "localhost", "Invalid localhost target"
473
+ assert toproxy.netloc != local.netloc, "Invalid netloc"
470
474
  try:
471
475
  # clean this when in python 3.4
472
476
  ipaddress = socket.gethostbyname(toproxy.hostname)
473
- except:
474
- raise AssertionError()
475
- assert not PRIVATE_IP.match(ipaddress)
477
+ except Exception as err:
478
+ raise AssertionError(err)
479
+ assert not PRIVATE_IP.match(ipaddress), "Private IP"
476
480
  return url
477
481
 
478
482
 
@@ -480,7 +484,8 @@ class AjaxProxy(View):
480
484
  def get(self, *args, **kwargs):
481
485
  try:
482
486
  url = validate_url(self.request)
483
- except AssertionError:
487
+ except AssertionError as err:
488
+ print(f"AjaxProxy: {err}")
484
489
  return HttpResponseBadRequest()
485
490
  try:
486
491
  ttl = int(self.request.GET.get("ttl"))
@@ -598,29 +603,34 @@ class MapDetailMixin(SessionMixin):
598
603
  "tilelayers": TileLayer.get_list(),
599
604
  "editMode": self.edit_mode,
600
605
  "schema": Map.extra_schema,
601
- "umap_id": self.get_umap_id(),
606
+ "id": self.get_id(),
602
607
  "starred": self.is_starred(),
603
608
  "licences": dict((l.name, l.json) for l in Licence.objects.all()),
604
- "share_statuses": [
605
- (i, str(label)) for i, label in Map.SHARE_STATUS if i != Map.BLOCKED
606
- ],
607
609
  "umap_version": VERSION,
608
610
  "featuresHaveOwner": settings.UMAP_DEFAULT_FEATURES_HAVE_OWNERS,
609
611
  "websocketEnabled": settings.WEBSOCKET_ENABLED,
610
612
  "websocketURI": settings.WEBSOCKET_FRONT_URI,
611
613
  "importers": settings.UMAP_IMPORTERS,
614
+ "defaultLabelKeys": settings.UMAP_LABEL_KEYS,
612
615
  }
613
616
  created = bool(getattr(self, "object", None))
614
617
  if (created and self.object.owner) or (not created and not user.is_anonymous):
615
- map_statuses = Map.EDIT_STATUS
618
+ edit_statuses = Map.EDIT_STATUS
616
619
  datalayer_statuses = DataLayer.EDIT_STATUS
620
+ share_statuses = Map.SHARE_STATUS
617
621
  else:
618
- map_statuses = AnonymousMapPermissionsForm.STATUS
619
- datalayer_statuses = AnonymousDataLayerPermissionsForm.STATUS
620
- properties["edit_statuses"] = [(i, str(label)) for i, label in map_statuses]
622
+ edit_statuses = Map.ANONYMOUS_EDIT_STATUS
623
+ datalayer_statuses = DataLayer.ANONYMOUS_EDIT_STATUS
624
+ share_statuses = Map.ANONYMOUS_SHARE_STATUS
625
+ properties["edit_statuses"] = [(i, str(label)) for i, label in edit_statuses]
621
626
  properties["datalayer_edit_statuses"] = [
622
627
  (i, str(label)) for i, label in datalayer_statuses
623
628
  ]
629
+ properties["share_statuses"] = [
630
+ (i, str(label))
631
+ for i, label in share_statuses
632
+ if i not in [Map.BLOCKED, Map.DELETED]
633
+ ]
624
634
  if self.get_short_url():
625
635
  properties["shortUrl"] = self.get_short_url()
626
636
 
@@ -655,7 +665,7 @@ class MapDetailMixin(SessionMixin):
655
665
  def edit_mode(self):
656
666
  return "advanced"
657
667
 
658
- def get_umap_id(self):
668
+ def get_id(self):
659
669
  return None
660
670
 
661
671
  def is_starred(self):
@@ -683,14 +693,9 @@ class PermissionsMixin:
683
693
  permissions["edit_status"] = self.object.edit_status
684
694
  permissions["share_status"] = self.object.share_status
685
695
  if self.object.owner:
686
- permissions["owner"] = {
687
- "id": self.object.owner.pk,
688
- "name": str(self.object.owner),
689
- "url": self.object.owner.get_url(),
690
- }
696
+ permissions["owner"] = self.object.owner.get_metadata()
691
697
  permissions["editors"] = [
692
- {"id": editor.pk, "name": str(editor)}
693
- for editor in self.object.editors.all()
698
+ editor.get_metadata() for editor in self.object.editors.all()
694
699
  ]
695
700
  if self.object.team:
696
701
  permissions["team"] = self.object.team.get_metadata()
@@ -725,6 +730,8 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
725
730
  return self.object.get_absolute_url()
726
731
 
727
732
  def get_datalayers(self):
733
+ # When initializing datalayers from map, we cannot get the reference version
734
+ # the normal way, which is from the header X-Reference-Version
728
735
  return [dl.metadata(self.request) for dl in self.object.datalayer_set.all()]
729
736
 
730
737
  @property
@@ -736,7 +743,7 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
736
743
  edit_mode = "simple"
737
744
  return edit_mode
738
745
 
739
- def get_umap_id(self):
746
+ def get_id(self):
740
747
  return self.object.pk
741
748
 
742
749
  def get_short_url(self):
@@ -844,6 +851,17 @@ class MapViewGeoJSON(MapView):
844
851
  class MapNew(MapDetailMixin, TemplateView):
845
852
  template_name = "umap/map_detail.html"
846
853
 
854
+ def get_map_properties(self):
855
+ properties = super().get_map_properties()
856
+ properties["permissions"] = {
857
+ "edit_status": Map.edit_status.field.default(),
858
+ "share_status": Map.share_status.field.default(),
859
+ }
860
+ if self.request.user.is_authenticated:
861
+ user = self.request.user
862
+ properties["permissions"]["owner"] = user.get_metadata()
863
+ return properties
864
+
847
865
 
848
866
  class MapPreview(MapDetailMixin, TemplateView):
849
867
  template_name = "umap/map_detail.html"
@@ -1008,7 +1026,7 @@ class MapDelete(DeleteView):
1008
1026
  self.object = self.get_object()
1009
1027
  if not self.object.can_delete(self.request):
1010
1028
  return HttpResponseForbidden(_("Only its owner can delete the map."))
1011
- self.object.delete()
1029
+ self.object.move_to_trash()
1012
1030
  home_url = reverse("home")
1013
1031
  messages.info(self.request, _("Map successfully deleted."))
1014
1032
  if is_ajax(self.request):
@@ -1103,97 +1121,117 @@ class MapAnonymousEditUrl(RedirectView):
1103
1121
  # ############## #
1104
1122
 
1105
1123
 
1106
- class GZipMixin(object):
1107
- EXT = ".gz"
1124
+ class DataLayerView(BaseDetailView):
1125
+ model = DataLayer
1108
1126
 
1109
1127
  @property
1110
- def path(self):
1111
- return Path(self.object.geojson.path)
1128
+ def accepts_gzip(self):
1129
+ return settings.UMAP_GZIP and re_accepts_gzip.search(
1130
+ self.request.META.get("HTTP_ACCEPT_ENCODING", "")
1131
+ )
1112
1132
 
1113
1133
  @property
1114
- def gzip_path(self):
1115
- return Path(f"{self.path}{self.EXT}")
1116
-
1117
- def read_version(self, path):
1118
- # Remove optional .gz, then .geojson, then return the trailing version from path.
1119
- return str(path.with_suffix("").with_suffix("")).split("_")[-1]
1134
+ def is_s3(self):
1135
+ return "S3" in settings.STORAGES["data"]["BACKEND"]
1120
1136
 
1121
1137
  @property
1122
- def version(self):
1123
- # Prior to 1.3.0 we did not set gzip mtime as geojson mtime,
1124
- # but we switched from If-Match header to If-Unmodified-Since
1125
- # and when users accepts gzip their last modified value is the gzip
1126
- # (when umap is served by nginx and X-Accel-Redirect)
1127
- # one, so we need to compare with that value in that case.
1128
- # cf https://github.com/umap-project/umap/issues/1212
1129
- path = (
1130
- self.gzip_path
1131
- if self.accepts_gzip and self.gzip_path.exists()
1132
- else self.path
1133
- )
1134
- return self.read_version(path)
1138
+ def filepath(self):
1139
+ return Path(self.object.geojson.path)
1135
1140
 
1136
1141
  @property
1137
- def accepts_gzip(self):
1138
- return settings.UMAP_GZIP and re_accepts_gzip.search(
1139
- self.request.META.get("HTTP_ACCEPT_ENCODING", "")
1140
- )
1142
+ def fileurl(self):
1143
+ return self.object.geojson.url
1141
1144
 
1145
+ @property
1146
+ def filedata(self):
1147
+ with self.object.geojson.open("rb") as f:
1148
+ return f.read()
1142
1149
 
1143
- class DataLayerView(GZipMixin, BaseDetailView):
1144
- model = DataLayer
1150
+ @property
1151
+ def fileversion(self):
1152
+ return self.object.reference_version
1145
1153
 
1146
1154
  def render_to_response(self, context, **response_kwargs):
1147
1155
  response = None
1148
- path = self.path
1149
1156
  # Generate gzip if needed
1150
- if self.accepts_gzip:
1151
- if not self.gzip_path.exists():
1152
- gzip_file(path, self.gzip_path)
1157
+ if not self.is_s3 and self.accepts_gzip:
1158
+ gzip_path = Path(f"{self.filepath}.gz")
1159
+ if not gzip_path.exists():
1160
+ gzip_file(self.filepath, gzip_path)
1153
1161
 
1154
1162
  if getattr(settings, "UMAP_XSENDFILE_HEADER", None):
1155
1163
  response = HttpResponse()
1156
- internal_path = str(path).replace(settings.MEDIA_ROOT, "/internal")
1164
+ if self.is_s3:
1165
+ internal_path = f"/s3/{self.fileurl}"
1166
+ else:
1167
+ internal_path = str(self.filepath).replace(
1168
+ settings.MEDIA_ROOT, "/internal"
1169
+ )
1157
1170
  response[settings.UMAP_XSENDFILE_HEADER] = internal_path
1158
1171
  else:
1159
1172
  # Do not use in production
1160
1173
  # (no gzip/cache-control/If-Modified-Since/If-None-Match)
1161
- statobj = os.stat(path)
1162
- with open(path, "rb") as f:
1163
- # Should not be used in production!
1164
- response = HttpResponse(f.read(), content_type="application/geo+json")
1165
- response["X-Datalayer-Version"] = self.version
1166
- response["Content-Length"] = statobj.st_size
1174
+ data = self.filedata
1175
+ response = HttpResponse(data, content_type="application/geo+json")
1176
+ response["X-Datalayer-Version"] = self.fileversion
1167
1177
  return response
1168
1178
 
1169
1179
 
1170
1180
  class DataLayerVersion(DataLayerView):
1171
1181
  @property
1172
- def path(self):
1173
- return Path(settings.MEDIA_ROOT) / self.object.get_version_path(
1174
- self.kwargs["name"]
1175
- )
1182
+ def filepath(self):
1183
+ try:
1184
+ return Path(settings.MEDIA_ROOT) / self.object.get_version_path(
1185
+ self.kwargs["ref"]
1186
+ )
1187
+ except ValueError:
1188
+ raise Http404("Invalid version reference")
1176
1189
 
1190
+ @property
1191
+ def fileurl(self):
1192
+ return self.object.get_version_path(self.kwargs["ref"])
1177
1193
 
1178
- class DataLayerCreate(FormLessEditMixin, GZipMixin, CreateView):
1194
+ @property
1195
+ def filedata(self):
1196
+ try:
1197
+ return self.object.get_version(self.kwargs["ref"])
1198
+ except ValueError:
1199
+ raise Http404("Invalid version reference.")
1200
+
1201
+ @property
1202
+ def fileversion(self):
1203
+ return self.kwargs["ref"]
1204
+
1205
+
1206
+ class DataLayerCreate(FormLessEditMixin, CreateView):
1179
1207
  model = DataLayer
1180
1208
  form_class = DataLayerForm
1181
1209
 
1182
1210
  def form_valid(self, form):
1183
1211
  form.instance.map = self.kwargs["map_inst"]
1212
+
1213
+ uuid = self.kwargs["pk"]
1214
+ # Check if UUID already exists
1215
+ if DataLayer.objects.filter(uuid=uuid).exists():
1216
+ return HttpResponseBadRequest("UUID already exists")
1217
+
1218
+ form.instance.uuid = uuid
1184
1219
  self.object = form.save()
1185
- # Simple response with only metadata (including new id)
1186
- response = simple_json_response(**self.object.metadata(self.request))
1187
- response["X-Datalayer-Version"] = self.version
1220
+ assert uuid == self.object.uuid
1221
+
1222
+ # Simple response with only metadata
1223
+ data = self.object.metadata(self.request)
1224
+ response = simple_json_response(**data)
1225
+ response["X-Datalayer-Version"] = self.object.reference_version
1188
1226
  return response
1189
1227
 
1190
1228
 
1191
- class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
1229
+ class DataLayerUpdate(FormLessEditMixin, UpdateView):
1192
1230
  model = DataLayer
1193
1231
  form_class = DataLayerForm
1194
1232
 
1195
1233
  def has_changes_since(self, incoming_version):
1196
- return incoming_version and self.version != incoming_version
1234
+ return incoming_version and self.object.reference_version != incoming_version
1197
1235
 
1198
1236
  def merge(self, reference_version):
1199
1237
  """
@@ -1205,11 +1243,9 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
1205
1243
 
1206
1244
  # Use the provided info to find the correct version in our storage.
1207
1245
  for version in self.object.versions:
1208
- name = version["name"]
1209
- path = Path(settings.MEDIA_ROOT) / self.object.get_version_path(name)
1210
- if reference_version == self.read_version(path):
1211
- with open(path) as f:
1212
- reference = json.loads(f.read())
1246
+ ref = version["ref"]
1247
+ if reference_version == ref:
1248
+ reference = json.loads(self.object.get_version(ref))
1213
1249
  break
1214
1250
  else:
1215
1251
  # If the reference document is not found, we can't merge.
@@ -1218,7 +1254,7 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
1218
1254
  incoming = json.loads(self.request.FILES["geojson"].read())
1219
1255
 
1220
1256
  # Latest known version of the data.
1221
- with open(self.path) as f:
1257
+ with self.object.geojson.open() as f:
1222
1258
  latest = json.loads(f.read())
1223
1259
 
1224
1260
  try:
@@ -1262,7 +1298,7 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
1262
1298
  data["geojson"] = json.loads(self.object.geojson.read().decode())
1263
1299
  self.request.session["needs_reload"] = False
1264
1300
  response = simple_json_response(**data)
1265
- response["X-Datalayer-Version"] = self.version
1301
+ response["X-Datalayer-Version"] = self.object.reference_version
1266
1302
  return response
1267
1303
 
1268
1304
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: umap-project
3
- Version: 2.7.3
3
+ Version: 2.8.0
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>
@@ -18,39 +18,42 @@ Requires-Python: >=3.10
18
18
  Requires-Dist: django-agnocomplete==2.2.0
19
19
  Requires-Dist: django-environ==0.11.2
20
20
  Requires-Dist: django-probes==1.7.0
21
- Requires-Dist: django==5.1.2
21
+ Requires-Dist: django==5.1.4
22
22
  Requires-Dist: pillow==11.0.0
23
23
  Requires-Dist: psycopg==3.2.3
24
- Requires-Dist: rcssmin==1.1.3
24
+ Requires-Dist: rcssmin==1.2.0
25
25
  Requires-Dist: requests==2.32.3
26
26
  Requires-Dist: rjsmin==1.2.3
27
27
  Requires-Dist: social-auth-app-django==5.4.2
28
28
  Requires-Dist: social-auth-core==4.5.4
29
29
  Provides-Extra: dev
30
- Requires-Dist: djlint==1.35.2; extra == 'dev'
30
+ Requires-Dist: djlint==1.36.3; extra == 'dev'
31
31
  Requires-Dist: hatch==1.13.0; extra == 'dev'
32
32
  Requires-Dist: isort==5.13.2; extra == 'dev'
33
- Requires-Dist: mkdocs-material==9.5.42; extra == 'dev'
33
+ Requires-Dist: mkdocs-material==9.5.48; extra == 'dev'
34
34
  Requires-Dist: mkdocs-static-i18n==1.2.3; extra == 'dev'
35
35
  Requires-Dist: mkdocs==1.6.1; extra == 'dev'
36
- Requires-Dist: pymdown-extensions==10.11.2; extra == 'dev'
37
- Requires-Dist: ruff==0.7.0; extra == 'dev'
36
+ Requires-Dist: pymdown-extensions==10.12; extra == 'dev'
37
+ Requires-Dist: ruff==0.8.2; extra == 'dev'
38
38
  Requires-Dist: vermin==1.6.0; extra == 'dev'
39
39
  Provides-Extra: docker
40
- Requires-Dist: uwsgi==2.0.27; extra == 'docker'
40
+ Requires-Dist: uwsgi==2.0.28; extra == 'docker'
41
+ Provides-Extra: s3
42
+ Requires-Dist: django-storages[s3]==1.14.4; extra == 's3'
41
43
  Provides-Extra: sync
42
- Requires-Dist: channels==4.1.0; extra == 'sync'
44
+ Requires-Dist: channels==4.2.0; extra == 'sync'
43
45
  Requires-Dist: daphne==4.1.2; extra == 'sync'
44
- Requires-Dist: pydantic==2.9.2; extra == 'sync'
46
+ Requires-Dist: pydantic==2.10.3; extra == 'sync'
45
47
  Requires-Dist: websockets==13.1; extra == 'sync'
46
48
  Provides-Extra: test
47
49
  Requires-Dist: factory-boy==3.3.1; extra == 'test'
50
+ Requires-Dist: moto[s3]==5.0.21; extra == 'test'
48
51
  Requires-Dist: playwright>=1.39; extra == 'test'
49
52
  Requires-Dist: pytest-django==4.9.0; extra == 'test'
50
- Requires-Dist: pytest-playwright==0.5.2; extra == 'test'
51
- Requires-Dist: pytest-rerunfailures==14.0; extra == 'test'
53
+ Requires-Dist: pytest-playwright==0.6.2; extra == 'test'
54
+ Requires-Dist: pytest-rerunfailures==15.0; extra == 'test'
52
55
  Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
53
- Requires-Dist: pytest==8.3.3; extra == 'test'
56
+ Requires-Dist: pytest==8.3.4; extra == 'test'
54
57
  Description-Content-Type: text/markdown
55
58
 
56
59
  # uMap project