Store DataLayer's settings in DB

This allows to known the full datalayer behaviour without needing
to load all the data, including the zoom from and to (new settings),
but also the color for example.

This will help also understanding datalayers usage and making
stats.

But no data migration is provided, it's retrocompatible (data
migration in OSM FR servers would be huge, so let's see if it's
really needed).
This commit is contained in:
Yohan Boniface 2023-08-20 09:48:01 +02:00
parent bb922d1418
commit fa090b89df
6 changed files with 79 additions and 40 deletions

View file

@ -59,7 +59,7 @@ class DataLayerForm(forms.ModelForm):
class Meta:
model = DataLayer
fields = ('geojson', 'name', 'display_on_load', 'rank')
fields = ('geojson', 'name', 'display_on_load', 'rank', 'settings')
class MapSettingsForm(forms.ModelForm):

View file

@ -0,0 +1,19 @@
# Generated by Django 4.2.2 on 2023-08-16 05:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("umap", "0011_alter_map_edit_status_alter_map_share_status"),
]
operations = [
migrations.AddField(
model_name="datalayer",
name="settings",
field=models.JSONField(
blank=True, default=dict, null=True, verbose_name="settings"
),
),
]

View file

@ -176,10 +176,14 @@ class Map(NamedModel):
settings.AUTH_USER_MODEL, blank=True, verbose_name=_("editors")
)
edit_status = models.SmallIntegerField(
choices=EDIT_STATUS, default=get_default_edit_status, verbose_name=_("edit status")
choices=EDIT_STATUS,
default=get_default_edit_status,
verbose_name=_("edit status"),
)
share_status = models.SmallIntegerField(
choices=SHARE_STATUS, default=get_default_share_status, verbose_name=_("share status")
choices=SHARE_STATUS,
default=get_default_share_status,
verbose_name=_("share status"),
)
settings = models.JSONField(
blank=True, null=True, verbose_name=_("settings"), default=dict
@ -308,6 +312,9 @@ class DataLayer(NamedModel):
help_text=_("Display this layer on load."),
)
rank = models.SmallIntegerField(default=0)
settings = models.JSONField(
blank=True, null=True, verbose_name=_("settings"), default=dict
)
class Meta:
ordering = ("rank",)
@ -340,7 +347,14 @@ class DataLayer(NamedModel):
@property
def metadata(self):
return {"name": self.name, "id": self.pk, "displayOnLoad": self.display_on_load}
# Retrocompat: minimal settings for maps not saved after settings property
# has been introduced
obj = self.settings or {
"name": self.name,
"displayOnLoad": self.display_on_load,
}
obj["id"] = self.pk
return obj
def clone(self, map_inst=None):
new = self.__class__.objects.get(pk=self.pk)

View file

