umap-project 2.8.0a0__py3-none-any.whl → 2.8.0a2__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.

umap/__init__.py CHANGED
@@ -1 +1 @@
1
- VERSION = "2.8.0a0"
1
+ VERSION = "2.8.0a2"
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2024-12-11 17:05+0000\n"
11
+ "POT-Creation-Date: 2024-12-13 08:26+0000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -617,57 +617,57 @@ msgstr ""
617
617
  msgid "View the map"
618
618
  msgstr ""
619
619
 
620
- #: views.py:820
620
+ #: views.py:821
621
621
  msgid "See full screen"
622
622
  msgstr ""
623
623
 
624
- #: views.py:963
624
+ #: views.py:964
625
625
  msgid "Map editors updated with success!"
626
626
  msgstr ""
627
627
 
628
- #: views.py:999
628
+ #: views.py:1000
629
629
  #, python-format
630
630
  msgid "The uMap edit link for your map: %(map_name)s"
631
631
  msgstr ""
632
632
 
633
- #: views.py:1002
633
+ #: views.py:1003
634
634
  #, python-format
635
635
  msgid "Here is your secret edit link: %(link)s"
636
636
  msgstr ""
637
637
 
638
- #: views.py:1009
638
+ #: views.py:1010
639
639
  #, python-format
640
640
  msgid "Can't send email to %(email)s"
641
641
  msgstr ""
642
642
 
643
- #: views.py:1012
643
+ #: views.py:1013
644
644
  #, python-format
645
645
  msgid "Email sent to %(email)s"
646
646
  msgstr ""
647
647
 
648
- #: views.py:1023
648
+ #: views.py:1024
649
649
  msgid "Only its owner can delete the map."
650
650
  msgstr ""
651
651
 
652
- #: views.py:1026
652
+ #: views.py:1027
653
653
  msgid "Map successfully deleted."
654
654
  msgstr ""
655
655
 
656
- #: views.py:1052
656
+ #: views.py:1053
657
657
  #, python-format
658
658
  msgid ""
659
659
  "Your map has been cloned! If you want to edit this map from another "
660
660
  "computer, please use this link: %(anonymous_url)s"
661
661
  msgstr ""
662
662
 
663
- #: views.py:1057
663
+ #: views.py:1058
664
664
  msgid "Congratulations, your map has been cloned!"
665
665
  msgstr ""
666
666
 
667
- #: views.py:1308
667
+ #: views.py:1309
668
668
  msgid "Layer successfully deleted."
669
669
  msgstr ""
670
670
 
671
- #: views.py:1330
671
+ #: views.py:1331
672
672
  msgid "Permissions updated with success!"
673
673
  msgstr ""
@@ -23,10 +23,13 @@ class Command(BaseCommand):
23
23
 
24
24
  def handle(self, *args, **options):
25
25
  days = options["days"]
26
- since = datetime.utcnow() - timedelta(days=days)
26
+ since = (datetime.utcnow() - timedelta(days=days)).date()
27
27
  print(f"Deleting map in trash since {since}")
28
28
  maps = Map.objects.filter(share_status=Map.DELETED, modified_at__lt=since)
29
29
  for map in maps:
30
+ map_id = map.id
31
+ map_name = map.name
32
+ trashed_at = map.modified_at.date()
30
33
  if not options["dry_run"]:
31
34
  map.delete()
32
- print(f"Deleted map {map.name} ({map.id}), trashed on {map.modified_at}")
35
+ print(f"Deleted map {map_name} ({map_id}), trashed at {trashed_at}")
@@ -2,7 +2,7 @@ from django.conf import settings
2
2
  from django.core.management.base import BaseCommand
3
3
 
4
4
  from umap.models import DataLayer
5
- from umap.storage import UmapFileSystem
5
+ from umap.storage.fs import FSDataStorage
6
6
 
7
7
 
8
8
  class Command(BaseCommand):
@@ -11,9 +11,9 @@ class Command(BaseCommand):
11
11
  def handle(self, *args, **options):
12
12
  assert settings.UMAP_READONLY, "You must run that script with a read-only uMap."
13
13
  assert (
14
- settings.STORAGES["data"]["BACKEND"] == "umap.storage.UmapS3"
14
+ settings.STORAGES["data"]["BACKEND"] == "umap.storage.s3.S3DataStorage"
15
15
  ), "You must configure your storages to point to S3"
16
- fs_storage = UmapFileSystem()
16
+ fs_storage = FSDataStorage()
17
17
  for datalayer in DataLayer.objects.all():
18
18
  geojson_fs_path = str(datalayer.geojson)
19
19
  try:
umap/settings/base.py CHANGED
@@ -176,10 +176,10 @@ STORAGES = {
176
176
  "BACKEND": "django.core.files.storage.FileSystemStorage",
177
177
  },
178
178
  "data": {
179
- "BACKEND": "umap.storage.UmapFileSystem",
179
+ "BACKEND": "umap.storage.fs.FSDataStorage",
180
180
  },
181
181
  "staticfiles": {
182
- "BACKEND": "umap.storage.UmapManifestStaticFilesStorage",
182
+ "BACKEND": "umap.storage.staticfiles.UmapManifestStaticFilesStorage",
183
183
  },
184
184
  }
185
185
  # Add application/json and application/geo+json to default django-storages setting
@@ -252,10 +252,11 @@ export class DataLayer extends ServerStored {
252
252
  }
253
253
 
254
254
  fromGeoJSON(geojson, sync = true) {
255
- this.addData(geojson, sync)
255
+ const features = this.addData(geojson, sync)
256
256
  this._geojson = geojson
257
257
  this.onDataLoaded()
258
258
  this.dataChanged()
259
+ return features
259
260
  }
260
261
 
