Skip to content

Commit

Permalink
WIP: rename DataLayer/Map.settings to DataLayer/Map.metadata and more
Browse files Browse the repository at this point in the history
The main goal is to clarify the use of options/settings/properties…

- `settings` should be reserved to Django
- `options` should be reserved to pure Leaflet context
- `properties` used in the geojson context, for user data

Main changes:
- `DataLayer.settings` is renamed to `DataLayer.metadata`
- `DataLayer.geojson` is renamed to `Datalayer.data`
- `DataLayer.metadata` are not saved anymore in the geojson data file
- `Map.settings` is renamed to `Map.metadata`
- `Map.metadata` is now a flat key/value object, not a geojson Feature anymore
- `Map.zoom` is now populated and reused

Note: U.Map is still inheriting from Leaflet Map, so it mixes
`options` and `metadata`.

This changes is very intrusive, it changes

- the DB schema
- the way we store data in the FS (now without metadata)
- the way we backup map data

fix #1636
prepares #1335
prepares #1635
prepares #175
  • Loading branch information
yohanboniface committed Aug 27, 2024
1 parent ab8bce9 commit 787f22e
Show file tree
Hide file tree
Showing 59 changed files with 695 additions and 699 deletions.
8 changes: 4 additions & 4 deletions umap/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Meta:
class DataLayerForm(forms.ModelForm):
class Meta:
model = DataLayer
fields = ("geojson", "name", "display_on_load", "rank", "settings")
fields = ("data", "name", "display_on_load", "rank", "metadata")


class DataLayerPermissionsForm(forms.ModelForm):
Expand All @@ -78,9 +78,9 @@ class Meta:
fields = ("edit_status",)


class MapSettingsForm(forms.ModelForm):
class MapMetadataForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MapSettingsForm, self).__init__(*args, **kwargs)
super(MapMetadataForm, self).__init__(*args, **kwargs)
self.fields["slug"].required = False
self.fields["center"].widget.map_srid = 4326

Expand All @@ -102,7 +102,7 @@ def clean_center(self):
return self.cleaned_data["center"]

class Meta:
fields = ("settings", "name", "center", "slug")
fields = ("metadata", "name", "center", "slug", "zoom")
model = Map


Expand Down
28 changes: 28 additions & 0 deletions umap/migrations/0022_rename_settings_to_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 5.0.8 on 2024-08-21 10:28

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("umap", "0021_remove_map_description"),
]