@ -248,11 +248,6 @@ L.U.DataLayer = L.Evented.extend({
}
this.setUmapId(data.id)
this.setOptions(data)
this.backupOptions()
this.connectToMap()
if (this.displayedOnLoad()) this.show()
if (!this.umap_id) this.isDirty = true
// Retrocompat
if (this.options.remoteData && this.options.remoteData.from) {
this.options.fromZoom = this.options.remoteData.from
@ -260,11 +255,15 @@ L.U.DataLayer = L.Evented.extend({
if (this.options.remoteData && this.options.remoteData.to) {
this.options.toZoom = this.options.remoteData.to
}
this.backupOptions()
this.connectToMap()
if (this.displayedOnLoad() && this.showAtZoom()) this.show()
if (!this.umap_id) this.isDirty = true
this.onceLoaded(function () {
this.map.on('moveend', this.onMoveEnd, this)
this.map.on('zoomend', this.onZoomEnd, this)
})
this.map.on('zoomend', this.onZoomEnd, this)
},
onMoveEnd: function (e) {
@ -1185,6 +1184,7 @@ L.U.DataLayer = L.Evented.extend({
formData.append('name', this.options.name)
formData.append('display_on_load', !!this.options.displayOnLoad)
formData.append('rank', this.getRank())
formData.append('settings', JSON.stringify(this.options))
// Filename support is shaky, don't do it for now.
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
formData.append('geojson', blob)

View file

@ -27,10 +27,11 @@ class TileLayerFactory(factory.django.DjangoModelFactory):
class UserFactory(factory.django.DjangoModelFactory):
username = 'Joe'
username = "Joe"
email = factory.LazyAttribute(
lambda a: '{0}@example.com'.format(a.username).lower())
password = factory.PostGenerationMethodCall('set_password', '123123')
lambda a: "{0}@example.com".format(a.username).lower()
)
password = factory.PostGenerationMethodCall("set_password", "123123")
class Meta:
model = User
@ -41,32 +42,32 @@ class MapFactory(factory.django.DjangoModelFactory):
slug = "test-map"
center = DEFAULT_CENTER
settings = {
'geometry': {
'coordinates': [13.447265624999998, 48.94415123418794],
'type': 'Point'
"geometry": {
"coordinates": [13.447265624999998, 48.94415123418794],
"type": "Point",
},
'properties': {
'datalayersControl': True,
'description': 'Which is just the Danube, at the end',
'displayCaptionOnLoad': False,
'displayDataBrowserOnLoad': False,
'displayPopupFooter': False,
'licence': '',
'miniMap': False,
'moreControl': True,
'name': 'Cruising on the Donau',
'scaleControl': True,
'tilelayer': {
'attribution': u'\xa9 OSM Contributors',
'maxZoom': 18,
'minZoom': 0,
'url_template': 'http://{s}.osm.fr/{z}/{x}/{y}.png'
"properties": {
"datalayersControl": True,
"description": "Which is just the Danube, at the end",
"displayCaptionOnLoad": False,
"displayDataBrowserOnLoad": False,
"displayPopupFooter": False,
"licence": "",
"miniMap": False,
"moreControl": True,
"name": "Cruising on the Donau",
"scaleControl": True,
"tilelayer": {
"attribution": "\xa9 OSM Contributors",
"maxZoom": 18,
"minZoom": 0,
"url_template": "http://{s}.osm.fr/{z}/{x}/{y}.png",
},
'tilelayersControl': True,
'zoom': 7,
'zoomControl': True
"tilelayersControl": True,
"zoom": 7,
"zoomControl": True,
},
'type': 'Feature'
"type": "Feature",
}
licence = factory.SubFactory(LicenceFactory)
@ -81,7 +82,10 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
name = "test datalayer"
description = "test description"
display_on_load = True
geojson = factory.django.FileField(data="""{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[13.68896484375,48.55297816440071]},"properties":{"_umap_options":{"color":"DarkCyan","iconClass":"Ball"},"name":"Here","description":"Da place anonymous again 755"}}],"_umap_options":{"displayOnLoad":true,"name":"Donau","id":926}}""") # noqa
settings = {"displayOnLoad": True, "browsable": True, name: "test datalayer"}
geojson = factory.django.FileField(
data="""{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[13.68896484375,48.55297816440071]},"properties":{"_umap_options":{"color":"DarkCyan","iconClass":"Ball"},"name":"Here","description":"Da place anonymous again 755"}}],"_umap_options":{"displayOnLoad":true,"name":"Donau","id":926}}"""
) # noqa
class Meta:
model = DataLayer
@ -90,7 +94,7 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
def login_required(response):
assert response.status_code == 200
j = json.loads(response.content.decode())
assert 'login_required' in j
redirect_url = reverse('login')
assert j['login_required'] == redirect_url
assert "login_required" in j
redirect_url = reverse("login")
assert j["login_required"] == redirect_url
return True

View file

@ -17,6 +17,7 @@ def post_data():
return {
"name": "name",
"display_on_load": True,
"settings": '{"displayOnLoad": true, "browsable": true, "name": "name"}',
"rank": 0,
"geojson": '{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-3.1640625,53.014783245859235],[-3.1640625,51.86292391360244],[-0.50537109375,51.385495069223204],[1.16455078125,52.38901106223456],[-0.41748046875,53.91728101547621],[-2.109375,53.85252660044951],[-3.1640625,53.014783245859235]]]},"properties":{"_umap_options":{},"name":"Ho god, sounds like a polygouine"}},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.8017578124999998,51.16556659836182],[-0.48339843749999994,49.710272582105695],[-3.1640625,50.0923932109388],[-5.60302734375,51.998410382390325]]},"properties":{"_umap_options":{},"name":"Light line"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[0.63720703125,51.15178610143037]},"properties":{"_umap_options":{},"name":"marker he"}}],"_umap_options":{"displayOnLoad":true,"name":"new name","id":1668,"remoteData":{},"color":"LightSeaGreen","description":"test"}}',
}
@ -61,6 +62,7 @@ def test_update(client, datalayer, map, post_data):
j = json.loads(response.content.decode())
assert "id" in j
assert datalayer.pk == j["id"]
assert j["browsable"] is True
assert Path(modified_datalayer.geojson.path).exists()