261
262
  onDataLoaded() {
@@ -315,7 +316,7 @@ export class DataLayer extends ServerStored {
315
316
  const response = await this._umap.request.get(url)
316
317
  if (response?.ok) {
317
318
  this.clear()
318
- this._umap.formatter
319
+ return this._umap.formatter
319
320
  .parse(await response.text(), this.options.remoteData.format)
320
321
  .then((geojson) => this.fromGeoJSON(geojson))
321
322
  }
@@ -443,10 +444,11 @@ export class DataLayer extends ServerStored {
443
444
  try {
444
445
  // Do not fail if remote data is somehow invalid,
445
446
  // otherwise the layer becomes uneditable.
446
- this.makeFeatures(geojson, sync)
447
+ return this.makeFeatures(geojson, sync)
447
448
  } catch (err) {
448
449
  console.log('Error with DataLayer', this.id)
449
450
  console.error(err)
451
+ return []
450
452
  }
451
453
  }
452
454
 
@@ -463,10 +465,13 @@ export class DataLayer extends ServerStored {
463
465
  ? geojson
464
466
  : geojson.features || geojson.geometries
465
467
  if (!collection) return
468
+ const features = []
466
469
  this.sortFeatures(collection)
467
- for (const feature of collection) {
468
- this.makeFeature(feature, sync)
470
+ for (const featureJson of collection) {
471
+ const feature = this.makeFeature(featureJson, sync)
472
+ if (feature) features.push(feature)
469
473
  }
474
+ return features
470
475
  }
471
476
 
472
477
  makeFeature(geojson = {}, sync = true, id = null) {
@@ -503,31 +508,47 @@ export class DataLayer extends ServerStored {
503
508
  }
504
509
 
505
510
  async importRaw(raw, format) {
506
- this._umap.formatter
511
+ return this._umap.formatter
507
512
  .parse(raw, format)
508
513
  .then((geojson) => this.addData(geojson))
509
- .then(() => this.zoomTo())
510
- this.isDirty = true
514
+ .then((data) => {
515
+ if (data?.length) this.isDirty = true
516
+ return data
517
+ })
511
518
  }
512
519
 
513
- importFromFiles(files, type) {
514
- for (const f of files) {
515
- this.importFromFile(f, type)
520
+ readFile(f) {
521
+ return new Promise((resolve) => {
522
+ const reader = new FileReader()
523
+ reader.onloadend = () => resolve(reader.result)
524
+ reader.readAsText(f)
525
+ })
526
+ }
527
+
528
+ async importFromFiles(files, type) {
529
+ let all = []
530
+ for (const file of files) {
531
+ const features = await this.importFromFile(file, type)
532
+ if (features) {
533
+ all = all.concat(features)
534
+ }
516
535
  }
536
+ return new Promise((resolve) => {
537
+ resolve(all)
538
+ })
517
539
  }
518
540
 
519
- importFromFile(f, type) {
520
- const reader = new FileReader()
541
+ async importFromFile(file, type) {
521
542
  type = type || Utils.detectFileType(f)
522
- reader.readAsText(f)
523
- reader.onload = (e) => this.importRaw(e.target.result, type)
543
+ const raw = await this.readFile(file)
544
+ return this.importRaw(raw, type)
524
545
  }
525
546
 
526
547
  async importFromUrl(uri, type) {
527
548
  uri = this._umap.renderUrl(uri)
528
549
  const response = await this._umap.request.get(uri)
529
550
  if (response?.ok) {
530
- this.importRaw(await response.text(), type)
551
+ return this.importRaw(await response.text(), type)
531
552
  }
532
553
  }
533
554
 
@@ -930,9 +951,9 @@ export class DataLayer extends ServerStored {
930
951
  else this.hide()
931
952
  }
932
953
 
933
- zoomTo() {
954
+ zoomTo(bounds) {
934
955
  if (!this.isVisible()) return
935
- const bounds = this.layer.getBounds()
956
+ bounds = bounds || this.layer.getBounds()
936
957
  if (bounds.isValid()) {
937
958
  const options = { maxZoom: this.getOption('zoomTo') }
938
959
  this._leafletMap.fitBounds(bounds, options)
@@ -1,4 +1,8 @@
1
- import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
1
+ import {
2
+ DomEvent,
3
+ DomUtil,
4
+ LatLngBounds,
5
+ } from '../../vendors/leaflet/leaflet-src.esm.js'
2
6
  import { uMapAlert as Alert } from '../components/alerts/alert.js'
3
7
  import { translate } from './i18n.js'
4
8
  import { SCHEMA } from './schema.js'
@@ -270,16 +274,12 @@ export default class Importer extends Utils.WithTemplate {
270
274
  }
271
275
 
272
276
  submit() {
273
- let hasErrors
274
277
  if (this.format === 'umap') {
275
- hasErrors = !this.full()
278
+ this.full()
276
279
  } else if (!this.url) {
277
- hasErrors = !this.copy()
280
+ this.copy()
278
281
  } else if (this.action) {
279
- hasErrors = !this[this.action]()
280
- }
281
- if (hasErrors === false) {
282
- Alert.info(translate('Data successfully imported!'))
282
+ this[this.action]()
283
283
  }
284
284
  }
285
285
 
@@ -294,8 +294,9 @@ export default class Importer extends Utils.WithTemplate {
294
294
  } else if (this.url) {
295
295
  this._umap.importFromUrl(this.url, this.format)
296
296
  }
297
+ this.onSuccess()
297
298
  } catch (e) {
298
- Alert.error(translate('Invalid umap data'))
299
+ this.onError(translate('Invalid umap data'))
299
300
  console.error(e)
300
301
  return false
301
302
  }
@@ -306,7 +307,7 @@ export default class Importer extends Utils.WithTemplate {
306
307
  return false
307
308
  }
308
309
  if (!this.format) {
309
- Alert.error(translate('Please choose a format'))
310
+ this.onError(translate('Please choose a format'))
310
311
  return false
311
312
  }
312
313
  const layer = this.layer
@@ -318,26 +319,65 @@ export default class Importer extends Utils.WithTemplate {
318
319
  layer.options.remoteData.proxy = true
319
320
  layer.options.remoteData.ttl = SCHEMA.ttl.default
320
321
  }
321
- layer.fetchRemoteData(true)
322
+ layer.fetchRemoteData(true).then((features) => {
323
+ if (features?.length) {
324
+ layer.zoomTo()
325
+ this.onSuccess()
326
+ } else {
327
+ this.onError()
328
+ }
329
+ })
322
330
  }
323
331
 
324
- copy() {
332
+ async copy() {
325
333
  // Format may be guessed from file later.
326
334
  // Usefull in case of multiple files with different formats.
327
335
  if (!this.format && !this.files.length) {
328
- Alert.error(translate('Please choose a format'))
336
+ this.onError(translate('Please choose a format'))
329
337
  return false
330
338
  }
339
+ let promise
331
340
  const layer = this.layer
332
341
  if (this.clear) layer.empty()
333
342
  if (this.files.length) {
334
- for (const file of this.files) {
335
- this._umap.processFileToImport(file, layer, this.format)
336
- }
343
+ promise = layer.importFromFiles(this.files, this.format)
337
344
  } else if (this.raw) {
338
- layer.importRaw(this.raw, this.format)
345
+ promise = layer.importRaw(this.raw, this.format)
339
346
  } else if (this.url) {
340
- layer.importFromUrl(this.url, this.format)
347
+ promise = layer.importFromUrl(this.url, this.format)
348
+ }
349
+ if (promise) promise.then((data) => this.onCopyFinished(layer, data))
350
+ }
351
+
352
+ onError(message = translate('No data has been found for import')) {
353
+ Alert.error(message)
354
+ }
355
+
356
+ onSuccess(count) {
357
+ if (count) {
358
+ Alert.success(translate('Successfully imported {count} feature(s)'), {
359
+ count: count,
360
+ })
361
+ } else {
362
+ Alert.success(translate('Data successfully imported!'))
363
+ }
364
+ }
365
+
366
+ onCopyFinished(layer, features) {
367
+ // undefined features means error, let original error message pop
368
+ if (!features) return
369
+ if (!features.length) {
370
+ this.onError()
371
+ } else {
372
+ const bounds = new LatLngBounds()
373
+ for (const feature of features) {
374
+ const featureBounds = feature.ui.getBounds
375
+ ? feature.ui.getBounds()
376
+ : feature.ui.getCenter()
377
+ bounds.extend(featureBounds)
378
+ }
379
+ this.onSuccess(features.length)
380
+ layer.zoomTo(bounds)
341
381
  }
342
382
  }
343
383
  }
@@ -316,12 +316,14 @@ export default class Umap extends ServerStored {
316
316
  dataUrl = this.renderUrl(dataUrl)
317
317
  dataUrl = this.proxyUrl(dataUrl)
318
318
  const datalayer = this.createDataLayer()
319
- await datalayer.importFromUrl(dataUrl, dataFormat)
319
+ await datalayer
320
+ .importFromUrl(dataUrl, dataFormat)
321
+ .then(() => datalayer.zoomTo())
320
322
  }
321
323
  } else if (data) {
322
324
  data = decodeURIComponent(data)
323
325
  const datalayer = this.createDataLayer()
324
- await datalayer.importRaw(data, dataFormat)
326
+ await datalayer.importRaw(data, dataFormat).then(() => datalayer.zoomTo())
325
327
  }
326
328
  }
327
329
 
@@ -1514,7 +1516,7 @@ export default class Umap extends ServerStored {
1514
1516
  processFileToImport(file, layer, type) {
1515
1517
  type = type || Utils.detectFileType(file)
1516
1518
  if (!type) {
1517
- U.Alert.error(
1519
+ Alert.error(
1518
1520
  translate('Unable to detect format of file {filename}', {
1519
1521
  filename: file.name,
1520
1522
  })
@@ -520,7 +520,9 @@ const locale = {
520
520
  "Import helpers": "Import helpers",
521
521
  "Import helpers will fill the URL field for you.": "Import helpers will fill the URL field for you.",
522
522
  "Wikipedia": "Wikipedia",
523
- "Save draft": "Save draft"
523
+ "Save draft": "Save draft",
524
+ "No data has been found for import": "No data has been found for import",
525
+ "Successfully imported {count} feature(s)": "Successfully imported {count} feature(s)"
524
526
  }
525
527
  L.registerLocale("en", locale)
526
528
  L.setLocale("en")
@@ -520,5 +520,7 @@
520
520
  "Import helpers": "Import helpers",
521
521
  "Import helpers will fill the URL field for you.": "Import helpers will fill the URL field for you.",
522
522
  "Wikipedia": "Wikipedia",
523
- "Save draft": "Save draft"
523
+ "Save draft": "Save draft",
524
+ "No data has been found for import": "No data has been found for import",
525
+ "Successfully imported {count} feature(s)": "Successfully imported {count} feature(s)"
524
526
  }
@@ -520,7 +520,9 @@ const locale = {
520
520
  "Import helpers": "Assistants d'import",
521
521
  "Import helpers will fill the URL field for you.": "Les assistants d'import vont renseigner le champ URL pour vous.",
522
522
  "Wikipedia": "Wikipedia",
523
- "Save draft": "Enregistrer le brouillon"
523
+ "Save draft": "Enregistrer le brouillon",
524
+ "No data has been found for import": "Aucunes données à importer",
525
+ "Successfully imported {count} feature(s)": "{count} élément(s) ajouté(s) à la carte"
524
526
  }
525
527
  L.registerLocale("fr", locale)
526
528
  L.setLocale("fr")
@@ -520,5 +520,7 @@
520
520
  "Import helpers": "Assistants d'import",
521
521
  "Import helpers will fill the URL field for you.": "Les assistants d'import vont renseigner le champ URL pour vous.",
522
522
  "Wikipedia": "Wikipedia",
523
- "Save draft": "Enregistrer le brouillon"
523
+ "Save draft": "Enregistrer le brouillon",
524
+ "No data has been found for import": "Aucunes données à importer",
525
+ "Successfully imported {count} feature(s)": "{count} élément(s) ajouté(s) à la carte"
524
526
  }
@@ -0,0 +1,3 @@
1
+ # Retrocompat
2
+
3
+ from .staticfiles import UmapManifestStaticFilesStorage # noqa: F401
umap/storage/fs.py ADDED
@@ -0,0 +1,101 @@
1
+ import operator
2
+ import os
3
+ import time
4
+ from pathlib import Path
5
+
6
+ from django.conf import settings
7
+ from django.core.files.storage import FileSystemStorage
8
+
9
+
10
+ class FSDataStorage(FileSystemStorage):
11
+ def get_reference_version(self, instance):
12
+ return self._extract_version_ref(instance.geojson.name)
13
+
14
+ def make_filename(self, instance):
15
+ root = self._base_path(instance)
16
+ name = "%s_%s.geojson" % (instance.pk, int(time.time() * 1000))
17
+ return root / name
18
+
19
+ def list_versions(self, instance):
20
+ root = self._base_path(instance)
21
+ names = self.listdir(root)[1]
22
+ names = [name for name in names if self._is_valid_version(name, instance)]
23
+ versions = [self._version_metadata(name, instance) for name in names]
24
+ versions.sort(reverse=True, key=operator.itemgetter("at"))
25
+ return versions
26
+
27
+ def get_version(self, ref, instance):
28
+ with self.open(self.get_version_path(ref, instance), "r") as f:
29
+ return f.read()
30
+
31
+ def get_version_path(self, ref, instance):
32
+ base_path = Path(settings.MEDIA_ROOT) / self._base_path(instance)
33
+ fullpath = base_path / f"{instance.pk}_{ref}.geojson"
34
+ if instance.old_id and not fullpath.exists():
35
+ fullpath = base_path / f"{instance.old_id}_{ref}.geojson"
36
+ if not fullpath.exists():
37
+ raise ValueError(f"Invalid version reference: {ref}")
38
+ return fullpath
39
+
40
+ def onDatalayerSave(self, instance):
41
+ self._purge_gzip(instance)
42
+ self._purge_old_versions(instance, keep=settings.UMAP_KEEP_VERSIONS)
43
+
44
+ def onDatalayerDelete(self, instance):
45
+ self._purge_gzip(instance)
46
+ self._purge_old_versions(instance, keep=None)
47
+
48
+ def _extract_version_ref(self, path):
49
+ version = path.split(".")[0]
50
+ if "_" in version:
51
+ return version.split("_")[-1]
52
+ return version
53
+
54
+ def _base_path(self, instance):
55
+ path = ["datalayer", str(instance.map.pk)[-1]]
56
+ if len(str(instance.map.pk)) > 1:
57
+ path.append(str(instance.map.pk)[-2])
58
+ path.append(str(instance.map.pk))
59
+ return Path(os.path.join(*path))
60
+
61
+ def _is_valid_version(self, name, instance):
62
+ valid_prefixes = [name.startswith("%s_" % instance.pk)]
63
+ if instance.old_id:
64
+ valid_prefixes.append(name.startswith("%s_" % instance.old_id))
65
+ return any(valid_prefixes) and name.endswith(".geojson")
66
+
67
+ def _version_metadata(self, name, instance):
68
+ ref = self._extract_version_ref(name)
69
+ return {
70
+ "name": name,
71
+ "ref": ref,
72
+ "at": ref,
73
+ "size": self.size(self._base_path(instance) / name),
74
+ }
75
+
76
+ def _purge_old_versions(self, instance, keep=None):
77
+ root = self._base_path(instance)
78
+ versions = self.list_versions(instance)
79
+ if keep is not None:
80
+ versions = versions[keep:]
81
+ for version in versions:
82
+ name = version["name"]
83
+ # Should not be in the list, but ensure to not delete the file
84
+ # currently used in database
85
+ if keep is not None and instance.geojson.name.endswith(name):
86
+ continue
87
+ try:
88
+ self.delete(root / name)
89
+ except FileNotFoundError:
90
+ pass
91
+
92
+ def _purge_gzip(self, instance):
93
+ root = self._base_path(instance)
94
+ names = self.listdir(root)[1]
95
+ prefixes = [f"{instance.pk}_"]
96
+ if instance.old_id:
97
+ prefixes.append(f"{instance.old_id}_")
98
+ prefixes = tuple(prefixes)
99
+ for name in names:
100
+ if name.startswith(prefixes) and name.endswith(".gz"):
101
+ self.delete(root / name)
umap/storage/s3.py ADDED
@@ -0,0 +1,61 @@
1
+ from gzip import GzipFile
2
+
3
+ from django.core.exceptions import ImproperlyConfigured
4
+
5
+ try:
6
+ from botocore.exceptions import ClientError
7
+ from storages.backends.s3 import S3Storage
8
+ except ImportError:
9
+ raise ImproperlyConfigured(
10
+ "You need to install s3 dependencies: pip install umap-project[s3]"
11
+ )
12
+
13
+
14
+ class S3DataStorage(S3Storage):
15
+ gzip = True
16
+
17
+ def get_reference_version(self, instance):
18
+ metadata = self.connection.meta.client.head_object(
19
+ Bucket=self.bucket_name, Key=instance.geojson.name
20
+ )
21
+ # Do not fail if bucket does not handle versioning
22
+ return metadata.get("VersionId", metadata["ETag"])
23
+
24
+ def make_filename(self, instance):
25
+ return f"{str(instance.pk)}.geojson"
26
+
27
+ def list_versions(self, instance):
28
+ response = self.connection.meta.client.list_object_versions(
29
+ Bucket=self.bucket_name, Prefix=instance.geojson.name
30
+ )
31
+ return [
32
+ {
33
+ "ref": version["VersionId"],
34
+ "at": version["LastModified"].timestamp() * 1000,
35
+ "size": version["Size"],
36
+ }
37
+ for version in response["Versions"]
38
+ ]
39
+
40
+ def get_version(self, ref, instance):
41
+ try:
42
+ data = self.connection.meta.client.get_object(
43
+ Bucket=self.bucket_name,
44
+ Key=instance.geojson.name,
45
+ VersionId=ref,
46
+ )
47
+ except ClientError:
48
+ raise ValueError(f"Invalid version reference: {ref}")
49
+ return GzipFile(mode="r", fileobj=data["Body"]).read()
50
+
51
+ def get_version_path(self, ref, instance):
52
+ return self.url(instance.geojson.name, parameters={"VersionId": ref})
53
+
54
+ def onDatalayerSave(self, instance):
55
+ pass
56
+
57
+ def onDatalayerDelete(self, instance):
58
+ return self.connection.meta.client.delete_object(
59
+ Bucket=self.bucket_name,
60
+ Key=instance.geojson.name,
61
+ )
@@ -0,0 +1,64 @@
1
+ from pathlib import Path
2
+
3
+ from django.conf import settings
4
+ from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
5
+ from rcssmin import cssmin
6
+ from rjsmin import jsmin
7
+
8
+
9
+ class UmapManifestStaticFilesStorage(ManifestStaticFilesStorage):
10
+ support_js_module_import_aggregation = True
11
+ max_post_process_passes = 15
12
+
13
+ # We remove `;` at the end of all regexps to match our biome config.
14
+ _js_module_import_aggregation_patterns = (
15
+ "*.js",
16
+ (
17
+ (
18
+ (
19
+ r"""(?P<matched>import(?s:(?P<import>[\s\{].*?))"""
20
+ r"""\s*from\s*['"](?P<url>[\.\/].*?)["']\s*)"""
21
+ ),
22
+ 'import%(import)s from "%(url)s"\n',
23
+ ),
24
+ (
25
+ (
26
+ r"""(?P<matched>export(?s:(?P<exports>[\s\{].*?))"""
27
+ r"""\s*from\s*["'](?P<url>[\.\/].*?)["']\s*)"""
28
+ ),
29
+ 'export%(exports)s from "%(url)s"\n',
30
+ ),
31
+ (
32
+ r"""(?P<matched>import\s*['"](?P<url>[\.\/].*?)["']\s*)""",
33
+ 'import"%(url)s"\n',
34
+ ),
35
+ (
36
+ r"""(?P<matched>import\(["'](?P<url>.*?)["']\)\.then)""",
37
+ """import("%(url)s").then""",
38
+ ),
39
+ (
40
+ r"""(?P<matched>await import\(["'](?P<url>.*?)["']\))""",
41
+ """await import("%(url)s")""",
42
+ ),
43
+ ),
44
+ )
45
+
46
+ def post_process(self, paths, **options):
47
+ collected = super().post_process(paths, **options)
48
+ for original_path, processed_path, processed in collected:
49
+ if isinstance(processed, Exception):
50
+ print("Error with file", original_path)
51
+ raise processed
52
+ if processed_path.endswith(".js"):
53
+ path = Path(settings.STATIC_ROOT) / processed_path
54
+ initial = path.read_text()
55
+ if "sourceMappingURL" not in initial: # Already minified.
56
+ minified = jsmin(initial)
57
+ path.write_text(minified)
58
+ if processed_path.endswith(".css"):
59
+ path = Path(settings.STATIC_ROOT) / processed_path
60
+ initial = path.read_text()
61
+ if "sourceMappingURL" not in initial: # Already minified.
62
+ minified = cssmin(initial)
63
+ path.write_text(minified)
64
+ yield original_path, processed_path, True
@@ -27,7 +27,7 @@ def patch_storage():
27
27
 
28
28
  DataLayer.geojson.field.storage = storages.create_storage(
29
29
  {
30
- "BACKEND": "umap.storage.UmapS3",
30
+ "BACKEND": "umap.storage.s3.S3DataStorage",
31
31
  "OPTIONS": {
32
32
  "access_key": "testing",
33
33
  "secret_key": "testing",
@@ -15,7 +15,7 @@ def staticfiles(settings):
15
15
  # Make sure settings are properly reset after the test
16
16
  settings.STORAGES = deepcopy(settings.STORAGES)
17
17
  settings.STORAGES["staticfiles"]["BACKEND"] = (
18
- "umap.storage.UmapManifestStaticFilesStorage"
18
+ "umap.storage.staticfiles.UmapManifestStaticFilesStorage"
19
19
  )
20
20
  try:
21
21
  call_command("collectstatic", "--noinput")
umap/views.py CHANGED
@@ -452,27 +452,27 @@ showcase = MapsShowCase.as_view()
452
452
 
453
453
 
454
454
  def validate_url(request):
455
- assert request.method == "GET"
455
+ assert request.method == "GET", "Wrong HTTP method"
456
456
  url = request.GET.get("url")
457
- assert url
457
+ assert url, "Missing URL"
458
458
  try:
459
459
  URLValidator(url)
460
- except ValidationError:
461
- raise AssertionError()
462
- assert "HTTP_REFERER" in request.META
460
+ except ValidationError as err:
461
+ raise AssertionError(err)
462
+ assert "HTTP_REFERER" in request.META, "Missing HTTP_REFERER"
463
463
  referer = urlparse(request.META.get("HTTP_REFERER"))
464
464
  toproxy = urlparse(url)
465
465
  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
466
+ assert toproxy.hostname, "No hostname"
467
+ assert referer.hostname == local.hostname, f"{referer.hostname} != {local.hostname}"
468
+ assert toproxy.hostname != "localhost", "Invalid localhost target"
469
+ assert toproxy.netloc != local.netloc, "Invalid netloc"
470
470
  try:
471
471
  # clean this when in python 3.4
472
472
  ipaddress = socket.gethostbyname(toproxy.hostname)
473
- except:
474
- raise AssertionError()
475
- assert not PRIVATE_IP.match(ipaddress)
473
+ except Exception as err:
474
+ raise AssertionError(err)
475
+ assert not PRIVATE_IP.match(ipaddress), "Private IP"
476
476
  return url
477
477
 
478
478
 
@@ -480,7 +480,8 @@ class AjaxProxy(View):
480
480
  def get(self, *args, **kwargs):
481
481
  try:
482
482
  url = validate_url(self.request)
483
- except AssertionError:
483
+ except AssertionError as err:
484
+ print(f"AjaxProxy: {err}")
484
485
  return HttpResponseBadRequest()
485
486
  try:
486
487
  ttl = int(self.request.GET.get("ttl"))
@@ -1168,7 +1169,7 @@ class DataLayerView(BaseDetailView):
1168
1169
  # (no gzip/cache-control/If-Modified-Since/If-None-Match)
1169
1170
  data = self.filedata
1170
1171
  response = HttpResponse(data, content_type="application/geo+json")
1171
- response["X-Datalayer-Version"] = self.fileversion
1172
+ response["X-Datalayer-Version"] = self.fileversion
1172
1173
  return response
1173
1174
 
1174
1175
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: umap-project
3
- Version: 2.8.0a0
3
+ Version: 2.8.0a2
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>
@@ -1,4 +1,4 @@
1
- umap/__init__.py,sha256=2KCwAhPAdl9iGZ4OqB3b9x9oEMjwYBXpF7OB99CO7m8,20
1
+ umap/__init__.py,sha256=0_15wRSL9SkA85FevSlxLo6H6SAySXR0t2XW5TtIIQA,20
2
2
  umap/admin.py,sha256=LoQytPGK6pLBqZ5QgQ9DIPAxhTG31cTtHOCqO9BY5S4,2645
3
3
  umap/apps.py,sha256=5ssKqPUuNJlapaBmr4LY_HDb7J1NFCT3wzythxQOOfs,109
4
4
  umap/asgi.py,sha256=CuVSNBwNb4AvuaD_Ha3ehtvf-c46ijZoVOSoP6WhXp8,432
@@ -10,10 +10,9 @@ umap/forms.py,sha256=fonoSwA02LawR7kXbjEZCH0ZYi53fAbRHYgW2RaqeYw,3803
10
10
  umap/managers.py,sha256=-lBK0xYFRDfX76qDRdLnZOA8jEPYseEwIj8QOiHVM4w,243
11
11
  umap/middleware.py,sha256=p8EPW_gYW8Wh2lk0DNIAkZQbYlBZugW7Yq4iiA7L4aE,514
12
12
  umap/models.py,sha256=4SzhKdyWXfJMdzEpCyVPnzbTT-TGbDusAy7SP_8UuwI,17929
13
- umap/storage.py,sha256=kEzS0BP9jrfVwlUtmEcf4W0t-7uEBqFfgKBlKvQTRVg,7900
14
13
  umap/urls.py,sha256=LA3zxyu-GDo8kVqdyU7_bdbDGhDJV8_yFW4oEPTXw4s,7559
15
14
  umap/utils.py,sha256=19i8ibi-1IXxafT4k_yOHMhD-DsPH74Ll9qw-UrUkM4,5856
16
- umap/views.py,sha256=xSKBdbRFtoA0RXm5qCrVAoir4gm0_rN7d-70Nzbg0pQ,46016
15
+ umap/views.py,sha256=hMBxefWDUZQUVUb5C477cVyL7hp7OfppZlu4_sw9L4o,46266
17
16
  umap/websocket_server.py,sha256=D9sTHhKg0DG37b8bw7KWTKMDc6TPyTkNLCVkh2mlFOo,6604
18
17
  umap/wsgi.py,sha256=IopIgnDZbCus3XpSetTHnra9VyzWi0Y2tJo-CmfTWCY,1132
19
18
  umap/bin/__init__.py,sha256=iA3ON4A6NCpenrn3q2OgefUKF5QRFIQS-FtS0pxruI8,234
@@ -38,7 +37,7 @@ umap/locale/de/LC_MESSAGES/django.po,sha256=kVaio9t9AKF3vBcZJ-Q2P6Ua90MIwbIRCABW
38
37
  umap/locale/el/LC_MESSAGES/django.mo,sha256=bJOH3_UQZoEfYi9mrsWHZfV6fVhMgnuiNQ_Dc_xOcuA,15058
39
38
  umap/locale/el/LC_MESSAGES/django.po,sha256=ruqAlyQryr7Ip5aTbjEwH6eumb_b8JS-u6qcl9yjN04,22180
40
39
  umap/locale/en/LC_MESSAGES/django.mo,sha256=UXCQbz2AxBvh-IQ7bGgjoBnijo8h9DfE9107A-2Mgkk,337
41
- umap/locale/en/LC_MESSAGES/django.po,sha256=RCM-hylLym43OQaDWCABqnHAyip57gwj4P_UNfyr3oU,13627
40
+ umap/locale/en/LC_MESSAGES/django.po,sha256=Ht498JmD3Zp08W7qpOjJBTngf6oSMW2pR5dT3OneL5Q,13628
42
41
  umap/locale/es/LC_MESSAGES/django.mo,sha256=rblKBhki-DnIAVxbo2UCYQCIte1dqRZDyAjDQCJgKug,11985
43
42
  umap/locale/es/LC_MESSAGES/django.po,sha256=E0xSceHtg4CkgSHT2mFPRdiBP9n8c-PnKDXHQgAgStA,18690
44
43
  umap/locale/et/LC_MESSAGES/django.mo,sha256=Y5ulivMuS2IOR9ITeWRx6PBynPn6LBFUizrFWRraR1s,5051
@@ -115,10 +114,10 @@ umap/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
114
  umap/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
115
  umap/management/commands/anonymous_edit_url.py,sha256=hsWgPzZJmLCoDKTWziFUuwq-DdnSiXkSal2t2TIED-s,1070
117
116
  umap/management/commands/clean_tilelayer.py,sha256=Rcc2PibUUreU0jUZMtUlyqVvgbQMLMuuCZ2tkrzRqHU,5712
118
- umap/management/commands/empty_trash.py,sha256=guGnu72EYOiRtS-pLjxByFOmFtq1Ra0JR0SWBOZDOsY,1024
117
+ umap/management/commands/empty_trash.py,sha256=POmBXloLoPZ_6MFbgsDz4YOKGmEwIWrvMt5v5QMi7ZM,1136
119
118
  umap/management/commands/generate_js_locale.py,sha256=wkf-PFIHS7m4ZhyL1ZRMBLqyUeY2SlOrTXS42tE0-bs,1281
120
119
  umap/management/commands/import_pictograms.py,sha256=RuQDCoiKamba4l3fZUGAXRyd-3zwWWT5c5AhgDvs7AQ,2369
121
- umap/management/commands/migrate_to_S3.py,sha256=-LVbLdr554mjhdSty1OE_33OI4CjdegqQZr2Fw2JeSk,1119
120
+ umap/management/commands/migrate_to_S3.py,sha256=GBGnydc107v75NYsQfMLLO7Jx0i2g7EKEfE00YZVb1M,1130
122
121
  umap/management/commands/run_websocket_server.py,sha256=TyECJWnmZ95KpVEWSaqfXywz5VwIEzPdypU2d6V541c,648
123
122
  umap/migrations/0001_initial.py,sha256=dMcXtTKPiA0IqXCrDVctH91Fe0hhc04NxmvcLAULyzE,8787
124
123
  umap/migrations/0002_tilelayer_tms.py,sha256=E99JAu1K0NzwsCEJs1z5uGlBkBJmoVb9a3WBKjpLYlo,372
@@ -147,7 +146,7 @@ umap/migrations/0024_alter_map_share_status.py,sha256=QRjERy2XN0jsc8MM5cCba5freq
147
146
  umap/migrations/0025_alter_datalayer_geojson.py,sha256=958v9AkpkAR5Q78ZcHC0fgZzN05BdfQwtNvUuPmWEvI,563
148
147
  umap/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
149
148
  umap/settings/__init__.py,sha256=aPJkOTk0uFusIBA-uOjdUi10R5Cxt4jl5yv2_uCTUvo,1702
150
- umap/settings/base.py,sha256=zdJa2Qqq1hlsFh80YFhOiNEs1cWa9ZAE5zzygpmHgKE,11082
149
+ umap/settings/base.py,sha256=rhaIDby2wSb4v8IBx_6xqHVnIigD4G0xzdHX2-9UfNM,11096
151
150
  umap/settings/dev.py,sha256=pj1mpmZXiI2syW8pB01wcVeqCFABF3V-nlOxArir4cw,386
152
151
  umap/settings/local.py.sample,sha256=wpnoe7qtXer_xBuhWbcbqcSCotTJRu6h8hG7N-sD0b4,3157
153
152
  umap/static/.gitignore,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -238,7 +237,7 @@ umap/static/umap/js/modules/formatter.js,sha256=drbIxbDGrcHOUtzJtC4B5iKpm8-YNg_b
238
237
  umap/static/umap/js/modules/global.js,sha256=7jm6NLZ5PM2yrkbWHdWkoDFcevgIAMqE-vZQRXcIgEo,934
239
238
  umap/static/umap/js/modules/help.js,sha256=0vsDTFGcPz2coG_LBeGPSUQupZTFUes6kCwQCPBUjuU,9694
240
239
  umap/static/umap/js/modules/i18n.js,sha256=dEpjsWoEZa-Tr5_MDO0tuWkt7kLL3crxXqhttyP-khU,1387
241
- umap/static/umap/js/modules/importer.js,sha256=hfFXTXtyCueeEZrs-cq6EAnicgRANzijbhoihYFT-RE,10000
240
+ umap/static/umap/js/modules/importer.js,sha256=6wqs2z-jrfcwvSFvmPuJWDnP9s1dUEe2Oi1ili3FZ0M,10961
242
241
  umap/static/umap/js/modules/leaflet-configure.js,sha256=P3aD8iNGxuVNv-xW4Di4txAjNmnlpKtCCzDvPaKEdQ8,243
243
242
  umap/static/umap/js/modules/orderable.js,sha256=zDtcElZ_MVPoGba8Iv9bxOzk4vuN7C-5XVl4UomDYHE,2521
244
243
  umap/static/umap/js/modules/permissions.js,sha256=RxKrfjLo6hdGuAvwQrzUUJJznD5RkmSkAHVMidA9iKQ,8497
@@ -249,11 +248,11 @@ umap/static/umap/js/modules/schema.js,sha256=RuBO5obpccTPH_iPXRU-lwyj1SoX9bp619t
249
248
  umap/static/umap/js/modules/share.js,sha256=s1X59VpMqut_Vhg7l7LV2IZDm9obRVbDH9wZbG5kX3c,7182
250
249
  umap/static/umap/js/modules/slideshow.js,sha256=zcRzOMkJvhps1npGRjHkdK4Ce3UkqOsv8OsDgQWO-bg,3567
251
250
  umap/static/umap/js/modules/tableeditor.js,sha256=6p2YE2-NF4NkwLDQkqrl90P_PwOYdFao0-ac_AkOwWs,9854
252
- umap/static/umap/js/modules/umap.js,sha256=n509OKscghGTM_sXlvcobVdJG36NRpeQA5pIlmEaUDI,49989
251
+ umap/static/umap/js/modules/umap.js,sha256=VSU8p94tLKZBGeL1NWWmrsT81RTZNW1oROOxsUqeAxI,50071
253
252
  umap/static/umap/js/modules/urls.js,sha256=76cFqycj2O8huuoYYBvxnVt2Fc2UDbgrRsiv6lQmcSY,890
254
253
  umap/static/umap/js/modules/utils.js,sha256=fYzo-WjRzDZsdjv3CI9U4Es3AqOfuBCJuRq997m2EfQ,12309
255
254
  umap/static/umap/js/modules/data/features.js,sha256=rd5WpZoai3u4baWxi_8m2vtwGLuI-9hdH4PGnrjqg_4,31667
256
- umap/static/umap/js/modules/data/layer.js,sha256=WhB0ZSaNpI082JKL28EYLjY2fhoIXdOhMwn8JDuqtqM,34530
255
+ umap/static/umap/js/modules/data/layer.js,sha256=uLEVGp3gFq7obA9Ij-RVz-VWqo9SwLQd1uBzmaKYo9o,35101
257
256
  umap/static/umap/js/modules/importers/cadastrefr.js,sha256=KHqxHleFRFzNi98gegvUM1R6eJorAGGcMft_ktUg-ug,2262
258
257
  umap/static/umap/js/modules/importers/communesfr.js,sha256=6q6ilmYhhuSmgdrvfTyEDNyMLbc9J9Bt8VMZVXB8ZOA,1723
259
258
  umap/static/umap/js/modules/importers/datasets.js,sha256=StZbRiq_1vqe0OO1w66k5Lwzju8RntmHpWe9HWIDfRE,1372
@@ -298,8 +297,8 @@ umap/static/umap/locale/de.js,sha256=wUAKh3L1vhrHH819XeKeLalw8GOhjh5G-yErBUDY4R0
298
297
  umap/static/umap/locale/de.json,sha256=cM1Q4WS2qCtQebyUHnHmblS4aj9j4TooDwk_nSQ8pqs,32723
299
298
  umap/static/umap/locale/el.js,sha256=aK9fBOMEaQf-a3IU3lEhJ5qeDNRtHIR5k_WD0n09d88,41377
300
299
  umap/static/umap/locale/el.json,sha256=G3O0k62tIsf654xUZO0x988aKIAo0zZCHzRibDxpjo8,41312
301
- umap/static/umap/locale/en.js,sha256=3zFUHDjehyf1qhY9NcO3SkiTY0cdEinrpIOSez3upRw,30089
302
- umap/static/umap/locale/en.json,sha256=9EbcClQgnn14besF7LctqoxAWPR4qvnRiMIoN3XeuHY,30024
300
+ umap/static/umap/locale/en.js,sha256=89UHZNqyNhy86A0lPxmFzsw7UYV-f6hHEwnYg4yzQAw,30259
301
+ umap/static/umap/locale/en.json,sha256=U5vQn1-kXUsmAm5tTN2-NbC6UulPa0ERVfs4JQywAwo,30194
303
302
  umap/static/umap/locale/en_US.json,sha256=E8ScCCs3lXiHOJPqTNxd7maDyhkkF3GNqN-r2p27lLQ,29813
304
303
  umap/static/umap/locale/es.js,sha256=xYnvTBRzycvrYhEA5yr8uFV8aeCoVLPsJegdccDdjQg,32933
305
304
  umap/static/umap/locale/es.json,sha256=-JF4DZ5B4yk2Qvhj4FsEhZs0UybS0m0t2roH0cvnPXw,32868
@@ -311,8 +310,8 @@ umap/static/umap/locale/fa_IR.js,sha256=qn-Oc23zaIz_ua3DAvvVljn--jn2fR7eaMF-bsyp
311
310
  umap/static/umap/locale/fa_IR.json,sha256=ql1IvgBcDHiAD6ho500PwRE2y8x9gSfsKq96IQgAZTw,38633
312
311
  umap/static/umap/locale/fi.js,sha256=wyW-hHzNfKHoPKata6sw_TBx3Grz227eXscuabAGbwQ,30915
313
312
  umap/static/umap/locale/fi.json,sha256=lDQ6_RxXtcNI_kahg8XBWlVYUE-MvgUqJA_Ue6DJj3c,30850
314
- umap/static/umap/locale/fr.js,sha256=qcNce3E8OnRxRQwi8bRm9XhcixmTGtZrdEN-4fjcPSA,32978
315
- umap/static/umap/locale/fr.json,sha256=RgemOdmvz98TVih78sXjhJ-D69KFE5xk12yr-75cMEo,32913
313
+ umap/static/umap/locale/fr.js,sha256=VLtQz8W6InWu90Ccxx3gJZwuVQ2AutTKImvjdbrNv3U,33146
314
+ umap/static/umap/locale/fr.json,sha256=DzTqe44_JAMHYiNFqGbF3VLHmoXSoavJXcvRegigw-8,33081
316
315
  umap/static/umap/locale/gl.js,sha256=ninZHPi3xo8aatt9G6zBNXCX6br0Na7-t20JJT0XDX4,31478
317
316
  umap/static/umap/locale/gl.json,sha256=b99nZnWgtOZf1Uj-WUCPnWLrcvg5IGDYCV0wc7ntgb8,31413
318
317
  umap/static/umap/locale/he.js,sha256=jljbzL6uctwBN5fzwQM4lFZ6ZLXKzldUeDTyzQTSyqU,33381
@@ -449,6 +448,10 @@ umap/static/umap/vendors/tokml/tokml.es.js,sha256=BqL0WqFH5UZAh_S_265E6PWZjPMYxe
449
448
  umap/static/umap/vendors/tokml/tokml.es.mjs.map,sha256=vw5JxZFh_2_xM1cHI51r1Bf48JaBKzNcR7ddgaaF8KY,45844
450
449
  umap/static/umap/vendors/toolbar/leaflet.toolbar.css,sha256=5KVBOQ0ivsFuafKYvVm32wJ_fi7w8Li1-2Rwwcv85jA,2244
451
450
  umap/static/umap/vendors/toolbar/leaflet.toolbar.js,sha256=HXh_bR49ZFpJ-GNXDNo2eHy-fJmrWehAzUeHgGhu__c,5326
451
+ umap/storage/__init__.py,sha256=Aj421eIsZhsu0B7zd5lTJufVYr0EtUkH0lTqpbBh8AU,85
452
+ umap/storage/fs.py,sha256=CdjnDe4UVMt_9Yp2uZqvtarfHTAYRed6Tid2UU-sBgY,3721
453
+ umap/storage/s3.py,sha256=KAYu3vAqXbd5UhaoPxG6zcGtBfKZOzzi-6uY6YEuIcY,1962
454
+ umap/storage/staticfiles.py,sha256=mxFcenC1JECmpNy4H0e7vX8GObDZVXzs1RPjQFWNd5k,2473
452
455
  umap/templates/404.html,sha256=1yLlD8rSF_9cfjm5FYy--P46HLVbHeFkJiW9nRzM--E,399
453
456
  umap/templates/500.html,sha256=Z8x47OVfYXquAYAlmRB0EJVTCiCaBppFFiFEmoYsMYY,5202
454
457
  umap/templates/base.html,sha256=_Q0Ikwese3vlUl0pKQdzHDy_oRT9OV4uWh0RGFaYAQA,1499
@@ -495,14 +498,14 @@ umap/tests/conftest.py,sha256=KQCZanCTl1ABLIKOuyxS_cpBoXGiwjDc29jsLBiSWxY,1633
495
498
  umap/tests/settings.py,sha256=tY70LMFXyo_WijswqGyeWai7vBzM62k7IA8pkkbc9y4,816
496
499
  umap/tests/test_clean_tilelayer.py,sha256=wGTd_AHOTmQ4QMswAyc-1_lJmQOSyhY3OahLAusEIdA,2515
497
500
  umap/tests/test_datalayer.py,sha256=NWX7o-sLOrq3nHT0GDywz5vtJU4HJhZZh11r_rWxhVA,9539
498
- umap/tests/test_datalayer_s3.py,sha256=bqLpdQZLoL-jwD7HeqMXuLFUfnkP-xd6B6BBW7JFm0U,4145
501
+ umap/tests/test_datalayer_s3.py,sha256=6V3AK22AXkFNjx5__SyBk0Uj0rTDAjJQv67r7D_MDVc,4155
499
502
  umap/tests/test_datalayer_views.py,sha256=Fx_oQF3hBC2FVHTTjTScXbFS2d7FRKdBL7QFFexvKkg,22880
500
503
  umap/tests/test_empty_trash.py,sha256=9dYdnQqzlfgkExQxiQDqQ4flKsPTZ97uB3te5wmZ0Nw,1112
501
504
  umap/tests/test_licence.py,sha256=BxNY3gdKhIoc2u5OPmAkmjCp0jJN-Jm-uPOfAZlpOHA,339
502
505
  umap/tests/test_map.py,sha256=vrtheSMQNk45kBIcJ0QY9K7HKYee5yg4Vnp78DyaIwQ,5170
503
506
  umap/tests/test_map_views.py,sha256=EKLJnQ-xk_bkaJ6P7OJ2bnya5orbaSnWFV8GIYcjDNk,33823
504
507
  umap/tests/test_merge_features.py,sha256=uLZSW00WAI8_nZS0KPP8gg8U4nnky-XGb-VhhKUxv1M,2275
505
- umap/tests/test_statics.py,sha256=WJe4DZ-cSfN_wCRD8U9ocl6v5FoXrVwBjU6kI6BOcmY,1252
508
+ umap/tests/test_statics.py,sha256=xKuxT8Xj5Ii7gKISuiSfDj7dpjmJ2Ierby3Lg-haZCg,1264
506
509
  umap/tests/test_team_views.py,sha256=vExhJ3c1cJ7vgxe0G20UzTKkzR5D2UgAapk09muUg5w,4481
507
510
  umap/tests/test_tilelayer.py,sha256=toVpVutEvMLWKx5uH7ZbGNPGzqICZx1_S2OOpIfYPfQ,603
508
511
  umap/tests/test_utils.py,sha256=noh-AFL3qV-dNZYr8L1acsYC02SI710Bq2ZXV-jBEzk,407
@@ -569,8 +572,8 @@ umap/tests/integration/test_view_marker.py,sha256=ZLS6-GOWYpjeoYGHiHa7HesXJTLu9w
569
572
  umap/tests/integration/test_view_polygon.py,sha256=NMJC6Nt9VpQ8FIU9Pqq2OspHv49xsWlsoXCr8iBa0VA,2060
570
573
  umap/tests/integration/test_view_polyline.py,sha256=aJoXKmLhJaN0yhPdDCVskZNGx3q3mLDkjVPhZ30cadA,13959
571
574
  umap/tests/integration/test_websocket_sync.py,sha256=Xjn8z7Gj2PAmPmLkMTsHztFmhzsfyE3vg-wfewpA2I4,15511
572
- umap_project-2.8.0a0.dist-info/METADATA,sha256=a0AHRG8dpF_7KYUQNFAGMe84KPD56yidRdzGIilsJCU,2993
573
- umap_project-2.8.0a0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
574
- umap_project-2.8.0a0.dist-info/entry_points.txt,sha256=gz-KDQfEsMLBae8ABOD3foJsCYGPW1tA4Y394R_1RW8,39
575
- umap_project-2.8.0a0.dist-info/licenses/LICENSE,sha256=kQtrtRKgiPhcl7aO0-lmvbrNAXu7WHyiXvPrUk-TD2Q,820
576
- umap_project-2.8.0a0.dist-info/RECORD,,
575
+ umap_project-2.8.0a2.dist-info/METADATA,sha256=Z8IXnRgLp4AOUzTAkfRUB5hatnauca2C0V7NrLZieHk,2993
576
+ umap_project-2.8.0a2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
577
+ umap_project-2.8.0a2.dist-info/entry_points.txt,sha256=gz-KDQfEsMLBae8ABOD3foJsCYGPW1tA4Y394R_1RW8,39
578
+ umap_project-2.8.0a2.dist-info/licenses/LICENSE,sha256=kQtrtRKgiPhcl7aO0-lmvbrNAXu7WHyiXvPrUk-TD2Q,820
579
+ umap_project-2.8.0a2.dist-info/RECORD,,
umap/storage.py DELETED
@@ -1,216 +0,0 @@
1
- import operator
2
- import os
3
- import shutil
4
- import time
5
- from gzip import GzipFile
6
- from pathlib import Path
7
-
8
- from botocore.exceptions import ClientError
9
- from django.conf import settings
10
- from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
11
- from django.core.files.storage import FileSystemStorage
12
- from rcssmin import cssmin
13
- from rjsmin import jsmin
14
- from storages.backends.s3 import S3Storage
15
-
16
-
17
- class UmapManifestStaticFilesStorage(ManifestStaticFilesStorage):
18
- support_js_module_import_aggregation = True
19
- max_post_process_passes = 15
20
-
21
- # We remove `;` at the end of all regexps to match our biome config.
22
- _js_module_import_aggregation_patterns = (
23
- "*.js",
24
- (
25
- (
26
- (
27
- r"""(?P<matched>import(?s:(?P<import>[\s\{].*?))"""
28
- r"""\s*from\s*['"](?P<url>[\.\/].*?)["']\s*)"""
29
- ),
30
- 'import%(import)s from "%(url)s"\n',
31
- ),
32
- (
33
- (
34
- r"""(?P<matched>export(?s:(?P<exports>[\s\{].*?))"""
35
- r"""\s*from\s*["'](?P<url>[\.\/].*?)["']\s*)"""
36
- ),
37
- 'export%(exports)s from "%(url)s"\n',
38
- ),
39
- (
40
- r"""(?P<matched>import\s*['"](?P<url>[\.\/].*?)["']\s*)""",
41
- 'import"%(url)s"\n',
42
- ),
43
- (
44
- r"""(?P<matched>import\(["'](?P<url>.*?)["']\)\.then)""",
45
- """import("%(url)s").then""",
46
- ),
47
- (
48
- r"""(?P<matched>await import\(["'](?P<url>.*?)["']\))""",
49
- """await import("%(url)s")""",
50
- ),
51
- ),
52
- )
53
-
54
- def post_process(self, paths, **options):
55
- collected = super().post_process(paths, **options)
56
- for original_path, processed_path, processed in collected:
57
- if isinstance(processed, Exception):
58
- print("Error with file", original_path)
59
- raise processed
60
- if processed_path.endswith(".js"):
61
- path = Path(settings.STATIC_ROOT) / processed_path
62
- initial = path.read_text()
63
- if "sourceMappingURL" not in initial: # Already minified.
64
- minified = jsmin(initial)
65
- path.write_text(minified)
66
- if processed_path.endswith(".css"):
67
- path = Path(settings.STATIC_ROOT) / processed_path
68
- initial = path.read_text()
69
- if "sourceMappingURL" not in initial: # Already minified.
70
- minified = cssmin(initial)
71
- path.write_text(minified)
72
- yield original_path, processed_path, True
73
-
74
-
75
- class UmapS3(S3Storage):
76
- gzip = True
77
-
78
- def get_reference_version(self, instance):
79
- metadata = self.connection.meta.client.head_object(
80
- Bucket=self.bucket_name, Key=instance.geojson.name
81
- )
82
- # Do not fail if bucket does not handle versioning
83
- return metadata.get("VersionId", metadata["ETag"])
84
-
85
- def make_filename(self, instance):
86
- return f"{str(instance.pk)}.geojson"
87
-
88
- def list_versions(self, instance):
89
- response = self.connection.meta.client.list_object_versions(
90
- Bucket=self.bucket_name, Prefix=instance.geojson.name
91
- )
92
- return [
93
- {
94
- "ref": version["VersionId"],
95
- "at": version["LastModified"].timestamp() * 1000,
96
- "size": version["Size"],
97
- }
98
- for version in response["Versions"]
99
- ]
100
-
101
- def get_version(self, ref, instance):
102
- try:
103
- data = self.connection.meta.client.get_object(
104
- Bucket=self.bucket_name,
105
- Key=instance.geojson.name,
106
- VersionId=ref,
107
- )
108
- except ClientError:
109
- raise ValueError(f"Invalid version reference: {ref}")
110
- return GzipFile(mode="r", fileobj=data["Body"]).read()
111
-
112
- def get_version_path(self, ref, instance):
113
- return self.url(instance.geojson.name, parameters={"VersionId": ref})
114
-
115
- def onDatalayerSave(self, instance):
116
- pass
117
-
118
- def onDatalayerDelete(self, instance):
119
- return self.connection.meta.client.delete_object(
120
- Bucket=self.bucket_name,
121
- Key=instance.geojson.name,
122
- )
123
-
124
-
125
- class UmapFileSystem(FileSystemStorage):
126
- def get_reference_version(self, instance):
127
- return self._extract_version_ref(instance.geojson.name)
128
-
129
- def make_filename(self, instance):
130
- root = self._base_path(instance)
131
- name = "%s_%s.geojson" % (instance.pk, int(time.time() * 1000))
132
- return root / name
133
-
134
- def list_versions(self, instance):
135
- root = self._base_path(instance)
136
- names = self.listdir(root)[1]
137
- names = [name for name in names if self._is_valid_version(name, instance)]
138
- versions = [self._version_metadata(name, instance) for name in names]
139
- versions.sort(reverse=True, key=operator.itemgetter("at"))
140
- return versions
141
-
142
- def get_version(self, ref, instance):
143
- with self.open(self.get_version_path(ref, instance), "r") as f:
144
- return f.read()
145
-
146
- def get_version_path(self, ref, instance):
147
- base_path = Path(settings.MEDIA_ROOT) / self._base_path(instance)
148
- fullpath = base_path / f"{instance.pk}_{ref}.geojson"
149
- if instance.old_id and not fullpath.exists():
150
- fullpath = base_path / f"{instance.old_id}_{ref}.geojson"
151
- if not fullpath.exists():
152
- raise ValueError(f"Invalid version reference: {ref}")
153
- return fullpath
154
-
155
- def onDatalayerSave(self, instance):
156
- self._purge_gzip(instance)
157
- self._purge_old_versions(instance, keep=settings.UMAP_KEEP_VERSIONS)
158
-
159
- def onDatalayerDelete(self, instance):
160
- self._purge_gzip(instance)
161
- self._purge_old_versions(instance, keep=None)
162
-
163
- def _extract_version_ref(self, path):
164
- version = path.split(".")[0]
165
- if "_" in version:
166
- return version.split("_")[-1]
167
- return version
168
-
169
- def _base_path(self, instance):
170
- path = ["datalayer", str(instance.map.pk)[-1]]
171
- if len(str(instance.map.pk)) > 1:
172
- path.append(str(instance.map.pk)[-2])
173
- path.append(str(instance.map.pk))
174
- return Path(os.path.join(*path))
175
-
176
- def _is_valid_version(self, name, instance):
177
- valid_prefixes = [name.startswith("%s_" % instance.pk)]
178
- if instance.old_id:
179
- valid_prefixes.append(name.startswith("%s_" % instance.old_id))
180
- return any(valid_prefixes) and name.endswith(".geojson")
181
-
182
- def _version_metadata(self, name, instance):
183
- ref = self._extract_version_ref(name)
184
- return {
185
- "name": name,
186
- "ref": ref,
187
- "at": ref,
188
- "size": self.size(self._base_path(instance) / name),
189
- }
190
-
191
- def _purge_old_versions(self, instance, keep=None):
192
- root = self._base_path(instance)
193
- versions = self.list_versions(instance)
194
- if keep is not None:
195
- versions = versions[keep:]
196
- for version in versions:
197
- name = version["name"]
198
- # Should not be in the list, but ensure to not delete the file
199
- # currently used in database
200
- if keep is not None and instance.geojson.name.endswith(name):
201
- continue
202
- try:
203
- self.delete(root / name)
204
- except FileNotFoundError:
205
- pass
206
-
207
- def _purge_gzip(self, instance):
208
- root = self._base_path(instance)
209
- names = self.listdir(root)[1]
210
- prefixes = [f"{instance.pk}_"]
211
- if instance.old_id:
212
- prefixes.append(f"{instance.old_id}_")
213
- prefixes = tuple(prefixes)
214
- for name in names:
215
- if name.startswith(prefixes) and name.endswith(".gz"):
216
- self.delete(root / name)