operations = [
migrations.RenameField(
model_name="datalayer",
old_name="settings",
new_name="metadata",
),
migrations.RenameField(
model_name="datalayer",
old_name="geojson",
new_name="data",
),
migrations.RenameField(
model_name="map",
old_name="settings",
new_name="metadata",
),
migrations.RunSQL("UPDATE umap_map SET metadata=metadata->'properties'"),
]
75 changes: 39 additions & 36 deletions umap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ class Map(NamedModel):
default=get_default_share_status,
verbose_name=_("share status"),
)
settings = models.JSONField(
blank=True, null=True, verbose_name=_("settings"), default=dict
metadata = models.JSONField(
blank=True, null=True, verbose_name=_("metadata"), default=dict
)

objects = models.Manager()
Expand All @@ -200,18 +200,20 @@ class Map(NamedModel):
@property
def description(self):
try:
return self.settings["properties"]["description"]
return self.metadata["description"]
except KeyError:
return ""

@property
def preview_settings(self):
def geometry(self):
return {"type": "Point", "coordinates": [self.center.x, self.center.y]}

@property
def preview_metadata(self):
layers = self.datalayer_set.all()
datalayer_data = [c.metadata() for c in layers]
map_settings = self.settings
if "properties" not in map_settings:
map_settings["properties"] = {}
map_settings["properties"].update(
datalayer_data = [l.get_metadata() for l in layers]
metadata = self.metadata
metadata.update(
{
"tilelayers": [TileLayer.get_default().json],
"datalayers": datalayer_data,
Expand All @@ -224,22 +226,23 @@ def preview_settings(self):
"umap_id": self.pk,
"schema": self.extra_schema,
"slideshow": {},
"geometry": self.geometry,
}
)
return map_settings
return metadata

def generate_umapjson(self, request):
umapjson = self.settings
umapjson["type"] = "umap"
umapjson["uri"] = request.build_absolute_uri(self.get_absolute_url())
datalayers = []
umapjson = {
"metadata": {"geometry": self.geometry, **self.metadata},
"type": "umap",
"uri": request.build_absolute_uri(self.get_absolute_url()),
"layers": [],
}
for datalayer in self.datalayer_set.all():
with open(datalayer.geojson.path, "rb") as f:
with open(datalayer.data.path, "rb") as f:
layer = json.loads(f.read())
if datalayer.settings:
layer["_umap_options"] = datalayer.settings
datalayers.append(layer)
umapjson["layers"] = datalayers
layer["metadata"] = datalayer.metadata
umapjson["layers"].append(layer)
return umapjson

def get_absolute_url(self):
Expand Down Expand Up @@ -397,15 +400,15 @@ class DataLayer(NamedModel):
old_id = models.IntegerField(null=True, blank=True)
map = models.ForeignKey(Map, on_delete=models.CASCADE)
description = models.TextField(blank=True, null=True, verbose_name=_("description"))
geojson = models.FileField(upload_to=upload_to, blank=True, null=True)
data = models.FileField(upload_to=upload_to, blank=True, null=True)
display_on_load = models.BooleanField(
default=False,
verbose_name=_("display on load"),
help_text=_("Display this layer on load."),
)
rank = models.SmallIntegerField(default=0)
settings = models.JSONField(
blank=True, null=True, verbose_name=_("settings"), default=dict
metadata = models.JSONField(
blank=True, null=True, verbose_name=_("metadata"), default=dict
)
edit_status = models.SmallIntegerField(
choices=EDIT_STATUS,
Expand All @@ -423,10 +426,10 @@ def save(self, force_insert=False, force_update=False, **kwargs):
if is_new:
force_insert, force_update = False, True
filename = self.upload_to()
old_name = self.geojson.name
new_name = self.geojson.storage.save(filename, self.geojson)
self.geojson.storage.delete(old_name)
self.geojson.name = new_name
old_name = self.data.name
new_name = self.data.storage.save(filename, self.data)
self.data.storage.delete(old_name)
self.data.name = new_name
super(DataLayer, self).save(force_insert, force_update, **kwargs)
self.purge_gzip()
self.purge_old_versions()
Expand All @@ -443,10 +446,10 @@ def storage_root(self):
path.append(str(self.map.pk))
return os.path.join(*path)

def metadata(self, user=None, request=None):
def get_metadata(self, user=None, request=None):
# Retrocompat: minimal settings for maps not saved after settings property
# has been introduced
obj = self.settings or {
obj = self.metadata or {
"name": self.name,
"displayOnLoad": self.display_on_load,
}
Expand All @@ -463,7 +466,7 @@ def clone(self, map_inst=None):
new.pk = None
if map_inst:
new.map = map_inst
new.geojson = File(new.geojson.file.file)
new.data = File(new.data.file.file)
new.save()
return new

Expand All @@ -478,21 +481,21 @@ def version_metadata(self, name):
return {
"name": name,
"at": els[1],
"size": self.geojson.storage.size(self.get_version_path(name)),
"size": self.data.storage.size(self.get_version_path(name)),
}

@property
def versions(self):
root = self.storage_root()
names = self.geojson.storage.listdir(root)[1]
names = self.data.storage.listdir(root)[1]
names = [name for name in names if self.is_valid_version(name)]
versions = [self.version_metadata(name) for name in names]
versions.sort(reverse=True, key=operator.itemgetter("at"))
return versions

def get_version(self, name):
path = self.get_version_path(name)
with self.geojson.storage.open(path, "r") as f:
with self.data.storage.open(path, "r") as f:
return f.read()

def get_version_path(self, name):
Expand All @@ -505,23 +508,23 @@ def purge_old_versions(self):
name = version["name"]
# Should not be in the list, but ensure to not delete the file
# currently used in database
if self.geojson.name.endswith(name):
if self.data.name.endswith(name):
continue
try:
self.geojson.storage.delete(os.path.join(root, name))
self.data.storage.delete(os.path.join(root, name))
except FileNotFoundError:
pass

def purge_gzip(self):
root = self.storage_root()
names = self.geojson.storage.listdir(root)[1]
names = self.data.storage.listdir(root)[1]
prefixes = [f"{self.pk}_"]
if self.old_id:
prefixes.append(f"{self.old_id}_")
prefixes = tuple(prefixes)
for name in names:
if name.startswith(prefixes) and name.endswith(".gz"):
self.geojson.storage.delete(os.path.join(root, name))
self.data.storage.delete(os.path.join(root, name))

def can_edit(self, user=None, request=None):
"""
Expand Down
2 changes: 1 addition & 1 deletion umap/static/umap/js/components/fragment.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class UmapFragment extends HTMLElement {
connectedCallback() {
new U.Map(this.firstElementChild.id, JSON.parse(this.dataset.settings))
new U.Map(this.firstElementChild.id, JSON.parse(this.dataset.metadata))
}
}

Expand Down
2 changes: 1 addition & 1 deletion umap/static/umap/js/modules/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default class Browser {
DomEvent.on(toggle, 'click', toggleList)
datalayer.renderToolbox(headline)
const name = DomUtil.create('span', 'datalayer-name', headline)
name.textContent = datalayer.options.name
name.textContent = datalayer.metadata.name
DomEvent.on(name, 'click', toggleList)
container.innerHTML = ''
datalayer.eachFeature((feature) => this.addFeature(feature, container))
Expand Down
8 changes: 4 additions & 4 deletions umap/static/umap/js/modules/caption.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ export default class Caption {
}

addDataLayer(datalayer, container) {
if (!datalayer.options.inCaption) return
if (!datalayer.metadata.inCaption) return
const p = DomUtil.create('p', 'datalayer-legend', container)
const legend = DomUtil.create('span', '', p)
const headline = DomUtil.create('strong', '', p)
datalayer.renderLegend(legend)
if (datalayer.options.description) {
if (datalayer.metadata.description) {
DomUtil.element({
tagName: 'span',
parent: p,
safeHTML: Utils.toHTML(datalayer.options.description),
safeHTML: Utils.toHTML(datalayer.metadata.description),
})
}
datalayer.renderToolbox(headline)
DomUtil.add('span', '', headline, `${datalayer.options.name} `)
DomUtil.add('span', '', headline, `${datalayer.metadata.name} `)
}

addCredits(container) {
Expand Down
Loading

0 comments on commit 787f22e

Please sign in to comment.