From 9759f780adb0000d6735097280c31280183d280c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Tue, 22 Oct 2024 16:17:34 +0200 Subject: [PATCH] feat(sync): Let the clients set layers UUID This make it possible to synchronize datalayers before their creation on the server, allowing at the same time to solve issues related to them not being saved (e.g. duplication of geometries) --- umap/models.py | 2 +- umap/static/umap/js/modules/data/features.js | 2 +- umap/static/umap/js/modules/data/layer.js | 34 ++++++++++++++------ umap/static/umap/js/modules/urls.js | 4 +-- umap/urls.py | 4 +-- umap/views.py | 12 ++++++- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/umap/models.py b/umap/models.py index 8f12ade7..8c3f4e6a 100644 --- a/umap/models.py +++ b/umap/models.py @@ -444,7 +444,7 @@ class DataLayer(NamedModel): (OWNER, _("Owner only")), ) uuid = models.UUIDField( - unique=True, primary_key=True, default=uuid.uuid4, editable=False + unique=True, primary_key=True, default=uuid.uuid4, editable=True ) old_id = models.IntegerField(null=True, blank=True) map = models.ForeignKey(Map, on_delete=models.CASCADE) diff --git a/umap/static/umap/js/modules/data/features.js b/umap/static/umap/js/modules/data/features.js index 260ed28f..3b39bbe8 100644 --- a/umap/static/umap/js/modules/data/features.js +++ b/umap/static/umap/js/modules/data/features.js @@ -139,7 +139,7 @@ class Feature { subject: 'feature', metadata: { id: this.id, - layerId: this.datalayer?.umap_id || null, + layerId: this.datalayer.umap_id, featureType: this.getClassName(), }, } diff --git a/umap/static/umap/js/modules/data/layer.js b/umap/static/umap/js/modules/data/layer.js index 795a1f7d..3dc07ad6 100644 --- a/umap/static/umap/js/modules/data/layer.js +++ b/umap/static/umap/js/modules/data/layer.js @@ -36,7 +36,7 @@ const LAYER_MAP = LAYER_TYPES.reduce((acc, klass) => { }, {}) export class DataLayer { - constructor(map, data) { + constructor(map, data, sync) { this.map = map this.sync = map.sync_engine.proxy(this) this._index = Array() @@ -78,7 +78,9 @@ export class DataLayer { this.backupOptions() this.connectToMap() this.permissions = new DataLayerPermissions(this) - if (!this.umap_id) { + + if (!this.createdOnServer) { + // When importing data, show the layer immediately if applicable if (this.showAtLoad()) this.show() this.isDirty = true } @@ -89,6 +91,11 @@ export class DataLayer { if (this.isVisible()) this.propagateShow() } + get createdOnServer(){ + console.log("reference version", this._reference_version) + return this._reference_version !== undefined + } + set isDirty(status) { this._isDirty = status if (status) { @@ -119,7 +126,7 @@ export class DataLayer { return { subject: 'datalayer', metadata: { - id: this.umap_id || null, + id: this.umap_id, }, } } @@ -212,7 +219,7 @@ export class DataLayer { } async fetchData() { - if (!this.umap_id) return + if (!this.createdOnServer) return if (this._loading) return this._loading = true const [geojson, response, error] = await this.map.server.get(this._dataUrl()) @@ -304,7 +311,7 @@ export class DataLayer { } isLoaded() { - return !this.umap_id || this._loaded + return !this.createdOnServer || this._loaded } hasDataLoaded() { @@ -312,8 +319,13 @@ export class DataLayer { } setUmapId(id) { - // Datalayer is null when listening creation form + // Datalayer ID is null when listening creation form if (!this.umap_id && id) this.umap_id = id + else { + // Generate a random uuid if none is provided + this.umap_id = crypto.randomUUID() + console.log('Generating random UUID for datalayer', this.umap_id) + } } backupOptions() { @@ -571,7 +583,7 @@ export class DataLayer { } reset() { - if (!this.umap_id) this.erase() + if (!this.createdOnServer) this.erase() this.resetOptions() this.parentPane.appendChild(this.pane) @@ -1040,14 +1052,16 @@ export class DataLayer { // Filename support is shaky, don't do it for now. const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' }) formData.append('geojson', blob) - const saveUrl = this.map.urls.get('datalayer_save', { + const saveURL = this.map.urls.get('datalayer_save', { map_id: this.map.options.umap_id, pk: this.umap_id, + created: this.createdOnServer, }) + console.log("saveUrl", saveURL) const headers = this._reference_version ? { 'X-Datalayer-Reference': this._reference_version } : {} - await this._trySave(saveUrl, headers, formData) + await this._trySave(saveURL, headers, formData) this._geojson = geojson } @@ -1076,7 +1090,7 @@ export class DataLayer { this._reference_version = response.headers.get('X-Datalayer-Version') this.sync.update('_reference_version', this._reference_version) - this.setUmapId(data.id) + // this.setUmapId(data.id) this.updateOptions(data) this.backupOptions() this.connectToMap() diff --git a/umap/static/umap/js/modules/urls.js b/umap/static/umap/js/modules/urls.js index c3cb8a2a..1fcb1542 100644 --- a/umap/static/umap/js/modules/urls.js +++ b/umap/static/umap/js/modules/urls.js @@ -25,8 +25,8 @@ export default class URLs { } // Update the layer if pk is passed, create otherwise. - datalayer_save({ map_id, pk }, ...options) { - if (pk) return this.get('datalayer_update', { map_id, pk }, ...options) + datalayer_save({ map_id, pk, created}, ...options) { + if (created) return this.get('datalayer_update', { map_id, pk }, ...options) return this.get('datalayer_create', { map_id, pk }, ...options) } } diff --git a/umap/urls.py b/umap/urls.py index aaab2beb..b3afb557 100644 --- a/umap/urls.py +++ b/umap/urls.py @@ -155,8 +155,8 @@ map_urls = [ views.MapClone.as_view(), name="map_clone", ), - re_path( - r"^map/(?P[\d]+)/datalayer/create/$", + path( + "map//datalayer/create//", views.DataLayerCreate.as_view(), name="datalayer_create", ), diff --git a/umap/views.py b/umap/views.py index 5726a838..2aa418fb 100644 --- a/umap/views.py +++ b/umap/views.py @@ -13,6 +13,7 @@ from smtplib import SMTPException from urllib.error import HTTPError, URLError from urllib.parse import quote_plus, urlparse from urllib.request import Request, build_opener +from uuid import UUID from django.conf import settings from django.contrib import messages @@ -1181,8 +1182,17 @@ class DataLayerCreate(FormLessEditMixin, GZipMixin, CreateView): def form_valid(self, form): form.instance.map = self.kwargs["map_inst"] + + uuid = self.kwargs["pk"] + # Check if UUID already exists + if DataLayer.objects.filter(uuid=uuid).exists(): + return HttpResponseBadRequest("UUID already exists") + + form.instance.uuid = uuid self.object = form.save() - # Simple response with only metadata (including new id) + assert uuid == self.object.uuid + + # Simple response with only metadata response = simple_json_response(**self.object.metadata(self.request)) response["X-Datalayer-Version"] = self.version return response