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)
This commit is contained in:
Alexis Métaireau 2024-10-22 16:17:34 +02:00 committed by Yohan Boniface
parent 200e12e0d9
commit 9759f780ad
6 changed files with 41 additions and 17 deletions

View file

@ -444,7 +444,7 @@ class DataLayer(NamedModel):
(OWNER, _("Owner only")), (OWNER, _("Owner only")),
) )
uuid = models.UUIDField( 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) old_id = models.IntegerField(null=True, blank=True)
map = models.ForeignKey(Map, on_delete=models.CASCADE) map = models.ForeignKey(Map, on_delete=models.CASCADE)

View file

@ -139,7 +139,7 @@ class Feature {
subject: 'feature', subject: 'feature',
metadata: { metadata: {
id: this.id, id: this.id,
layerId: this.datalayer?.umap_id || null, layerId: this.datalayer.umap_id,
featureType: this.getClassName(), featureType: this.getClassName(),
}, },
} }

View file

@ -36,7 +36,7 @@ const LAYER_MAP = LAYER_TYPES.reduce((acc, klass) => {
}, {}) }, {})
export class DataLayer { export class DataLayer {
constructor(map, data) { constructor(map, data, sync) {
this.map = map this.map = map
this.sync = map.sync_engine.proxy(this) this.sync = map.sync_engine.proxy(this)
this._index = Array() this._index = Array()
@ -78,7 +78,9 @@ export class DataLayer {
this.backupOptions() this.backupOptions()
this.connectToMap() this.connectToMap()
this.permissions = new DataLayerPermissions(this) 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() if (this.showAtLoad()) this.show()
this.isDirty = true this.isDirty = true
} }
@ -89,6 +91,11 @@ export class DataLayer {
if (this.isVisible()) this.propagateShow() if (this.isVisible()) this.propagateShow()
} }
get createdOnServer(){
console.log("reference version", this._reference_version)
return this._reference_version !== undefined
}
set isDirty(status) { set isDirty(status) {
this._isDirty = status this._isDirty = status
if (status) { if (status) {
@ -119,7 +126,7 @@ export class DataLayer {
return { return {
subject: 'datalayer', subject: 'datalayer',
metadata: { metadata: {
id: this.umap_id || null, id: this.umap_id,
}, },
} }
} }
@ -212,7 +219,7 @@ export class DataLayer {
} }
async fetchData() { async fetchData() {
if (!this.umap_id) return if (!this.createdOnServer) return
if (this._loading) return if (this._loading) return
this._loading = true this._loading = true
const [geojson, response, error] = await this.map.server.get(this._dataUrl()) const [geojson, response, error] = await this.map.server.get(this._dataUrl())
@ -304,7 +311,7 @@ export class DataLayer {
} }
isLoaded() { isLoaded() {
return !this.umap_id || this._loaded return !this.createdOnServer || this._loaded
} }
hasDataLoaded() { hasDataLoaded() {
@ -312,8 +319,13 @@ export class DataLayer {
} }
setUmapId(id) { 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 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() { backupOptions() {
@ -571,7 +583,7 @@ export class DataLayer {
} }
reset() { reset() {
if (!this.umap_id) this.erase() if (!this.createdOnServer) this.erase()
this.resetOptions() this.resetOptions()
this.parentPane.appendChild(this.pane) this.parentPane.appendChild(this.pane)
@ -1040,14 +1052,16 @@ export class DataLayer {
// Filename support is shaky, don't do it for now. // Filename support is shaky, don't do it for now.
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' }) const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
formData.append('geojson', blob) 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, map_id: this.map.options.umap_id,
pk: this.umap_id, pk: this.umap_id,
created: this.createdOnServer,
}) })
console.log("saveUrl", saveURL)
const headers = this._reference_version const headers = this._reference_version
? { 'X-Datalayer-Reference': this._reference_version } ? { 'X-Datalayer-Reference': this._reference_version }
: {} : {}
await this._trySave(saveUrl, headers, formData) await this._trySave(saveURL, headers, formData)
this._geojson = geojson this._geojson = geojson
} }
@ -1076,7 +1090,7 @@ export class DataLayer {
this._reference_version = response.headers.get('X-Datalayer-Version') this._reference_version = response.headers.get('X-Datalayer-Version')
this.sync.update('_reference_version', this._reference_version) this.sync.update('_reference_version', this._reference_version)
this.setUmapId(data.id) // this.setUmapId(data.id)
this.updateOptions(data) this.updateOptions(data)
this.backupOptions() this.backupOptions()
this.connectToMap() this.connectToMap()

View file

@ -25,8 +25,8 @@ export default class URLs {
} }
// Update the layer if pk is passed, create otherwise. // Update the layer if pk is passed, create otherwise.
datalayer_save({ map_id, pk }, ...options) { datalayer_save({ map_id, pk, created}, ...options) {
if (pk) return this.get('datalayer_update', { map_id, pk }, ...options) if (created) return this.get('datalayer_update', { map_id, pk }, ...options)
return this.get('datalayer_create', { map_id, pk }, ...options) return this.get('datalayer_create', { map_id, pk }, ...options)
} }
} }

View file

@ -155,8 +155,8 @@ map_urls = [
views.MapClone.as_view(), views.MapClone.as_view(),
name="map_clone", name="map_clone",
), ),
re_path( path(
r"^map/(?P<map_id>[\d]+)/datalayer/create/$", "map/<int:map_id>/datalayer/create/<uuid:pk>/",
views.DataLayerCreate.as_view(), views.DataLayerCreate.as_view(),
name="datalayer_create", name="datalayer_create",
), ),

View file

@ -13,6 +13,7 @@ from smtplib import SMTPException
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.parse import quote_plus, urlparse from urllib.parse import quote_plus, urlparse
from urllib.request import Request, build_opener from urllib.request import Request, build_opener
from uuid import UUID
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
@ -1181,8 +1182,17 @@ class DataLayerCreate(FormLessEditMixin, GZipMixin, CreateView):
def form_valid(self, form): def form_valid(self, form):
form.instance.map = self.kwargs["map_inst"] 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() 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 = simple_json_response(**self.object.metadata(self.request))
response["X-Datalayer-Version"] = self.version response["X-Datalayer-Version"] = self.version
return response return response