mirror of
https://github.com/umap-project/umap.git
synced 2025-05-04 21:51:50 +02:00
WIP: rename DataLayer/Map.settings to DataLayer/Map.metadata and more
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
This commit is contained in:
parent
ab8bce985e
commit
787f22efd4
59 changed files with 695 additions and 699 deletions
|
@ -55,7 +55,7 @@ class AnonymousMapPermissionsForm(forms.ModelForm):
|
|||
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):
|
||||
|
@ -78,9 +78,9 @@ class AnonymousDataLayerPermissionsForm(forms.ModelForm):
|
|||
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
|
||||
|
||||
|
@ -102,7 +102,7 @@ class MapSettingsForm(forms.ModelForm):
|
|||
return self.cleaned_data["center"]
|
||||
|
||||
class Meta:
|
||||
fields = ("settings", "name", "center", "slug")
|
||||
fields = ("metadata", "name", "center", "slug", "zoom")
|
||||
model = Map
|
||||
|
||||
|
||||
|
|
28
umap/migrations/0022_rename_settings_to_metadata.py
Normal file
28
umap/migrations/0022_rename_settings_to_metadata.py
Normal file
|
@ -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'"),
|
||||
]
|
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -224,22 +226,23 @@ class Map(NamedModel):
|
|||
"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):
|
||||
|
@ -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,
|
||||
|
@ -423,10 +426,10 @@ class DataLayer(NamedModel):
|
|||
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()
|
||||
|
@ -443,10 +446,10 @@ class DataLayer(NamedModel):
|
|||
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,
|
||||
}
|
||||
|
@ -463,7 +466,7 @@ class DataLayer(NamedModel):
|
|||
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
|
||||
|
||||
|
@ -478,13 +481,13 @@ class DataLayer(NamedModel):
|
|||
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"))
|
||||
|
@ -492,7 +495,7 @@ class DataLayer(NamedModel):
|
|||
|
||||
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):
|
||||
|
@ -505,23 +508,23 @@ class DataLayer(NamedModel):
|
|||
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):
|
||||
"""
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -26,15 +26,14 @@ class Feature {
|
|||
|
||||
// DataLayer the feature belongs to
|
||||
this.datalayer = datalayer
|
||||
this.properties = { _umap_options: {}, ...(geojson.properties || {}) }
|
||||
this.properties = { ...(geojson.properties || {}) }
|
||||
this.metadata = {}
|
||||
this.staticOptions = {}
|
||||
|
||||
if (geojson.coordinates) {
|
||||
geojson = { geometry: geojson }
|
||||
}
|
||||
if (geojson.geometry) {
|
||||
this.populate(geojson)
|
||||
}
|
||||
this.populate(geojson)
|
||||
|
||||
if (id) {
|
||||
this.id = id
|
||||
|
@ -187,7 +186,7 @@ class Feature {
|
|||
window.top.location = outlink
|
||||
break
|
||||
default:
|
||||
window.open(this.properties._umap_options.outlink)
|
||||
window.open(this.metadata.outlink)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -298,13 +297,13 @@ class Feature {
|
|||
|
||||
getInteractionOptions() {
|
||||
return [
|
||||
'properties._umap_options.popupShape',
|
||||
'properties._umap_options.popupTemplate',
|
||||
'properties._umap_options.showLabel',
|
||||
'properties._umap_options.labelDirection',
|
||||
'properties._umap_options.labelInteractive',
|
||||
'properties._umap_options.outlink',
|
||||
'properties._umap_options.outlinkTarget',
|
||||
'metadata.popupShape',
|
||||
'metadata.popupTemplate',
|
||||
'metadata.showLabel',
|
||||
'metadata.labelDirection',
|
||||
'metadata.labelInteractive',
|
||||
'metadata.outlink',
|
||||
'metadata.outlinkTarget',
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -321,7 +320,7 @@ class Feature {
|
|||
}
|
||||
|
||||
hasPopupFooter() {
|
||||
if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) {
|
||||
if (this.datalayer.isRemoteLayer() && this.datalayer.metadata.remoteData.dynamic) {
|
||||
return false
|
||||
}
|
||||
return this.map.getOption('displayPopupFooter')
|
||||
|
@ -375,20 +374,24 @@ class Feature {
|
|||
return [key, value]
|
||||
}
|
||||
|
||||
populate(geojson) {
|
||||
populate(geojson = {}) {
|
||||
this._geometry = geojson.geometry
|
||||
this.properties = Object.fromEntries(
|
||||
Object.entries(geojson.properties || {}).map(this.cleanProperty)
|
||||
)
|
||||
this.properties._umap_options = L.extend(
|
||||
this.metadata = L.extend(
|
||||
{},
|
||||
this.properties._storage_options,
|
||||
this.properties._umap_options
|
||||
this.properties._umap_options,
|
||||
geojson.metadata
|
||||
)
|
||||
// Legacy
|
||||
delete this.properties._umap_options
|
||||
delete this.properties._storage_options
|
||||
// Retrocompat
|
||||
if (this.properties._umap_options.clickable === false) {
|
||||
this.properties._umap_options.interactive = false
|
||||
delete this.properties._umap_options.clickable
|
||||
if (this.metadata.clickable === false) {
|
||||
this.metadata.interactive = false
|
||||
delete this.metadata.clickable
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,8 +411,8 @@ class Feature {
|
|||
let value = fallback
|
||||
if (typeof this.staticOptions[option] !== 'undefined') {
|
||||
value = this.staticOptions[option]
|
||||
} else if (U.Utils.usableOption(this.properties._umap_options, option)) {
|
||||
value = this.properties._umap_options[option]
|
||||
} else if (U.Utils.usableOption(this.metadata, option)) {
|
||||
value = this.metadata[option]
|
||||
} else if (this.datalayer) {
|
||||
value = this.datalayer.getOption(option, this)
|
||||
} else {
|
||||
|
@ -451,15 +454,12 @@ class Feature {
|
|||
return this.datalayer.getPreviousFeature(this)
|
||||
}
|
||||
|
||||
cloneMetadata() {
|
||||
return L.extend({}, this.metadata)
|
||||
}
|
||||
|
||||
cloneProperties() {
|
||||
const properties = L.extend({}, this.properties)
|
||||
properties._umap_options = L.extend({}, properties._umap_options)
|
||||
if (Object.keys && Object.keys(properties._umap_options).length === 0) {
|
||||
delete properties._umap_options // It can make a difference on big data sets
|
||||
}
|
||||
// Legacy
|
||||
delete properties._storage_options
|
||||
return properties
|
||||
return L.extend({}, this.properties)
|
||||
}
|
||||
|
||||
deleteProperty(property) {
|
||||
|
@ -473,10 +473,16 @@ class Feature {
|
|||
}
|
||||
|
||||
toGeoJSON() {
|
||||
return Utils.CopyJSON({
|
||||
let metadata = this.cloneMetadata()
|
||||
if (!Object.keys(metadata).length) {
|
||||
// Remove empty object from exported json
|
||||
metadata = undefined
|
||||
}
|
||||
return Utils.copyJSON({
|
||||
type: 'Feature',
|
||||
geometry: this.geometry,
|
||||
properties: this.cloneProperties(),
|
||||
metadata: metadata,
|
||||
id: this.id,
|
||||
})
|
||||
}
|
||||
|
@ -615,15 +621,15 @@ export class Point extends Feature {
|
|||
|
||||
getShapeOptions() {
|
||||
return [
|
||||
'properties._umap_options.color',
|
||||
'properties._umap_options.iconClass',
|
||||
'properties._umap_options.iconUrl',
|
||||
'properties._umap_options.iconOpacity',
|
||||
'metadata.color',
|
||||
'metadata.iconClass',
|
||||
'metadata.iconUrl',
|
||||
'metadata.iconOpacity',
|
||||
]
|
||||
}
|
||||
|
||||
getAdvancedOptions() {
|
||||
return ['properties._umap_options.zoomTo']
|
||||
return ['metadata.zoomTo']
|
||||
}
|
||||
|
||||
appendEditFieldsets(container) {
|
||||
|
@ -695,19 +701,11 @@ class Path extends Feature {
|
|||
}
|
||||
|
||||
getShapeOptions() {
|
||||
return [
|
||||
'properties._umap_options.color',
|
||||
'properties._umap_options.opacity',
|
||||
'properties._umap_options.weight',
|
||||
]
|
||||
return ['metadata.color', 'metadata.opacity', 'metadata.weight']
|
||||
}
|
||||
|
||||
getAdvancedOptions() {
|
||||
return [
|
||||
'properties._umap_options.smoothFactor',
|
||||
'properties._umap_options.dashArray',
|
||||
'properties._umap_options.zoomTo',
|
||||
]
|
||||
return ['metadata.smoothFactor', 'metadata.dashArray', 'metadata.zoomTo']
|
||||
}
|
||||
|
||||
getBestZoom() {
|
||||
|
@ -732,9 +730,10 @@ class Path extends Feature {
|
|||
|
||||
isolateShape(latlngs) {
|
||||
const properties = this.cloneProperties()
|
||||
const metadata = this.cloneMetadata()
|
||||
const type = this instanceof LineString ? 'LineString' : 'Polygon'
|
||||
const geometry = this.convertLatLngs(latlngs)
|
||||
const other = this.datalayer.makeFeature({ type, geometry, properties })
|
||||
const other = this.datalayer.makeFeature({ type, geometry, properties, metadata })
|
||||
other.edit()
|
||||
return other
|
||||
}
|
||||
|
@ -919,10 +918,10 @@ export class Polygon extends Path {
|
|||
getShapeOptions() {
|
||||
const options = super.getShapeOptions()
|
||||
options.push(
|
||||
'properties._umap_options.stroke',
|
||||
'properties._umap_options.fill',
|
||||
'properties._umap_options.fillColor',
|
||||
'properties._umap_options.fillOpacity'
|
||||
'metadata.stroke',
|
||||
'metadata.fill',
|
||||
'metadata.fillColor',
|
||||
'metadata.fillOpacity'
|
||||
)
|
||||
return options
|
||||
}
|
||||
|
@ -937,7 +936,7 @@ export class Polygon extends Path {
|
|||
|
||||
getInteractionOptions() {
|
||||
const options = super.getInteractionOptions()
|
||||
options.push('properties._umap_options.interactive')
|
||||
options.push('metadata.interactive')
|
||||
return options
|
||||
}
|
||||
|
||||
|
@ -956,7 +955,7 @@ export class Polygon extends Path {
|
|||
|
||||
getAdvancedOptions() {
|
||||
const actions = super.getAdvancedOptions()
|
||||
actions.push('properties._umap_options.mask')
|
||||
actions.push('metadata.mask')
|
||||
return actions
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ export class DataLayer {
|
|||
this.pane.dataset.id = stamp(this)
|
||||
// FIXME: should be on layer
|
||||
this.renderer = L.svg({ pane: this.pane })
|
||||
this.defaultOptions = {
|
||||
this.defaultMetadata = {
|
||||
displayOnLoad: true,
|
||||
inCaption: true,
|
||||
browsable: true,
|
||||
|
@ -61,21 +61,21 @@ export class DataLayer {
|
|||
this._isDirty = false
|
||||
this._isDeleted = false
|
||||
this.setUmapId(data.id)
|
||||
this.setOptions(data)
|
||||
this.setMetadata(data)
|
||||
|
||||
if (!Utils.isObject(this.options.remoteData)) {
|
||||
this.options.remoteData = {}
|
||||
if (!Utils.isObject(this.metadata.remoteData)) {
|
||||
this.metadata.remoteData = {}
|
||||
}
|
||||
// Retrocompat
|
||||
if (this.options.remoteData?.from) {
|
||||
this.options.fromZoom = this.options.remoteData.from
|
||||
delete this.options.remoteData.from
|
||||
if (this.metadata.remoteData?.from) {
|
||||
this.metadata.fromZoom = this.metadata.remoteData.from
|
||||
delete this.metadata.remoteData.from
|
||||
}
|
||||
if (this.options.remoteData?.to) {
|
||||
this.options.toZoom = this.options.remoteData.to
|
||||
delete this.options.remoteData.to
|
||||
if (this.metadata.remoteData?.to) {
|
||||
this.metadata.toZoom = this.metadata.remoteData.to
|
||||
delete this.metadata.remoteData.to
|
||||
}
|
||||
this.backupOptions()
|
||||
this.backupMetadata()
|
||||
this.connectToMap()
|
||||
this.permissions = new DataLayerPermissions(this)
|
||||
if (!this.umap_id) {
|
||||
|
@ -133,7 +133,7 @@ export class DataLayer {
|
|||
this.map.onDataLayersChanged()
|
||||
break
|
||||
case 'data':
|
||||
if (fields.includes('options.type')) {
|
||||
if (fields.includes('metadata.type')) {
|
||||
this.resetLayer()
|
||||
}
|
||||
this.hide()
|
||||
|
@ -155,11 +155,11 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
autoLoaded() {
|
||||
if (!this.map.datalayersFromQueryString) return this.options.displayOnLoad
|
||||
if (!this.map.datalayersFromQueryString) return this.metadata.displayOnLoad
|
||||
const datalayerIds = this.map.datalayersFromQueryString
|
||||
let loadMe = datalayerIds.includes(this.umap_id.toString())
|
||||
if (this.options.old_id) {
|
||||
loadMe = loadMe || datalayerIds.includes(this.options.old_id.toString())
|
||||
if (this.metadata.old_id) {
|
||||
loadMe = loadMe || datalayerIds.includes(this.metadata.old_id.toString())
|
||||
}
|
||||
return loadMe
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ export class DataLayer {
|
|||
// Only reset if type is defined (undefined is the default) and different from current type
|
||||
if (
|
||||
this.layer &&
|
||||
(!this.options.type || this.options.type === this.layer.getType()) &&
|
||||
(!this.metadata.type || this.metadata.type === this.layer.getType()) &&
|
||||
!force
|
||||
) {
|
||||
return
|
||||
|
@ -195,7 +195,7 @@ export class DataLayer {
|
|||
if (this.layer) this.layer.clearLayers()
|
||||
// delete this.layer?
|
||||
if (visible) this.map.removeLayer(this.layer)
|
||||
const Class = LAYER_MAP[this.options.type] || DefaultLayer
|
||||
const Class = LAYER_MAP[this.metadata.type] || DefaultLayer
|
||||
this.layer = new Class(this)
|
||||
// Rendering layer changed, so let's force reset the feature rendering too.
|
||||
this.eachFeature((feature) => feature.makeUI())
|
||||
|
@ -218,18 +218,8 @@ export class DataLayer {
|
|||
const [geojson, response, error] = await this.map.server.get(this._dataUrl())
|
||||
if (!error) {
|
||||
this._reference_version = response.headers.get('X-Datalayer-Version')
|
||||
// FIXME: for now this property is set dynamically from backend
|
||||
// And thus it's not in the geojson file in the server
|
||||
// So do not let all options to be reset
|
||||
// Fix is a proper migration so all datalayers settings are
|
||||
// in DB, and we remove it from geojson flat files.
|
||||
if (geojson._umap_options) {
|
||||
geojson._umap_options.editMode = this.options.editMode
|
||||
}
|
||||
// In case of maps pre 1.0 still around
|
||||
if (geojson._storage) geojson._storage.editMode = this.options.editMode
|
||||
await this.fromUmapGeoJSON(geojson)
|
||||
this.backupOptions()
|
||||
this.backupMetadata()
|
||||
this._loading = false
|
||||
}
|
||||
}
|
||||
|
@ -247,8 +237,11 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
async fromUmapGeoJSON(geojson) {
|
||||
if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
|
||||
if (geojson._umap_options) this.setOptions(geojson._umap_options)
|
||||
if (!geojson.metadata) {
|
||||
// Retrocompat
|
||||
geojson.metadata = geojson._umap_options || geojson._storage
|
||||
}
|
||||
if (geojson.metadata) this.setMetadata(geojson.metadata)
|
||||
if (this.isRemoteLayer()) await this.fetchRemoteData()
|
||||
else this.fromGeoJSON(geojson, false)
|
||||
this._loaded = true
|
||||
|
@ -266,7 +259,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
backupData() {
|
||||
this._geojson_bk = Utils.CopyJSON(this._geojson)
|
||||
this._geojson_bk = Utils.copyJSON(this._geojson)
|
||||
}
|
||||
|
||||
reindex() {
|
||||
|
@ -276,29 +269,29 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
showAtZoom() {
|
||||
const from = Number.parseInt(this.options.fromZoom, 10)
|
||||
const to = Number.parseInt(this.options.toZoom, 10)
|
||||
const from = Number.parseInt(this.metadata.fromZoom, 10)
|
||||
const to = Number.parseInt(this.metadata.toZoom, 10)
|
||||
const zoom = this.map.getZoom()
|
||||
return !((!Number.isNaN(from) && zoom < from) || (!Number.isNaN(to) && zoom > to))
|
||||
}
|
||||
|
||||
hasDynamicData() {
|
||||
return !!this.options.remoteData?.dynamic
|
||||
return !!this.metadata.remoteData?.dynamic
|
||||
}
|
||||
|
||||
async fetchRemoteData(force) {
|
||||
if (!this.isRemoteLayer()) return
|
||||
if (!this.hasDynamicData() && this.hasDataLoaded() && !force) return
|
||||
if (!this.isVisible()) return
|
||||
let url = this.map.localizeUrl(this.options.remoteData.url)
|
||||
if (this.options.remoteData.proxy) {
|
||||
url = this.map.proxyUrl(url, this.options.remoteData.ttl)
|
||||
let url = this.map.localizeUrl(this.metadata.remoteData.url)
|
||||
if (this.metadata.remoteData.proxy) {
|
||||
url = this.map.proxyUrl(url, this.metadata.remoteData.ttl)
|
||||
}
|
||||
const response = await this.map.request.get(url)
|
||||
if (response?.ok) {
|
||||
this.clear()
|
||||
this.map.formatter
|
||||
.parse(await response.text(), this.options.remoteData.format)
|
||||
.parse(await response.text(), this.metadata.remoteData.format)
|
||||
.then((geojson) => this.fromGeoJSON(geojson))
|
||||
}
|
||||
}
|
||||
|
@ -316,22 +309,22 @@ export class DataLayer {
|
|||
if (!this.umap_id && id) this.umap_id = id
|
||||
}
|
||||
|
||||
backupOptions() {
|
||||
this._backupOptions = Utils.CopyJSON(this.options)
|
||||
backupMetadata() {
|
||||
this._backupMetadata = Utils.copyJSON(this.metadata)
|
||||
}
|
||||
|
||||
resetOptions() {
|
||||
this.options = Utils.CopyJSON(this._backupOptions)
|
||||
resetMetadata() {
|
||||
this.metadata = Utils.copyJSON(this._backupMetadata)
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
delete options.geojson
|
||||
this.options = Utils.CopyJSON(this.defaultOptions) // Start from fresh.
|
||||
this.updateOptions(options)
|
||||
setMetadata(metadata) {
|
||||
delete metadata.geojson
|
||||
this.metadata = Utils.copyJSON(this.defaultMetadata) // Start from fresh.
|
||||
this.updateMetadata(metadata)
|
||||
}
|
||||
|
||||
updateOptions(options) {
|
||||
this.options = Object.assign(this.options, options)
|
||||
updateMetadata(metadata) {
|
||||
this.metadata = Object.assign(this.metadata, metadata)
|
||||
this.resetLayer()
|
||||
}
|
||||
|
||||
|
@ -360,11 +353,11 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
isRemoteLayer() {
|
||||
return Boolean(this.options.remoteData?.url && this.options.remoteData.format)
|
||||
return Boolean(this.metadata.remoteData?.url && this.metadata.remoteData.format)
|
||||
}
|
||||
|
||||
isClustered() {
|
||||
return this.options.type === 'Cluster'
|
||||
return this.metadata.type === 'Cluster'
|
||||
}
|
||||
|
||||
showFeature(feature) {
|
||||
|
@ -511,7 +504,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
getColor() {
|
||||
return this.options.color || this.map.getOption('color')
|
||||
return this.metadata.color || this.map.getOption('color')
|
||||
}
|
||||
|
||||
getDeleteUrl() {
|
||||
|
@ -548,11 +541,11 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
clone() {
|
||||
const options = Utils.CopyJSON(this.options)
|
||||
options.name = translate('Clone of {name}', { name: this.options.name })
|
||||
delete options.id
|
||||
const geojson = Utils.CopyJSON(this._geojson)
|
||||
const datalayer = this.map.createDataLayer(options)
|
||||
const metadata = Utils.copyJSON(this.metadata)
|
||||
metadata.name = translate('Clone of {name}', { name: this.metadata.name })
|
||||
delete metadata.id
|
||||
const geojson = Utils.copyJSON(this._geojson)
|
||||
const datalayer = this.map.createDataLayer(metadata)
|
||||
datalayer.fromGeoJSON(geojson)
|
||||
return datalayer
|
||||
}
|
||||
|
@ -574,7 +567,7 @@ export class DataLayer {
|
|||
reset() {
|
||||
if (!this.umap_id) this.erase()
|
||||
|
||||
this.resetOptions()
|
||||
this.resetMetadata()
|
||||
this.parentPane.appendChild(this.pane)
|
||||
if (this._leaflet_events_bk && !this._leaflet_events) {
|
||||
this._leaflet_events = this._leaflet_events_bk
|
||||
|
@ -600,18 +593,18 @@ export class DataLayer {
|
|||
}
|
||||
const container = DomUtil.create('div', 'umap-layer-properties-container')
|
||||
const metadataFields = [
|
||||
'options.name',
|
||||
'options.description',
|
||||
'metadata.name',
|
||||
'metadata.description',
|
||||
[
|
||||
'options.type',
|
||||
'metadata.type',
|
||||
{ handler: 'LayerTypeChooser', label: translate('Type of layer') },
|
||||
],
|
||||
[
|
||||
'options.displayOnLoad',
|
||||
'metadata.displayOnLoad',
|
||||
{ label: translate('Display on load'), handler: 'Switch' },
|
||||
],
|
||||
[
|
||||
'options.browsable',
|
||||
'metadata.browsable',
|
||||
{
|
||||
label: translate('Data is browsable'),
|
||||
handler: 'Switch',
|
||||
|
@ -619,7 +612,7 @@ export class DataLayer {
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.inCaption',
|
||||
'metadata.inCaption',
|
||||
{
|
||||
label: translate('Show this layer in the caption'),
|
||||
handler: 'Switch',
|
||||
|
@ -630,7 +623,7 @@ export class DataLayer {
|
|||
let builder = new U.FormBuilder(this, metadataFields, {
|
||||
callback(e) {
|
||||
this.map.onDataLayersChanged()
|
||||
if (e.helper.field === 'options.type') {
|
||||
if (e.helper.field === 'metadata.type') {
|
||||
this.edit()
|
||||
}
|
||||
},
|
||||
|
@ -651,16 +644,16 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
const shapeOptions = [
|
||||
'options.color',
|
||||
'options.iconClass',
|
||||
'options.iconUrl',
|
||||
'options.iconOpacity',
|
||||
'options.opacity',
|
||||
'options.stroke',
|
||||
'options.weight',
|
||||
'options.fill',
|
||||
'options.fillColor',
|
||||
'options.fillOpacity',
|
||||
'metadata.color',
|
||||
'metadata.iconClass',
|
||||
'metadata.iconUrl',
|
||||
'metadata.iconOpacity',
|
||||
'metadata.opacity',
|
||||
'metadata.stroke',
|
||||
'metadata.weight',
|
||||
'metadata.fill',
|
||||
'metadata.fillColor',
|
||||
'metadata.fillOpacity',
|
||||
]
|
||||
|
||||
builder = new U.FormBuilder(this, shapeOptions, {
|
||||
|
@ -673,12 +666,12 @@ export class DataLayer {
|
|||
shapeProperties.appendChild(builder.build())
|
||||
|
||||
const optionsFields = [
|
||||
'options.smoothFactor',
|
||||
'options.dashArray',
|
||||
'options.zoomTo',
|
||||
'options.fromZoom',
|
||||
'options.toZoom',
|
||||
'options.labelKey',
|
||||
'metadata.smoothFactor',
|
||||
'metadata.dashArray',
|
||||
'metadata.zoomTo',
|
||||
'metadata.fromZoom',
|
||||
'metadata.toZoom',
|
||||
'metadata.labelKey',
|
||||
]
|
||||
|
||||
builder = new U.FormBuilder(this, optionsFields, {
|
||||
|
@ -691,14 +684,14 @@ export class DataLayer {
|
|||
advancedProperties.appendChild(builder.build())
|
||||
|
||||
const popupFields = [
|
||||
'options.popupShape',
|
||||
'options.popupTemplate',
|
||||
'options.popupContentTemplate',
|
||||
'options.showLabel',
|
||||
'options.labelDirection',
|
||||
'options.labelInteractive',
|
||||
'options.outlinkTarget',
|
||||
'options.interactive',
|
||||
'metadata.popupShape',
|
||||
'metadata.popupTemplate',
|
||||
'metadata.popupContentTemplate',
|
||||
'metadata.showLabel',
|
||||
'metadata.labelDirection',
|
||||
'metadata.labelInteractive',
|
||||
'metadata.outlinkTarget',
|
||||
'metadata.interactive',
|
||||
]
|
||||
builder = new U.FormBuilder(this, popupFields)
|
||||
const popupFieldset = DomUtil.createFieldset(
|
||||
|
@ -709,23 +702,23 @@ export class DataLayer {
|
|||
|
||||
// XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
|
||||
// but apparently it's needed.
|
||||
if (!Utils.isObject(this.options.remoteData)) {
|
||||
this.options.remoteData = {}
|
||||
if (!Utils.isObject(this.metadata.remoteData)) {
|
||||
this.metadata.remoteData = {}
|
||||
}
|
||||
|
||||
const remoteDataFields = [
|
||||
[
|
||||
'options.remoteData.url',
|
||||
'metadata.remoteData.url',
|
||||
{ handler: 'Url', label: translate('Url'), helpEntries: 'formatURL' },
|
||||
],
|
||||
[
|
||||
'options.remoteData.format',
|
||||
'metadata.remoteData.format',
|
||||
{ handler: 'DataFormat', label: translate('Format') },
|
||||
],
|
||||
'options.fromZoom',
|
||||
'options.toZoom',
|
||||
'metadata.fromZoom',
|
||||
'metadata.toZoom',
|
||||
[
|
||||
'options.remoteData.dynamic',
|
||||
'metadata.remoteData.dynamic',
|
||||
{
|
||||
handler: 'Switch',
|
||||
label: translate('Dynamic'),
|
||||
|
@ -733,7 +726,7 @@ export class DataLayer {
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.remoteData.licence',
|
||||
'metadata.remoteData.licence',
|
||||
{
|
||||
label: translate('Licence'),
|
||||
helpText: translate('Please be sure the licence is compliant with your use.'),
|
||||
|
@ -742,14 +735,14 @@ export class DataLayer {
|
|||
]
|
||||
if (this.map.options.urls.ajax_proxy) {
|
||||
remoteDataFields.push([
|
||||
'options.remoteData.proxy',
|
||||
'metadata.remoteData.proxy',
|
||||
{
|
||||
handler: 'Switch',
|
||||
label: translate('Proxy request'),
|
||||
helpEntries: 'proxyRemoteData',
|
||||
},
|
||||
])
|
||||
remoteDataFields.push('options.remoteData.ttl')
|
||||
remoteDataFields.push('metadata.remoteData.ttl')
|
||||
}
|
||||
|
||||
const remoteDataContainer = DomUtil.createFieldset(
|
||||
|
@ -828,7 +821,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
getOwnOption(option) {
|
||||
if (Utils.usableOption(this.options, option)) return this.options[option]
|
||||
if (Utils.usableOption(this.metadata, option)) return this.metadata[option]
|
||||
}
|
||||
|
||||
getOption(option, feature) {
|
||||
|
@ -879,8 +872,11 @@ export class DataLayer {
|
|||
this.getVersionUrl(version)
|
||||
)
|
||||
if (!error) {
|
||||
if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
|
||||
if (geojson._umap_options) this.setOptions(geojson._umap_options)
|
||||
if (!geojson.metadata) {
|
||||
// Retrocompat.
|
||||
geojson.metadata = geojson._umap_options || geojson._storage
|
||||
}
|
||||
if (geojson.metadata) this.setMetadata(geojson.metadata)
|
||||
this.empty()
|
||||
if (this.isRemoteLayer()) this.fetchRemoteData()
|
||||
else this.addData(geojson)
|
||||
|
@ -930,7 +926,7 @@ export class DataLayer {
|
|||
// Is this layer browsable in theorie
|
||||
// AND the user allows it
|
||||
allowBrowse() {
|
||||
return !!this.options.browsable && this.isBrowsable()
|
||||
return !!this.metadata.browsable && this.isBrowsable()
|
||||
}
|
||||
|
||||
// Is this layer browsable in theorie
|
||||
|
@ -1003,21 +999,13 @@ export class DataLayer {
|
|||
return prev
|
||||
}
|
||||
|
||||
umapGeoJSON() {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
|
||||
_umap_options: this.options,
|
||||
}
|
||||
}
|
||||
|
||||
getRank() {
|
||||
return this.map.datalayers_index.indexOf(this)
|
||||
}
|
||||
|
||||
isReadOnly() {
|
||||
// isReadOnly must return true if unset
|
||||
return this.options.editMode === 'disabled'
|
||||
return this.metadata.editMode === 'disabled'
|
||||
}
|
||||
|
||||
isDataReadOnly() {
|
||||
|
@ -1025,20 +1013,32 @@ export class DataLayer {
|
|||
return this.isReadOnly() || this.isRemoteLayer()
|
||||
}
|
||||
|
||||
cleanedMetadata() {
|
||||
const metadata = Utils.copyJSON(this.metadata)
|
||||
delete metadata.permissions
|
||||
delete metadata.editMode
|
||||
delete metadata.id
|
||||
return metadata
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.isDeleted) return this.saveDelete()
|
||||
if (!this.isLoaded()) {
|
||||
return
|
||||
}
|
||||
const geojson = this.umapGeoJSON()
|
||||
const geojson = {
|
||||
type: 'FeatureCollection',
|
||||
features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('name', this.options.name)
|
||||
formData.append('display_on_load', !!this.options.displayOnLoad)
|
||||
formData.append('name', this.metadata.name)
|
||||
formData.append('display_on_load', Boolean(this.metadata.displayOnLoad))
|
||||
formData.append('rank', this.getRank())
|
||||
formData.append('settings', JSON.stringify(this.options))
|
||||
formData.append('metadata', JSON.stringify(this.cleanedMetadata()))
|
||||
// Filename support is shaky, don't do it for now.
|
||||
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
|
||||
formData.append('geojson', blob)
|
||||
formData.append('data', blob)
|
||||
const saveUrl = this.map.urls.get('datalayer_save', {
|
||||
map_id: this.map.options.umap_id,
|
||||
pk: this.umap_id,
|
||||
|
@ -1067,17 +1067,17 @@ export class DataLayer {
|
|||
} else {
|
||||
// Response contains geojson only if save has conflicted and conflicts have
|
||||
// been resolved. So we need to reload to get extra data (added by someone else)
|
||||
if (data.geojson) {
|
||||
if (data.data) {
|
||||
this.clear()
|
||||
this.fromGeoJSON(data.geojson)
|
||||
delete data.geojson
|
||||
this.fromGeoJSON(data.data)
|
||||
delete data.data
|
||||
}
|
||||
this._reference_version = response.headers.get('X-Datalayer-Version')
|
||||
this.sync.update('_reference_version', this._reference_version)
|
||||
|
||||
this.setUmapId(data.id)
|
||||
this.updateOptions(data)
|
||||
this.backupOptions()
|
||||
this.updateMetadata(data)
|
||||
this.backupMetadata()
|
||||
this.connectToMap()
|
||||
this._loaded = true
|
||||
this.redraw() // Needed for reordering features
|
||||
|
@ -1099,7 +1099,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return this.options.name || translate('Untitled layer')
|
||||
return this.metadata.name || translate('Untitled layer')
|
||||
}
|
||||
|
||||
tableEdit() {
|
||||
|
|
|
@ -23,7 +23,6 @@ export const EXPORT_FORMATS = {
|
|||
map.eachFeature((feature) => {
|
||||
const row = feature.toGeoJSON().properties
|
||||
const center = feature.center
|
||||
delete row._umap_options
|
||||
row.Latitude = center.lat
|
||||
row.Longitude = center.lng
|
||||
table.push(row)
|
||||
|
|
|
@ -208,7 +208,7 @@ export default class Importer {
|
|||
DomUtil.element({
|
||||
tagName: 'option',
|
||||
parent: layerSelect,
|
||||
textContent: datalayer.options.name,
|
||||
textContent: datalayer.metadata.name,
|
||||
value: L.stamp(datalayer),
|
||||
})
|
||||
}
|
||||
|
@ -275,13 +275,13 @@ export default class Importer {
|
|||
return false
|
||||
}
|
||||
const layer = this.layer
|
||||
layer.options.remoteData = {
|
||||
layer.metadata.remoteData = {
|
||||
url: this.url,
|
||||
format: this.format,
|
||||
}
|
||||
if (this.map.options.urls.ajax_proxy) {
|
||||
layer.options.remoteData.proxy = true
|
||||
layer.options.remoteData.ttl = SCHEMA.ttl.default
|
||||
layer.metadata.remoteData.proxy = true
|
||||
layer.metadata.remoteData.ttl = SCHEMA.ttl.default
|
||||
}
|
||||
layer.fetchRemoteData(true)
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ export class DataLayerPermissions {
|
|||
{
|
||||
edit_status: null,
|
||||
},
|
||||
datalayer.options.permissions
|
||||
datalayer.metadata.permissions
|
||||
)
|
||||
|
||||
this.datalayer = datalayer
|
||||
|
@ -277,8 +277,8 @@ export class DataLayerPermissions {
|
|||
}
|
||||
|
||||
commit() {
|
||||
this.datalayer.options.permissions = Object.assign(
|
||||
this.datalayer.options.permissions,
|
||||
this.datalayer.metadata.permissions = Object.assign(
|
||||
this.datalayer.metadata.permissions,
|
||||
this.options
|
||||
)
|
||||
}
|
||||
|
|
|
@ -217,8 +217,8 @@ export const Cluster = DivIcon.extend({
|
|||
computeTextColor: function (el) {
|
||||
let color
|
||||
const backgroundColor = this.datalayer.getColor()
|
||||
if (this.datalayer.options.cluster?.textColor) {
|
||||
color = this.datalayer.options.cluster.textColor
|
||||
if (this.datalayer.metadata.cluster?.textColor) {
|
||||
color = this.datalayer.metadata.cluster.textColor
|
||||
}
|
||||
return color || DomUtil.TextColorFromBackgroundColor(el, backgroundColor)
|
||||
},
|
||||
|
|
|
@ -14,11 +14,11 @@ const ClassifiedMixin = {
|
|||
.filter((k) => k !== 'schemeGroups')
|
||||
.sort()
|
||||
const key = this.getType().toLowerCase()
|
||||
if (!Utils.isObject(this.datalayer.options[key])) {
|
||||
this.datalayer.options[key] = {}
|
||||
if (!Utils.isObject(this.datalayer.metadata[key])) {
|
||||
this.datalayer.metadata[key] = {}
|
||||
}
|
||||
this.ensureOptions(this.datalayer.options[key])
|
||||
FeatureGroup.prototype.initialize.call(this, [], this.datalayer.options[key])
|
||||
this.ensureOptions(this.datalayer.metadata[key])
|
||||
FeatureGroup.prototype.initialize.call(this, [], this.datalayer.metadata[key])
|
||||
LayerMixin.onInit.call(this, this.datalayer.map)
|
||||
},
|
||||
|
||||
|
@ -111,7 +111,7 @@ export const Choropleth = FeatureGroup.extend({
|
|||
},
|
||||
|
||||
_getValue: function (feature) {
|
||||
const key = this.datalayer.options.choropleth.property || 'value'
|
||||
const key = this.datalayer.metadata.choropleth.property || 'value'
|
||||
const value = +feature.properties[key]
|
||||
if (!Number.isNaN(value)) return value
|
||||
},
|
||||
|
@ -124,12 +124,12 @@ export const Choropleth = FeatureGroup.extend({
|
|||
this.options.colors = []
|
||||
return
|
||||
}
|
||||
const mode = this.datalayer.options.choropleth.mode
|
||||
let classes = +this.datalayer.options.choropleth.classes || 5
|
||||
const mode = this.datalayer.metadata.choropleth.mode
|
||||
let classes = +this.datalayer.metadata.choropleth.classes || 5
|
||||
let breaks
|
||||
classes = Math.min(classes, values.length)
|
||||
if (mode === 'manual') {
|
||||
const manualBreaks = this.datalayer.options.choropleth.breaks
|
||||
const manualBreaks = this.datalayer.metadata.choropleth.breaks
|
||||
if (manualBreaks) {
|
||||
breaks = manualBreaks
|
||||
.split(',')
|
||||
|
@ -148,10 +148,10 @@ export const Choropleth = FeatureGroup.extend({
|
|||
breaks.push(ss.max(values)) // Needed for computing the legend
|
||||
}
|
||||
this.options.breaks = breaks || []
|
||||
this.datalayer.options.choropleth.breaks = this.options.breaks
|
||||
this.datalayer.metadata.choropleth.breaks = this.options.breaks
|
||||
.map((b) => +b.toFixed(2))
|
||||
.join(',')
|
||||
let colorScheme = this.datalayer.options.choropleth.brewer
|
||||
let colorScheme = this.datalayer.metadata.choropleth.brewer
|
||||
if (!colorbrewer[colorScheme]) colorScheme = 'Blues'
|
||||
this.options.colors = colorbrewer[colorScheme][this.options.breaks.length - 1] || []
|
||||
},
|
||||
|
@ -169,24 +169,24 @@ export const Choropleth = FeatureGroup.extend({
|
|||
|
||||
onEdit: function (field, builder) {
|
||||
// Only compute the breaks if we're dealing with choropleth
|
||||
if (!field.startsWith('options.choropleth')) return
|
||||
if (!field.startsWith('metadata.choropleth')) return
|
||||
// If user touches the breaks, then force manual mode
|
||||
if (field === 'options.choropleth.breaks') {
|
||||
this.datalayer.options.choropleth.mode = 'manual'
|
||||
if (builder) builder.helpers['options.choropleth.mode'].fetch()
|
||||
if (field === 'metadata.choropleth.breaks') {
|
||||
this.datalayer.metadata.choropleth.mode = 'manual'
|
||||
if (builder) builder.helpers['metadata.choropleth.mode'].fetch()
|
||||
}
|
||||
this.compute()
|
||||
// If user changes the mode or the number of classes,
|
||||
// then update the breaks input value
|
||||
if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
|
||||
if (builder) builder.helpers['options.choropleth.breaks'].fetch()
|
||||
if (field === 'metadata.choropleth.mode' || field === 'metadata.choropleth.classes') {
|
||||
if (builder) builder.helpers['metadata.choropleth.breaks'].fetch()
|
||||
}
|
||||
},
|
||||
|
||||
getEditableOptions: function () {
|
||||
return [
|
||||
[
|
||||
'options.choropleth.property',
|
||||
'metadata.choropleth.property',
|
||||
{
|
||||
handler: 'Select',
|
||||
selectOptions: this.datalayer._propertiesIndex,
|
||||
|
@ -194,7 +194,7 @@ export const Choropleth = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.choropleth.brewer',
|
||||
'metadata.choropleth.brewer',
|
||||
{
|
||||
handler: 'Select',
|
||||
label: translate('Choropleth color palette'),
|
||||
|
@ -202,7 +202,7 @@ export const Choropleth = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.choropleth.classes',
|
||||
'metadata.choropleth.classes',
|
||||
{
|
||||
handler: 'Range',
|
||||
min: 3,
|
||||
|
@ -213,7 +213,7 @@ export const Choropleth = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.choropleth.breaks',
|
||||
'metadata.choropleth.breaks',
|
||||
{
|
||||
handler: 'BlurInput',
|
||||
label: translate('Choropleth breakpoints'),
|
||||
|
@ -223,7 +223,7 @@ export const Choropleth = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.choropleth.mode',
|
||||
'metadata.choropleth.mode',
|
||||
{
|
||||
handler: 'MultiChoice',
|
||||
default: 'kmeans',
|
||||
|
@ -255,13 +255,13 @@ export const Circles = FeatureGroup.extend({
|
|||
},
|
||||
|
||||
ensureOptions: function (options) {
|
||||
if (!Utils.isObject(this.datalayer.options.circles.radius)) {
|
||||
this.datalayer.options.circles.radius = {}
|
||||
if (!Utils.isObject(this.datalayer.metadata.circles.radius)) {
|
||||
this.datalayer.metadata.circles.radius = {}
|
||||
}
|
||||
},
|
||||
|
||||
_getValue: function (feature) {
|
||||
const key = this.datalayer.options.circles.property || 'value'
|
||||
const key = this.datalayer.metadata.circles.property || 'value'
|
||||
const value = +feature.properties[key]
|
||||
if (!Number.isNaN(value)) return value
|
||||
},
|
||||
|
@ -270,8 +270,8 @@ export const Circles = FeatureGroup.extend({
|
|||
const values = this.getValues()
|
||||
this.options.minValue = Math.sqrt(Math.min(...values))
|
||||
this.options.maxValue = Math.sqrt(Math.max(...values))
|
||||
this.options.minPX = this.datalayer.options.circles.radius?.min || 2
|
||||
this.options.maxPX = this.datalayer.options.circles.radius?.max || 50
|
||||
this.options.minPX = this.datalayer.metadata.circles.radius?.min || 2
|
||||
this.options.maxPX = this.datalayer.metadata.circles.radius?.max || 50
|
||||
},
|
||||
|
||||
onEdit: function (field, builder) {
|
||||
|
@ -375,7 +375,7 @@ export const Categorized = FeatureGroup.extend({
|
|||
|
||||
_getValue: function (feature) {
|
||||
const key =
|
||||
this.datalayer.options.categorized.property || this.datalayer._propertiesIndex[0]
|
||||
this.datalayer.metadata.categorized.property || this.datalayer._propertiesIndex[0]
|
||||
return feature.properties[key]
|
||||
},
|
||||
|
||||
|
@ -397,10 +397,10 @@ export const Categorized = FeatureGroup.extend({
|
|||
this.options.colors = []
|
||||
return
|
||||
}
|
||||
const mode = this.datalayer.options.categorized.mode
|
||||
const mode = this.datalayer.metadata.categorized.mode
|
||||
let categories = []
|
||||
if (mode === 'manual') {
|
||||
const manualCategories = this.datalayer.options.categorized.categories
|
||||
const manualCategories = this.datalayer.metadata.categorized.categories
|
||||
if (manualCategories) {
|
||||
categories = manualCategories.split(',')
|
||||
}
|
||||
|
@ -410,8 +410,8 @@ export const Categorized = FeatureGroup.extend({
|
|||
.sort(Utils.naturalSort)
|
||||
}
|
||||
this.options.categories = categories
|
||||
this.datalayer.options.categorized.categories = this.options.categories.join(',')
|
||||
const colorScheme = this.datalayer.options.categorized.brewer
|
||||
this.datalayer.metadata.categorized.categories = this.options.categories.join(',')
|
||||
const colorScheme = this.datalayer.metadata.categorized.brewer
|
||||
this._classes = this.options.categories.length
|
||||
if (colorbrewer[colorScheme]?.[this._classes]) {
|
||||
this.options.colors = colorbrewer[colorScheme][this._classes]
|
||||
|
@ -425,7 +425,7 @@ export const Categorized = FeatureGroup.extend({
|
|||
getEditableOptions: function () {
|
||||
return [
|
||||
[
|
||||
'options.categorized.property',
|
||||
'metadata.categorized.property',
|
||||
{
|
||||
handler: 'Select',
|
||||
selectOptions: this.datalayer._propertiesIndex,
|
||||
|
@ -433,7 +433,7 @@ export const Categorized = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.categorized.brewer',
|
||||
'metadata.categorized.brewer',
|
||||
{
|
||||
handler: 'Select',
|
||||
label: translate('Color palette'),
|
||||
|
@ -441,7 +441,7 @@ export const Categorized = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.categorized.categories',
|
||||
'metadata.categorized.categories',
|
||||
{
|
||||
handler: 'BlurInput',
|
||||
label: translate('Categories'),
|
||||
|
@ -449,7 +449,7 @@ export const Categorized = FeatureGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.categorized.mode',
|
||||
'metadata.categorized.mode',
|
||||
{
|
||||
handler: 'MultiChoice',
|
||||
default: 'alpha',
|
||||
|
@ -462,17 +462,17 @@ export const Categorized = FeatureGroup.extend({
|
|||
|
||||
onEdit: function (field, builder) {
|
||||
// Only compute the categories if we're dealing with categorized
|
||||
if (!field.startsWith('options.categorized')) return
|
||||
if (!field.startsWith('metadata.categorized')) return
|
||||
// If user touches the categories, then force manual mode
|
||||
if (field === 'options.categorized.categories') {
|
||||
this.datalayer.options.categorized.mode = 'manual'
|
||||
if (builder) builder.helpers['options.categorized.mode'].fetch()
|
||||
if (field === 'metadata.categorized.categories') {
|
||||
this.datalayer.metadata.categorized.mode = 'manual'
|
||||
if (builder) builder.helpers['metadata.categorized.mode'].fetch()
|
||||
}
|
||||
this.compute()
|
||||
// If user changes the mode
|
||||
// then update the categories input value
|
||||
if (field === 'options.categorized.mode') {
|
||||
if (builder) builder.helpers['options.categorized.categories'].fetch()
|
||||
if (field === 'metadata.categorized.mode') {
|
||||
if (builder) builder.helpers['metadata.categorized.categories'].fetch()
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
|||
|
||||
initialize: function (datalayer) {
|
||||
this.datalayer = datalayer
|
||||
if (!Utils.isObject(this.datalayer.options.cluster)) {
|
||||
this.datalayer.options.cluster = {}
|
||||
if (!Utils.isObject(this.datalayer.metadata.cluster)) {
|
||||
this.datalayer.metadata.cluster = {}
|
||||
}
|
||||
const options = {
|
||||
polygonOptions: {
|
||||
|
@ -36,8 +36,8 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
|||
},
|
||||
iconCreateFunction: (cluster) => new ClusterIcon(datalayer, cluster),
|
||||
}
|
||||
if (this.datalayer.options.cluster?.radius) {
|
||||
options.maxClusterRadius = this.datalayer.options.cluster.radius
|
||||
if (this.datalayer.metadata.cluster?.radius) {
|
||||
options.maxClusterRadius = this.datalayer.metadata.cluster.radius
|
||||
}
|
||||
L.MarkerClusterGroup.prototype.initialize.call(this, options)
|
||||
LayerMixin.onInit.call(this, this.datalayer.map)
|
||||
|
@ -73,7 +73,7 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
|||
|
||||
getEditableOptions: () => [
|
||||
[
|
||||
'options.cluster.radius',
|
||||
'metadata.cluster.radius',
|
||||
{
|
||||
handler: 'BlurIntInput',
|
||||
placeholder: translate('Clustering radius'),
|
||||
|
@ -81,7 +81,7 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.cluster.textColor',
|
||||
'metadata.cluster.textColor',
|
||||
{
|
||||
handler: 'TextColorPicker',
|
||||
placeholder: translate('Auto'),
|
||||
|
@ -91,7 +91,7 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
|||
],
|
||||
|
||||
onEdit: function (field, builder) {
|
||||
if (field === 'options.cluster.radius') {
|
||||
if (field === 'metadata.cluster.radius') {
|
||||
// No way to reset radius of an already instanciated MarkerClusterGroup...
|
||||
this.datalayer.resetLayer(true)
|
||||
return
|
||||
|
|
|
@ -20,10 +20,10 @@ export const Heat = L.HeatLayer.extend({
|
|||
|
||||
initialize: function (datalayer) {
|
||||
this.datalayer = datalayer
|
||||
L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat)
|
||||
L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.metadata.heat)
|
||||
LayerMixin.onInit.call(this, this.datalayer.map)
|
||||
if (!Utils.isObject(this.datalayer.options.heat)) {
|
||||
this.datalayer.options.heat = {}
|
||||
if (!Utils.isObject(this.datalayer.metadata.heat)) {
|
||||
this.datalayer.metadata.heat = {}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -31,9 +31,9 @@ export const Heat = L.HeatLayer.extend({
|
|||
if (layer instanceof Marker) {
|
||||
let latlng = layer.getLatLng()
|
||||
let alt
|
||||
if (this.datalayer.options.heat?.intensityProperty) {
|
||||
if (this.datalayer.metadata.heat?.intensityProperty) {
|
||||
alt = Number.parseFloat(
|
||||
layer.feature.properties[this.datalayer.options.heat.intensityProperty || 0]
|
||||
layer.feature.properties[this.datalayer.metadata.heat.intensityProperty || 0]
|
||||
)
|
||||
latlng = new LatLng(latlng.lat, latlng.lng, alt)
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export const Heat = L.HeatLayer.extend({
|
|||
|
||||
getEditableOptions: () => [
|
||||
[
|
||||
'options.heat.radius',
|
||||
'metadata.heat.radius',
|
||||
{
|
||||
handler: 'Range',
|
||||
min: 10,
|
||||
|
@ -74,7 +74,7 @@ export const Heat = L.HeatLayer.extend({
|
|||
},
|
||||
],
|
||||
[
|
||||
'options.heat.intensityProperty',
|
||||
'metadata.heat.intensityProperty',
|
||||
{
|
||||
handler: 'BlurInput',
|
||||
placeholder: translate('Heatmap intensity property'),
|
||||
|
@ -84,12 +84,12 @@ export const Heat = L.HeatLayer.extend({
|
|||
],
|
||||
|
||||
onEdit: function (field, builder) {
|
||||
if (field === 'options.heat.intensityProperty') {
|
||||
if (field === 'metadata.heat.intensityProperty') {
|
||||
this.datalayer.resetLayer(true) // We need to repopulate the latlngs
|
||||
return
|
||||
}
|
||||
if (field === 'options.heat.radius') {
|
||||
this.options.radius = this.datalayer.options.heat.radius
|
||||
if (field === 'metadata.heat.radius') {
|
||||
this.options.radius = this.datalayer.metadata.heat.radius
|
||||
}
|
||||
this._updateOptions()
|
||||
},
|
||||
|
|
|
@ -41,7 +41,7 @@ export function getImpactsFromSchema(fields, schema) {
|
|||
// remove the option prefix for fields
|
||||
// And only keep the first part in case of a subfield
|
||||
// (e.g "options.limitBounds.foobar" will just return "limitBounds")
|
||||
return field.replace('options.', '').split('.')[0]
|
||||
return field.replace('options.', '').replace('metadata.', '').split('.')[0]
|
||||
})
|
||||
.reduce((acc, field) => {
|
||||
// retrieve the "impacts" field from the schema
|
||||
|
@ -181,7 +181,7 @@ export function isObject(what) {
|
|||
return typeof what === 'object' && what !== null
|
||||
}
|
||||
|
||||
export function CopyJSON(geojson) {
|
||||
export function copyJSON(geojson) {
|
||||
return JSON.parse(JSON.stringify(geojson))
|
||||
}
|
||||
|
||||
|
|
|
@ -725,9 +725,9 @@ const ControlsMixin = {
|
|||
const row = L.DomUtil.create('li', 'orderable', ul)
|
||||
L.DomUtil.createIcon(row, 'icon-drag', L._('Drag to reorder'))
|
||||
datalayer.renderToolbox(row)
|
||||
const title = L.DomUtil.add('span', '', row, datalayer.options.name)
|
||||
const title = L.DomUtil.add('span', '', row, datalayer.metadata.name)
|
||||
row.classList.toggle('off', !datalayer.isVisible())
|
||||
title.textContent = datalayer.options.name
|
||||
title.textContent = datalayer.metadata.name
|
||||
row.dataset.id = L.stamp(datalayer)
|
||||
})
|
||||
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
||||
|
|
|
@ -29,30 +29,30 @@ L.Map.mergeOptions({
|
|||
U.Map = L.Map.extend({
|
||||
includes: [ControlsMixin],
|
||||
|
||||
initialize: async function (el, geojson) {
|
||||
initialize: async function (el, metadata) {
|
||||
this.sync_engine = new U.SyncEngine(this)
|
||||
this.sync = this.sync_engine.proxy(this)
|
||||
// Locale name (pt_PT, en_US…)
|
||||
// To be used for Django localization
|
||||
if (geojson.properties.locale) L.setLocale(geojson.properties.locale)
|
||||
if (metadata.locale) L.setLocale(metadata.locale)
|
||||
|
||||
// Language code (pt-pt, en-us…)
|
||||
// To be used in javascript APIs
|
||||
if (geojson.properties.lang) L.lang = geojson.properties.lang
|
||||
if (metadata.lang) L.lang = metadata.lang
|
||||
|
||||
this.setOptionsFromQueryString(geojson.properties)
|
||||
this.setOptionsFromQueryString(metadata)
|
||||
// Prevent default creation of controls
|
||||
const zoomControl = geojson.properties.zoomControl
|
||||
const fullscreenControl = geojson.properties.fullscreenControl
|
||||
geojson.properties.zoomControl = false
|
||||
geojson.properties.fullscreenControl = false
|
||||
const zoomControl = metadata.zoomControl
|
||||
const fullscreenControl = metadata.fullscreenControl
|
||||
metadata.zoomControl = false
|
||||
metadata.fullscreenControl = false
|
||||
|
||||
L.Map.prototype.initialize.call(this, el, geojson.properties)
|
||||
L.Map.prototype.initialize.call(this, el, metadata)
|
||||
|
||||
if (geojson.properties.schema) this.overrideSchema(geojson.properties.schema)
|
||||
if (metadata.schema) this.overrideSchema(metadata.schema)
|
||||
|
||||
// After calling parent initialize, as we are doing initCenter our-selves
|
||||
if (geojson.geometry) this.options.center = this.latLng(geojson.geometry)
|
||||
if (metadata.geometry) this.options.center = this.latLng(metadata.geometry)
|
||||
this.urls = new U.URLs(this.options.urls)
|
||||
|
||||
this.panel = new U.Panel(this)
|
||||
|
@ -805,7 +805,7 @@ U.Map = L.Map.extend({
|
|||
const datalayer = new U.DataLayer(this, options, sync)
|
||||
|
||||
if (sync !== false) {
|
||||
datalayer.sync.upsert(datalayer.options)
|
||||
datalayer.sync.upsert(datalayer.metadata)
|
||||
}
|
||||
return datalayer
|
||||
},
|
||||
|
@ -906,15 +906,16 @@ U.Map = L.Map.extend({
|
|||
}
|
||||
|
||||
if (importedData.geometry) this.options.center = this.latLng(importedData.geometry)
|
||||
importedData.layers.forEach((geojson) => {
|
||||
if (!geojson._umap_options && geojson._storage) {
|
||||
geojson._umap_options = geojson._storage
|
||||
for (const geojson of importedData.layers) {
|
||||
if (!geojson.metadata) {
|
||||
geojson.metadata = geojson._umap_options || geojson._storage
|
||||
delete geojson._umap_options
|
||||
delete geojson._storage
|
||||
}
|
||||
delete geojson._umap_options?.id // Never trust an id at this stage
|
||||
const dataLayer = this.createDataLayer(geojson._umap_options)
|
||||
delete geojson.metadata?.id // Never trust an id at this stage
|
||||
const dataLayer = this.createDataLayer(geojson.metadata)
|
||||
dataLayer.fromUmapGeoJSON(geojson)
|
||||
})
|
||||
}
|
||||
|
||||
this.initTileLayers()
|
||||
this.renderControls()
|
||||
|
@ -1032,22 +1033,14 @@ U.Map = L.Map.extend({
|
|||
|
||||
saveSelf: async function () {
|
||||
this.rules.commit()
|
||||
const geojson = {
|
||||
type: 'Feature',
|
||||
geometry: this.geometry(),
|
||||
properties: this.exportOptions(),
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('name', this.options.name)
|
||||
formData.append('center', JSON.stringify(this.geometry()))
|
||||
formData.append('settings', JSON.stringify(geojson))
|
||||
formData.append('zoom', this.getZoom())
|
||||
formData.append('metadata', JSON.stringify(this.exportOptions()))
|
||||
const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
|
||||
const [data, _, error] = await this.server.post(uri, {}, formData)
|
||||
// FIXME: login_required response will not be an error, so it will not
|
||||
// stop code while it should
|
||||
if (error) {
|
||||
return
|
||||
}
|
||||
if (error) return
|
||||
if (data.login_required) {
|
||||
window.onLogin = () => this.saveSelf()
|
||||
window.open(data.login_required)
|
||||
|
|
|
@ -682,7 +682,7 @@ describe('Utils', () => {
|
|||
describe('#copyJSON', () => {
|
||||
it('should actually copy the JSON', () => {
|
||||
const originalJSON = { some: 'json' }
|
||||
const returned = Utils.CopyJSON(originalJSON)
|
||||
const returned = Utils.copyJSON(originalJSON)
|
||||
|
||||
// Change the original JSON
|
||||
originalJSON.anotherKey = 'value'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% load umap_tags %}
|
||||
|
||||
<umap-fragment data-settings='{{ map_settings|escape }}'>
|
||||
<umap-fragment data-metadata='{{ map_metadata|escape }}'>
|
||||
<div id="{{ unique_id }}" class="map_fragment">
|
||||
</div>
|
||||
</umap-fragment>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!-- djlint:off -->
|
||||
<script defer type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
U.MAP = new U.Map("map", {{ map_settings|notag|safe }})
|
||||
U.MAP = new U.Map("map", {{ map_metadata|notag|safe }})
|
||||
})
|
||||
</script>
|
||||
<!-- djlint:on -->
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<a href="{{ map_inst.get_absolute_url }}">{{ map_inst.name }}</a>
|
||||
</th>
|
||||
<td>
|
||||
{{ map_inst.preview_settings|json_script:unique_id }}
|
||||
{{ map_inst.preview_metadata|json_script:unique_id }}
|
||||
<button class="map-icon map-opener"
|
||||
data-map-id="{{ unique_id }}"
|
||||
title="{% translate "Open preview" %}">
|
||||
|
|
|
@ -20,13 +20,13 @@ def umap_js(locale=None):
|
|||
|
||||
@register.inclusion_tag("umap/map_fragment.html")
|
||||
def map_fragment(map_instance, **kwargs):
|
||||
map_settings = map_instance.preview_settings
|
||||
map_settings["properties"].update(kwargs)
|
||||
map_metadata = map_instance.preview_metadata
|
||||
map_metadata.update(kwargs)
|
||||
prefix = kwargs.pop("prefix", None) or "map"
|
||||
page = kwargs.pop("page", None) or ""
|
||||
unique_id = prefix + str(page) + "_" + str(map_instance.pk)
|
||||
return {
|
||||
"map_settings": json_dumps(map_settings),
|
||||
"map_metadata": json_dumps(map_metadata),
|
||||
"map": map_instance,
|
||||
"unique_id": unique_id,
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ import json
|
|||
|
||||
import factory
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.core.files.base import ContentFile
|
||||
from django.urls import reverse
|
||||
|
||||
from umap.forms import DEFAULT_CENTER
|
||||
from umap.models import DataLayer, Licence, Map, TileLayer
|
||||
|
||||
User = get_user_model()
|
||||
|
@ -20,14 +20,13 @@ DATALAYER_DATA = {
|
|||
"type": "Point",
|
||||
"coordinates": [14.68896484375, 48.55297816440071],
|
||||
},
|
||||
"metadata": {"color": "DarkCyan", "iconClass": "Ball"},
|
||||
"properties": {
|
||||
"_umap_options": {"color": "DarkCyan", "iconClass": "Ball"},
|
||||
"name": "Here",
|
||||
"description": "Da place anonymous again 755",
|
||||
},
|
||||
}
|
||||
],
|
||||
"_umap_options": {"displayOnLoad": True, "name": "Donau", "id": 926},
|
||||
}
|
||||
|
||||
|
||||
|
@ -61,34 +60,27 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
class MapFactory(factory.django.DjangoModelFactory):
|
||||
name = "test map"
|
||||
slug = "test-map"
|
||||
center = DEFAULT_CENTER
|
||||
settings = factory.Dict(
|
||||
center = Point(13.447265624999998, 48.94415123418794)
|
||||
metadata = factory.Dict(
|
||||
{
|
||||
"geometry": {
|
||||
"coordinates": [13.447265624999998, 48.94415123418794],
|
||||
"type": "Point",
|
||||
"datalayersControl": True,
|
||||
"description": "Which is just the Danube, at the end",
|
||||
"displayPopupFooter": False,
|
||||
"licence": "",
|
||||
"miniMap": False,
|
||||
"moreControl": True,
|
||||
"name": name,
|
||||
"scaleControl": True,
|
||||
"tilelayer": {
|
||||
"attribution": "\xa9 OSM Contributors",
|
||||
"maxZoom": 18,
|
||||
"minZoom": 0,
|
||||
"url_template": "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
},
|
||||
"properties": {
|
||||
"datalayersControl": True,
|
||||
"description": "Which is just the Danube, at the end",
|
||||
"displayPopupFooter": False,
|
||||
"licence": "",
|
||||
"miniMap": False,
|
||||
"moreControl": True,
|
||||
"name": name,
|
||||
"scaleControl": True,
|
||||
"tilelayer": {
|
||||
"attribution": "\xa9 OSM Contributors",
|
||||
"maxZoom": 18,
|
||||
"minZoom": 0,
|
||||
"url_template": "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png",
|
||||
},
|
||||
"tilelayersControl": True,
|
||||
"zoom": 7,
|
||||
"zoomControl": True,
|
||||
},
|
||||
"type": "Feature",
|
||||
}
|
||||
"tilelayersControl": True,
|
||||
"zoom": 7,
|
||||
"zoomControl": True,
|
||||
},
|
||||
)
|
||||
|
||||
licence = factory.SubFactory(LicenceFactory)
|
||||
|
@ -97,8 +89,8 @@ class MapFactory(factory.django.DjangoModelFactory):
|
|||
@classmethod
|
||||
def _adjust_kwargs(cls, **kwargs):
|
||||
# Make sure there is no persistency
|
||||
kwargs["settings"] = copy.deepcopy(kwargs["settings"])
|
||||
kwargs["settings"]["properties"]["name"] = kwargs["name"]
|
||||
kwargs["metadata"] = copy.deepcopy(kwargs["metadata"])
|
||||
kwargs["metadata"]["name"] = kwargs["name"]
|
||||
return kwargs
|
||||
|
||||
class Meta:
|
||||
|
@ -110,26 +102,18 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
|
|||
name = "test datalayer"
|
||||
description = "test description"
|
||||
display_on_load = True
|
||||
settings = factory.Dict({"displayOnLoad": True, "browsable": True, "name": name})
|
||||
metadata = factory.Dict({"displayOnLoad": True, "browsable": True, "name": name})
|
||||
|
||||
@classmethod
|
||||
def _adjust_kwargs(cls, **kwargs):
|
||||
if "data" in kwargs:
|
||||
data = copy.deepcopy(kwargs.pop("data"))
|
||||
if "settings" not in kwargs:
|
||||
kwargs["settings"] = data.get("_umap_options", {})
|
||||
else:
|
||||
data = DATALAYER_DATA.copy()
|
||||
data["_umap_options"] = {
|
||||
**DataLayerFactory.settings._defaults,
|
||||
**kwargs["settings"],
|
||||
}
|
||||
data.setdefault("_umap_options", {})
|
||||
kwargs["settings"]["name"] = kwargs["name"]
|
||||
data["_umap_options"]["name"] = kwargs["name"]
|
||||
kwargs["metadata"]["name"] = kwargs["name"]
|
||||
data.setdefault("type", "FeatureCollection")
|
||||
data.setdefault("features", [])
|
||||
kwargs["geojson"] = ContentFile(json.dumps(data), "foo.json")
|
||||
kwargs["data"] = ContentFile(json.dumps(data), "foo.json")
|
||||
return kwargs
|
||||
|
||||
class Meta:
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -12,4 +12,4 @@
|
|||
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[2.062908,44.976505],[2.09421,44.872012],[2.171637,44.790027],[2.169416,44.63807],[2.326791,44.669693],[2.435001,44.638875],[2.556123,44.721284],[2.602682,44.843163],[2.738258,44.941219],[2.849652,44.87149],[2.923267,44.728643],[2.998574,44.674443],[3.105495,44.886775],[3.182317,44.863735],[3.337942,44.955907],[3.412832,44.944842],[3.475771,44.815371],[3.740649,44.838697],[3.875462,44.740627],[3.905171,44.592709],[4.068445,44.405112],[4.051457,44.317322],[4.25885,44.264784],[4.336071,44.339519],[4.503539,44.340188],[4.649227,44.27036],[4.762255,44.325382],[4.81409,44.232315],[5.060561,44.308137],[5.1549,44.230941],[5.384527,44.201049],[5.454715,44.119226],[5.576192,44.188037],[5.686443,44.197158],[5.631598,44.328306],[5.49307,44.337174],[5.418533,44.424945],[5.597253,44.543274],[5.649631,44.617885],[5.790624,44.653293],[5.850394,44.750747],[6.136227,44.864072],[6.355363,44.854775],[6.318202,45.003859],[6.203923,45.012471],[6.229392,45.10875],[6.331295,45.118124],[6.393911,45.061818],[6.629992,45.109325],[6.767941,45.15974],[6.849855,45.127165],[6.968762,45.208058],[7.137593,45.255693],[7.110693,45.326509],[7.184271,45.407484],[7.000332,45.504414],[7.000692,45.6399],[6.829113,45.702831],[6.818078,45.834974],[6.939609,45.846733],[7.043891,45.922087],[7.018252,45.984185],[6.81473,46.129696],[6.864511,46.282986],[6.722865,46.40755],[6.545176,46.394725],[6.390033,46.340163],[6.279914,46.351093],[6.295651,46.226055],[6.126621,46.14046],[5.985317,46.143309],[5.971781,46.211519],[6.124246,46.251016],[6.169736,46.367935],[6.064006,46.416223],[5.908936,46.283951],[5.725182,46.260732],[5.649345,46.339495],[5.473052,46.265067],[5.310563,46.44677],[5.20114,46.508211],[5.052372,46.484874],[4.940022,46.517199],[4.780208,46.176676],[4.69311,46.302197],[4.618558,46.264794],[4.405814,46.296058],[4.389398,46.213601],[4.261025,46.178754],[4.104087,46.198391],[3.988788,46.169805],[3.890131,46.214487],[3.891239,46.285246],[3.986627,46.319196],[3.99804,46.465464],[3.890467,46.481246],[3.743289,46.567565],[3.696958,46.660583],[3.598001,46.723983],[3.215545,46.682893],[3.032063,46.794909],[2.959919,46.803872],[2.774489,46.718903],[2.70497,46.73939],[2.596648,46.637215],[2.614961,46.553276],[2.352004,46.512207],[2.281044,46.420404],[2.323023,46.329277],[2.478945,46.281146],[2.5598,46.173367],[2.59442,45.989441],[2.492228,45.86403],[2.388014,45.827373],[2.52836,45.681924],[2.465345,45.60082],[2.516327,45.553428],[2.487472,45.418842],[2.37825,45.414302],[2.350481,45.327561],[2.195364,45.220851],[2.171759,45.081497],[2.062908,44.976505]]]},"properties":{"code":"84","nom":"Auvergne-Rhône-Alpes","taux":"6.1"}},
|
||||
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[4.230281,43.460184],[4.554917,43.446213],[4.562798,43.372135],[4.855045,43.332619],[4.86685,43.404678],[4.96771,43.4261],[5.04104,43.327285],[5.36205,43.32196],[5.363649,43.207122],[5.53693,43.21449],[5.600895,43.162546],[5.812732,43.109367],[6.124052,43.079307],[6.387568,43.1449],[6.635535,43.172509],[6.665956,43.31822],[6.739809,43.412882],[6.917972,43.447739],[6.971833,43.545451],[7.040444,43.541583],[7.167666,43.657402],[7.320289,43.691329],[7.528519,43.790518],[7.495441,43.864356],[7.648598,43.97411],[7.716938,44.081763],[7.670853,44.153737],[7.426953,44.112875],[7.188913,44.197801],[7.008059,44.236435],[6.896505,44.374301],[6.854014,44.529125],[6.933509,44.575953],[6.987061,44.690138],[7.006773,44.839316],[6.859866,44.852903],[6.749751,44.907359],[6.740812,45.016733],[6.629992,45.109325],[6.393911,45.061818],[6.331295,45.118124],[6.229392,45.10875],[6.203923,45.012471],[6.318202,45.003859],[6.355363,44.854775],[6.136227,44.864072],[5.850394,44.750747],[5.790624,44.653293],[5.649631,44.617885],[5.597253,44.543274],[5.418533,44.424945],[5.49307,44.337174],[5.631598,44.328306],[5.686443,44.197158],[5.576192,44.188037],[5.454715,44.119226],[5.384527,44.201049],[5.1549,44.230941],[5.060561,44.308137],[4.81409,44.232315],[4.762255,44.325382],[4.649227,44.27036],[4.722071,44.187421],[4.70746,44.10367],[4.8421,43.986474],[4.690546,43.883899],[4.593035,43.68746],[4.487234,43.699241],[4.409353,43.561127],[4.230281,43.460184]]]},"properties":{"code":"93","nom":"Provence-Alpes-Côte d'Azur","taux":"7.8"}},
|
||||
{"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[9.402271,41.858702],[9.412569,41.952476],[9.54998,42.104166],[9.558829,42.285265],[9.533196,42.545947],[9.449194,42.66224],[9.492385,42.8051],[9.463558,42.986401],[9.340873,42.994465],[9.311015,42.834679],[9.344478,42.73781],[9.085764,42.714609],[9.020694,42.644273],[8.886527,42.628966],[8.666509,42.515224],[8.674792,42.476243],[8.555885,42.36475],[8.689105,42.263528],[8.570341,42.230301],[8.590174,42.163885],[8.741329,42.040912],[8.5977,41.953238],[8.64145,41.909889],[8.803133,41.891381],[8.717242,41.722775],[8.914508,41.689724],[8.793077,41.629554],[8.788535,41.557736],[9.082201,41.441974],[9.219679,41.368212],[9.327205,41.616357],[9.387491,41.657359],[9.402271,41.858702]]]},"properties":{"code":"94","nom":"Corse","taux":"6.2"}}
|
||||
],"_umap_options": {"displayOnLoad": true,"browsable": true,"name": "Taux de chômage","labelKey": "{nom} ({taux})","type": "Choropleth","choropleth": {"property": "taux"}}}
|
||||
]}
|
||||
|
|
4
umap/tests/fixtures/display_on_load.umap
vendored
4
umap/tests/fixtures/display_on_load.umap
vendored
|
@ -47,7 +47,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"displayOnLoad": true,
|
||||
"inCaption": true,
|
||||
"browsable": true,
|
||||
|
@ -71,7 +71,7 @@
|
|||
}
|
||||
}
|
||||
],
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"displayOnLoad": false,
|
||||
"inCaption": true,
|
||||
"browsable": true,
|
||||
|
|
|
@ -203,7 +203,7 @@
|
|||
"id": "c1160"
|
||||
}
|
||||
],
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"displayOnLoad": true,
|
||||
"inCaption": true,
|
||||
"browsable": true,
|
||||
|
|
16
umap/tests/fixtures/test_upload_data.umap
vendored
16
umap/tests/fixtures/test_upload_data.umap
vendored
|
@ -98,16 +98,16 @@
|
|||
50.6269
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"color": "Orange"
|
||||
},
|
||||
"properties": {
|
||||
"_umap_options": {
|
||||
"color": "Orange"
|
||||
},
|
||||
"name": "Lille",
|
||||
"description": "une ville"
|
||||
}
|
||||
}
|
||||
],
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"displayOnLoad": true,
|
||||
"name": "Cities",
|
||||
"id": 108,
|
||||
|
@ -152,15 +152,15 @@
|
|||
]
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"weight": "4"
|
||||
},
|
||||
"properties": {
|
||||
"_umap_options": {
|
||||
"weight": "4"
|
||||
},
|
||||
"name": "tunnel sous la Manche"
|
||||
}
|
||||
}
|
||||
],
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"displayOnLoad": true,
|
||||
"name": "Tunnels",
|
||||
"id": 109,
|
||||
|
|
|
@ -9,4 +9,4 @@ def save_and_get_json(page):
|
|||
with page.expect_response(re.compile(r".*/datalayer/create/.*")):
|
||||
page.get_by_role("button", name="Save").click()
|
||||
datalayer = DataLayer.objects.last()
|
||||
return json.loads(Path(datalayer.geojson.path).read_text())
|
||||
return json.loads(Path(datalayer.data.path).read_text())
|
||||
|
|
|
@ -55,17 +55,12 @@ DATALAYER_DATA = {
|
|||
},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"displayOnLoad": True,
|
||||
"browsable": True,
|
||||
"name": "Calque 1",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bootstrap(map, live_server):
|
||||
map.settings["properties"]["onLoadPanel"] = "databrowser"
|
||||
map.metadata["onLoadPanel"] = "databrowser"
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
|
||||
|
@ -113,7 +108,7 @@ def test_data_browser_should_be_filterable(live_server, page, bootstrap, map):
|
|||
|
||||
def test_filter_uses_layer_setting_if_any(live_server, page, bootstrap, map):
|
||||
datalayer = map.datalayer_set.first()
|
||||
datalayer.settings["labelKey"] = "foo"
|
||||
datalayer.metadata["labelKey"] = "foo"
|
||||
datalayer.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.get_by_title("Features in this layer: 3")).to_be_visible()
|
||||
|
@ -149,11 +144,10 @@ def test_filter_uses_layer_setting_if_any(live_server, page, bootstrap, map):
|
|||
|
||||
|
||||
def test_filter_works_with_variable_in_labelKey(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "databrowser"
|
||||
map.metadata["onLoadPanel"] = "databrowser"
|
||||
map.save()
|
||||
data = deepcopy(DATALAYER_DATA)
|
||||
data["_umap_options"]["labelKey"] = "{name} ({bar})"
|
||||
DataLayerFactory(map=map, data=data)
|
||||
DataLayerFactory(map=map, data=data, metadata={"labelKey": "{name} ({bar})"})
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.get_by_title("Features in this layer: 3")).to_be_visible()
|
||||
markers = page.locator(".leaflet-marker-icon")
|
||||
|
@ -277,7 +271,7 @@ def test_data_browser_bbox_filtered_is_clickable(live_server, page, bootstrap, m
|
|||
|
||||
def test_data_browser_with_variable_in_name(live_server, page, bootstrap, map):
|
||||
# Include a variable
|
||||
map.settings["properties"]["labelKey"] = "{name} ({foo})"
|
||||
map.metadata["labelKey"] = "{name} ({foo})"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.get_by_text("one point in france (point)")).to_be_visible()
|
||||
|
@ -299,7 +293,7 @@ def test_data_browser_with_variable_in_name(live_server, page, bootstrap, map):
|
|||
|
||||
|
||||
def test_should_sort_features_in_natural_order(live_server, map, page):
|
||||
map.settings["properties"]["onLoadPanel"] = "databrowser"
|
||||
map.metadata["onLoadPanel"] = "databrowser"
|
||||
map.save()
|
||||
datalayer_data = deepcopy(DATALAYER_DATA)
|
||||
datalayer_data["features"][0]["properties"]["name"] = "9. a marker"
|
||||
|
@ -331,8 +325,8 @@ def test_should_redraw_list_on_feature_delete(live_server, openmap, page, bootst
|
|||
def test_should_show_header_for_display_on_load_false(
|
||||
live_server, page, bootstrap, map, datalayer
|
||||
):
|
||||
datalayer.settings["displayOnLoad"] = False
|
||||
datalayer.settings["name"] = "This layer is not loaded"
|
||||
datalayer.metadata["displayOnLoad"] = False
|
||||
datalayer.metadata["name"] = "This layer is not loaded"
|
||||
datalayer.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
browser = page.locator(".umap-browser")
|
||||
|
@ -341,8 +335,8 @@ def test_should_show_header_for_display_on_load_false(
|
|||
|
||||
|
||||
def test_should_use_color_variable(live_server, map, page):
|
||||
map.settings["properties"]["onLoadPanel"] = "databrowser"
|
||||
map.settings["properties"]["color"] = "{mycolor}"
|
||||
map.metadata["onLoadPanel"] = "databrowser"
|
||||
map.metadata["color"] = "{mycolor}"
|
||||
map.save()
|
||||
datalayer_data = deepcopy(DATALAYER_DATA)
|
||||
datalayer_data["features"][0]["properties"]["mycolor"] = "DarkRed"
|
||||
|
|
|
@ -9,13 +9,13 @@ pytestmark = pytest.mark.django_db
|
|||
|
||||
|
||||
def test_caption(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "caption"
|
||||
map.metadata["onLoadPanel"] = "caption"
|
||||
map.save()
|
||||
basic = DataLayerFactory(map=map, name="Basic layer")
|
||||
non_loaded = DataLayerFactory(
|
||||
map=map, name="Non loaded", settings={"displayOnLoad": False}
|
||||
map=map, name="Non loaded", metadata={"displayOnLoad": False}
|
||||
)
|
||||
hidden = DataLayerFactory(map=map, name="Hidden", settings={"inCaption": False})
|
||||
hidden = DataLayerFactory(map=map, name="Hidden", metadata={"inCaption": False})
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
panel = page.locator(".panel.left.on")
|
||||
expect(panel).to_have_class(re.compile(".*condensed.*"))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
@ -8,11 +9,24 @@ from ..base import DataLayerFactory
|
|||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
METADATA = {
|
||||
"displayOnLoad": True,
|
||||
"inCaption": True,
|
||||
"browsable": True,
|
||||
"name": "Grisy-sur-Seine",
|
||||
"id": "769b2bb0-920d-4531-8055-dd198a33456a",
|
||||
"type": "Categorized",
|
||||
"weight": 3,
|
||||
"opacity": 0.9,
|
||||
"categorized": {"property": "highway"},
|
||||
"popupContentTemplate": "# {name}\n{highway}",
|
||||
}
|
||||
|
||||
|
||||
def test_basic_categorized_map_with_default_color(map, live_server, page):
|
||||
path = Path(__file__).parent.parent / "fixtures/categorized_highway.geojson"
|
||||
data = json.loads(path.read_text())
|
||||
DataLayerFactory(data=data, map=map)
|
||||
DataLayerFactory(data=data, map=map, metadata=METADATA)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}#13/48.4378/3.3043")
|
||||
# residential
|
||||
expect(page.locator("path[stroke='#7fc97f']")).to_have_count(5)
|
||||
|
@ -33,8 +47,9 @@ def test_basic_categorized_map_with_custom_brewer(openmap, live_server, page):
|
|||
data = json.loads(path.read_text())
|
||||
|
||||
# Change brewer at load
|
||||
data["_umap_options"]["categorized"]["brewer"] = "Spectral"
|
||||
DataLayerFactory(data=data, map=openmap)
|
||||
metadata = deepcopy(METADATA)
|
||||
metadata["categorized"]["brewer"] = "Spectral"
|
||||
DataLayerFactory(data=data, map=openmap, metadata=metadata)
|
||||
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}#13/48.4378/3.3043")
|
||||
# residential
|
||||
|
@ -76,11 +91,12 @@ def test_basic_categorized_map_with_custom_categories(openmap, live_server, page
|
|||
data = json.loads(path.read_text())
|
||||
|
||||
# Change categories at load
|
||||
data["_umap_options"]["categorized"]["categories"] = (
|
||||
metadata = deepcopy(METADATA)
|
||||
metadata["categorized"]["categories"] = (
|
||||
"unclassified,track,service,residential,tertiary,secondary"
|
||||
)
|
||||
data["_umap_options"]["categorized"]["mode"] = "manual"
|
||||
DataLayerFactory(data=data, map=openmap)
|
||||
metadata["categorized"]["mode"] = "manual"
|
||||
DataLayerFactory(data=data, map=openmap, metadata=metadata)
|
||||
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}#13/48.4378/3.3043")
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
@ -9,10 +10,20 @@ from ..base import DataLayerFactory
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
METADATA = {
|
||||
"displayOnLoad": True,
|
||||
"browsable": True,
|
||||
"name": "Taux de chômage",
|
||||
"labelKey": "{nom} ({taux})",
|
||||
"type": "Choropleth",
|
||||
"choropleth": {"property": "taux"},
|
||||
}
|
||||
|
||||
|
||||
def test_basic_choropleth_map_with_default_color(map, live_server, page):
|
||||
path = Path(__file__).parent.parent / "fixtures/choropleth_region_chomage.geojson"
|
||||
data = json.loads(path.read_text())
|
||||
DataLayerFactory(data=data, map=map)
|
||||
DataLayerFactory(data=data, map=map, metadata=METADATA)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
# Hauts-de-France
|
||||
expect(page.locator("path[fill='#08519c']")).to_have_count(1)
|
||||
|
@ -31,8 +42,13 @@ def test_basic_choropleth_map_with_custom_brewer(openmap, live_server, page):
|
|||
data = json.loads(path.read_text())
|
||||
|
||||
# Change brewer at load
|
||||
data["_umap_options"]["choropleth"]["brewer"] = "Reds"
|
||||
DataLayerFactory(data=data, map=openmap)
|
||||
metadata = deepcopy(METADATA)
|
||||
metadata["choropleth"]["brewer"] = "Reds"
|
||||
DataLayerFactory(
|
||||
data=data,
|
||||
map=openmap,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}")
|
||||
# Hauts-de-France
|
||||
|
@ -70,8 +86,9 @@ def test_basic_choropleth_map_with_custom_classes(openmap, live_server, page):
|
|||
data = json.loads(path.read_text())
|
||||
|
||||
# Change brewer at load
|
||||
data["_umap_options"]["choropleth"]["classes"] = 6
|
||||
DataLayerFactory(data=data, map=openmap)
|
||||
metadata = deepcopy(METADATA)
|
||||
metadata["choropleth"]["classes"] = 6
|
||||
DataLayerFactory(data=data, map=openmap, metadata=metadata)
|
||||
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}")
|
||||
|
||||
|
|
|
@ -39,9 +39,6 @@ DATALAYER_DATA1 = {
|
|||
"geometry": {"type": "Point", "coordinates": [3.55957, 49.767074]},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"name": "Calque 1",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -70,14 +67,11 @@ DATALAYER_DATA2 = {
|
|||
"geometry": {"type": "Point", "coordinates": [4.372559, 47.945786]},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"name": "Calque 2",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_simple_equal_rule_at_load(live_server, page, map):
|
||||
map.settings["properties"]["rules"] = [
|
||||
map.metadata["rules"] = [
|
||||
{"condition": "mytype=odd", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
map.save()
|
||||
|
@ -91,7 +85,7 @@ def test_simple_equal_rule_at_load(live_server, page, map):
|
|||
|
||||
|
||||
def test_simple_not_equal_rule_at_load(live_server, page, map):
|
||||
map.settings["properties"]["rules"] = [
|
||||
map.metadata["rules"] = [
|
||||
{"condition": "mytype!=even", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
map.save()
|
||||
|
@ -105,7 +99,7 @@ def test_simple_not_equal_rule_at_load(live_server, page, map):
|
|||
|
||||
|
||||
def test_gt_rule_with_number_at_load(live_server, page, map):
|
||||
map.settings["properties"]["rules"] = [
|
||||
map.metadata["rules"] = [
|
||||
{"condition": "mynumber>10", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
map.save()
|
||||
|
@ -119,7 +113,7 @@ def test_gt_rule_with_number_at_load(live_server, page, map):
|
|||
|
||||
|
||||
def test_lt_rule_with_number_at_load(live_server, page, map):
|
||||
map.settings["properties"]["rules"] = [
|
||||
map.metadata["rules"] = [
|
||||
{"condition": "mynumber<14", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
map.save()
|
||||
|
@ -133,7 +127,7 @@ def test_lt_rule_with_number_at_load(live_server, page, map):
|
|||
|
||||
|
||||
def test_lt_rule_with_float_at_load(live_server, page, map):
|
||||
map.settings["properties"]["rules"] = [
|
||||
map.metadata["rules"] = [
|
||||
{"condition": "mynumber<12.3", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
map.save()
|
||||
|
@ -147,7 +141,7 @@ def test_lt_rule_with_float_at_load(live_server, page, map):
|
|||
|
||||
|
||||
def test_equal_rule_with_boolean_at_load(live_server, page, map):
|
||||
map.settings["properties"]["rules"] = [
|
||||
map.metadata["rules"] = [
|
||||
{"condition": "myboolean=true", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
map.save()
|
||||
|
@ -179,7 +173,7 @@ def test_can_create_new_rule(live_server, page, openmap):
|
|||
|
||||
|
||||
def test_can_deactive_rule_from_list(live_server, page, openmap):
|
||||
openmap.settings["properties"]["rules"] = [
|
||||
openmap.metadata["rules"] = [
|
||||
{"condition": "mytype=odd", "options": {"color": "aliceblue"}}
|
||||
]
|
||||
openmap.save()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import json
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from django.core.files.base import ContentFile
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from ..base import DataLayerFactory
|
||||
|
@ -10,17 +8,13 @@ from ..base import DataLayerFactory
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def set_options(datalayer, **options):
|
||||
# For now we need to change both the DB and the FS…
|
||||
datalayer.settings.update(options)
|
||||
data = json.load(datalayer.geojson.file)
|
||||
data["_umap_options"].update(**options)
|
||||
datalayer.geojson = ContentFile(json.dumps(data), "foo.json")
|
||||
def set_metadata(datalayer, **options):
|
||||
datalayer.metadata.update(options)
|
||||
datalayer.save()
|
||||
|
||||
|
||||
def test_honour_displayOnLoad_false(map, live_server, datalayer, page):
|
||||
set_options(datalayer, displayOnLoad=False)
|
||||
set_metadata(datalayer, displayOnLoad=False)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
expect(page.locator(".leaflet-marker-icon")).to_be_hidden()
|
||||
layers = page.locator(".umap-browser .datalayer")
|
||||
|
@ -37,7 +31,7 @@ def test_honour_displayOnLoad_false(map, live_server, datalayer, page):
|
|||
|
||||
|
||||
def test_should_honour_fromZoom(live_server, map, datalayer, page):
|
||||
set_options(datalayer, displayOnLoad=True, fromZoom=6)
|
||||
set_metadata(datalayer, displayOnLoad=True, fromZoom=6)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}#5/48.55/14.68")
|
||||
markers = page.locator(".leaflet-marker-icon")
|
||||
expect(markers).to_be_hidden()
|
||||
|
@ -55,7 +49,7 @@ def test_should_honour_fromZoom(live_server, map, datalayer, page):
|
|||
|
||||
|
||||
def test_should_honour_toZoom(live_server, map, datalayer, page):
|
||||
set_options(datalayer, displayOnLoad=True, toZoom=6)
|
||||
set_metadata(datalayer, displayOnLoad=True, toZoom=6)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}#7/48.55/14.68")
|
||||
markers = page.locator(".leaflet-marker-icon")
|
||||
expect(markers).to_be_hidden()
|
||||
|
@ -99,13 +93,15 @@ def test_should_honour_color_variable(live_server, map, page):
|
|||
},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"name": "Calque 2",
|
||||
}
|
||||
DataLayerFactory(
|
||||
map=map,
|
||||
data=data,
|
||||
metadata={
|
||||
"color": "{mycolor}",
|
||||
"fillColor": "{mycolor}",
|
||||
},
|
||||
}
|
||||
DataLayerFactory(map=map, data=data)
|
||||
)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='tomato']"))
|
||||
markers = page.locator(".leaflet-marker-icon .icon_container")
|
||||
|
@ -113,10 +109,10 @@ def test_should_honour_color_variable(live_server, map, page):
|
|||
|
||||
|
||||
def test_datalayers_in_query_string(live_server, datalayer, map, page):
|
||||
map.settings["properties"]["onLoadPanel"] = "datalayers"
|
||||
map.metadata["onLoadPanel"] = "datalayers"
|
||||
map.save()
|
||||
with_old_id = DataLayerFactory(old_id=134, map=map, name="with old id")
|
||||
set_options(with_old_id, name="with old id")
|
||||
set_metadata(with_old_id, name="with old id")
|
||||
visible = page.locator(".umap-browser .datalayer:not(.off) .datalayer-name")
|
||||
hidden = page.locator(".umap-browser .datalayer.off .datalayer-name")
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
|
|
|
@ -99,16 +99,15 @@ def test_should_follow_datalayer_style_when_changing_datalayer(
|
|||
live_server, openmap, page
|
||||
):
|
||||
data = deepcopy(DATALAYER_DATA)
|
||||
data["_umap_options"] = {"color": "DarkCyan"}
|
||||
DataLayerFactory(map=openmap, data=data)
|
||||
DataLayerFactory(map=openmap, data=data, metadata={"color": "DarkCyan"})
|
||||
DataLayerFactory(
|
||||
map=openmap,
|
||||
name="other datalayer",
|
||||
data={
|
||||
"type": "FeatureCollection",
|
||||
"features": [],
|
||||
"_umap_options": {"color": "DarkViolet"},
|
||||
},
|
||||
metadata={"color": "DarkViolet"},
|
||||
)
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||
marker = page.locator(".leaflet-marker-icon .icon_container")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import platform
|
||||
|
||||
import pytest
|
||||
from django.contrib.gis.geos import Point
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from ..base import DataLayerFactory
|
||||
|
@ -37,11 +38,8 @@ DATALAYER_DATA = {
|
|||
|
||||
@pytest.fixture
|
||||
def bootstrap(map, live_server):
|
||||
map.settings["properties"]["zoom"] = 6
|
||||
map.settings["geometry"] = {
|
||||
"type": "Point",
|
||||
"coordinates": [8.429, 53.239],
|
||||
}
|
||||
map.zoom = 6
|
||||
map.center = Point(8.429, 53.239)
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
|
||||
|
@ -124,7 +122,7 @@ def test_should_reset_style_on_cancel(live_server, openmap, page, bootstrap):
|
|||
|
||||
def test_can_change_datalayer(live_server, openmap, page, bootstrap):
|
||||
other = DataLayerFactory(
|
||||
name="Layer 2", map=openmap, settings={"color": "GoldenRod"}
|
||||
name="Layer 2", map=openmap, metadata={"color": "GoldenRod"}
|
||||
)
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||
expect(page.locator("path[fill='DarkBlue']")).to_have_count(1)
|
||||
|
|
|
@ -34,10 +34,10 @@ DATALAYER_DATA = {
|
|||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"metadata": {
|
||||
"color": "OliveDrab",
|
||||
},
|
||||
"properties": {
|
||||
"_umap_options": {
|
||||
"color": "OliveDrab",
|
||||
},
|
||||
"name": "test",
|
||||
"description": "Some description",
|
||||
},
|
||||
|
@ -49,11 +49,11 @@ DATALAYER_DATA = {
|
|||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"metadata": {
|
||||
"fill": False,
|
||||
"opacity": 0.6,
|
||||
},
|
||||
"properties": {
|
||||
"_umap_options": {
|
||||
"fill": False,
|
||||
"opacity": 0.6,
|
||||
},
|
||||
"name": "test",
|
||||
},
|
||||
"id": "YwMTM",
|
||||
|
@ -76,7 +76,7 @@ DATALAYER_DATA = {
|
|||
|
||||
@pytest.fixture
|
||||
def bootstrap(map, live_server):
|
||||
map.settings["properties"]["onLoadPanel"] = "databrowser"
|
||||
map.metadata["onLoadPanel"] = "databrowser"
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
|
||||
|
@ -94,13 +94,9 @@ def test_umap_export(map, live_server, bootstrap, page):
|
|||
downloaded = json.loads(path.read_text())
|
||||
del downloaded["uri"] # Port changes at each run
|
||||
assert downloaded == {
|
||||
"geometry": {
|
||||
"coordinates": [13.447265624999998, 48.94415123418794],
|
||||
"type": "Point",
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
|
@ -131,8 +127,8 @@ def test_umap_export(map, live_server, bootstrap, page):
|
|||
"type": "Point",
|
||||
},
|
||||
"id": "QwNjg",
|
||||
"metadata": {"color": "OliveDrab"},
|
||||
"properties": {
|
||||
"_umap_options": {"color": "OliveDrab"},
|
||||
"name": "test",
|
||||
"description": "Some description",
|
||||
},
|
||||
|
@ -152,8 +148,8 @@ def test_umap_export(map, live_server, bootstrap, page):
|
|||
"type": "LineString",
|
||||
},
|
||||
"id": "YwMTM",
|
||||
"metadata": {"fill": False, "opacity": 0.6},
|
||||
"properties": {
|
||||
"_umap_options": {"fill": False, "opacity": 0.6},
|
||||
"name": "test",
|
||||
},
|
||||
"type": "Feature",
|
||||
|
@ -162,7 +158,11 @@ def test_umap_export(map, live_server, bootstrap, page):
|
|||
"type": "FeatureCollection",
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"metadata": {
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [13.447265624999998, 48.94415123418794],
|
||||
},
|
||||
"datalayersControl": True,
|
||||
"description": "Which is just the Danube, at the end",
|
||||
"displayPopupFooter": False,
|
||||
|
@ -234,7 +234,7 @@ def test_kml_export(map, live_server, bootstrap, page):
|
|||
download.save_as(path)
|
||||
assert (
|
||||
path.read_text()
|
||||
== """<kml xmlns="http://www.opengis.net/kml/2.2"><Document>\n<Placemark id="gyNzM">\n<name>name poly</name><ExtendedData></ExtendedData>\n <Polygon>\n<outerBoundaryIs>\n <LinearRing><coordinates>11.25,53.585984\n10.151367,52.975108\n12.689209,52.167194\n14.084473,53.199452\n12.634277,53.618579\n11.25,53.585984\n11.25,53.585984</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark>\n<Placemark id="QwNjg">\n<name>test</name><description>Some description</description><ExtendedData>\n <Data name="_umap_options"><value>{"color":"OliveDrab"}</value></Data></ExtendedData>\n <Point><coordinates>-0.274658,52.57635</coordinates></Point></Placemark>\n<Placemark id="YwMTM">\n<name>test</name><ExtendedData>\n <Data name="_umap_options"><value>{"fill":false,"opacity":0.6}</value></Data></ExtendedData>\n <LineString><coordinates>-0.571289,54.476422\n0.439453,54.610255\n1.724854,53.448807\n4.163818,53.988395\n5.306396,53.533778\n6.591797,53.709714\n7.042236,53.350551</coordinates></LineString></Placemark></Document></kml>"""
|
||||
== """<kml xmlns="http://www.opengis.net/kml/2.2"><Document>\n<Placemark id="gyNzM">\n<name>name poly</name><ExtendedData></ExtendedData>\n <Polygon>\n<outerBoundaryIs>\n <LinearRing><coordinates>11.25,53.585984\n10.151367,52.975108\n12.689209,52.167194\n14.084473,53.199452\n12.634277,53.618579\n11.25,53.585984\n11.25,53.585984</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark>\n<Placemark id="QwNjg">\n<name>test</name><description>Some description</description><ExtendedData></ExtendedData>\n <Point><coordinates>-0.274658,52.57635</coordinates></Point></Placemark>\n<Placemark id="YwMTM">\n<name>test</name><ExtendedData></ExtendedData>\n <LineString><coordinates>-0.571289,54.476422\n0.439453,54.610255\n1.724854,53.448807\n4.163818,53.988395\n5.306396,53.533778\n6.591797,53.709714\n7.042236,53.350551</coordinates></LineString></Placemark></Document></kml>"""
|
||||
)
|
||||
|
||||
|
||||
|
@ -272,8 +272,8 @@ def test_geojson_export(map, live_server, bootstrap, page):
|
|||
{
|
||||
"geometry": {"coordinates": [-0.274658, 52.57635], "type": "Point"},
|
||||
"id": "QwNjg",
|
||||
"metadata": {"color": "OliveDrab"},
|
||||
"properties": {
|
||||
"_umap_options": {"color": "OliveDrab"},
|
||||
"name": "test",
|
||||
"description": "Some description",
|
||||
},
|
||||
|
@ -293,8 +293,8 @@ def test_geojson_export(map, live_server, bootstrap, page):
|
|||
"type": "LineString",
|
||||
},
|
||||
"id": "YwMTM",
|
||||
"metadata": {"fill": False, "opacity": 0.6},
|
||||
"properties": {
|
||||
"_umap_options": {"fill": False, "opacity": 0.6},
|
||||
"name": "test",
|
||||
},
|
||||
"type": "Feature",
|
||||
|
|
|
@ -33,9 +33,6 @@ DATALAYER_DATA1 = {
|
|||
"geometry": {"type": "Point", "coordinates": [3.55957, 49.767074]},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"name": "Calque 1",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -63,9 +60,6 @@ DATALAYER_DATA2 = {
|
|||
"geometry": {"type": "Point", "coordinates": [4.372559, 47.945786]},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"name": "Calque 2",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -89,18 +83,17 @@ DATALAYER_DATA3 = {
|
|||
},
|
||||
},
|
||||
],
|
||||
"_umap_options": {"name": "Calque 2", "browsable": False},
|
||||
}
|
||||
|
||||
|
||||
def test_simple_facet_search(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "datafilters"
|
||||
map.settings["properties"]["facetKey"] = "mytype|My type,mynumber|My Number|number"
|
||||
map.settings["properties"]["showLabel"] = True
|
||||
map.metadata["onLoadPanel"] = "datafilters"
|
||||
map.metadata["facetKey"] = "mytype|My type,mynumber|My Number|number"
|
||||
map.metadata["showLabel"] = True
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA3)
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA3, metadata={"browsable": False})
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
|
||||
panel = page.locator(".panel.left.on")
|
||||
expect(panel).to_have_class(re.compile(".*expanded.*"))
|
||||
|
@ -170,8 +163,8 @@ def test_simple_facet_search(live_server, page, map):
|
|||
|
||||
|
||||
def test_date_facet_search(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "datafilters"
|
||||
map.settings["properties"]["facetKey"] = "mydate|Date filter|date"
|
||||
map.metadata["onLoadPanel"] = "datafilters"
|
||||
map.metadata["facetKey"] = "mydate|Date filter|date"
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||
|
@ -188,8 +181,8 @@ def test_date_facet_search(live_server, page, map):
|
|||
|
||||
|
||||
def test_choice_with_empty_value(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "datafilters"
|
||||
map.settings["properties"]["facetKey"] = "mytype|My type"
|
||||
map.metadata["onLoadPanel"] = "datafilters"
|
||||
map.metadata["facetKey"] = "mytype|My type"
|
||||
map.save()
|
||||
data = copy.deepcopy(DATALAYER_DATA1)
|
||||
data["features"][0]["properties"]["mytype"] = ""
|
||||
|
@ -205,8 +198,8 @@ def test_choice_with_empty_value(live_server, page, map):
|
|||
|
||||
|
||||
def test_number_with_zero_value(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "datafilters"
|
||||
map.settings["properties"]["facetKey"] = "mynumber|Filter|number"
|
||||
map.metadata["onLoadPanel"] = "datafilters"
|
||||
map.metadata["facetKey"] = "mynumber|Filter|number"
|
||||
map.save()
|
||||
data = copy.deepcopy(DATALAYER_DATA1)
|
||||
data["features"][0]["properties"]["mynumber"] = 0
|
||||
|
@ -222,8 +215,8 @@ def test_number_with_zero_value(live_server, page, map):
|
|||
|
||||
|
||||
def test_facets_search_are_persistent_when_closing_panel(live_server, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "datafilters"
|
||||
map.settings["properties"]["facetKey"] = "mytype|My type,mynumber|My Number|number"
|
||||
map.metadata["onLoadPanel"] = "datafilters"
|
||||
map.metadata["facetKey"] = "mytype|My type,mynumber|My Number|number"
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA1)
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA2)
|
||||
|
|
|
@ -348,7 +348,7 @@ def test_should_remove_dot_in_property_names(live_server, page, settings, tilela
|
|||
with page.expect_response(re.compile(r".*/datalayer/create/.*")):
|
||||
page.get_by_role("button", name="Save").click()
|
||||
datalayer = DataLayer.objects.last()
|
||||
saved_data = json.loads(Path(datalayer.geojson.path).read_text())
|
||||
saved_data = json.loads(Path(datalayer.data.path).read_text())
|
||||
assert saved_data["features"][0]["properties"] == {
|
||||
"color": "",
|
||||
"name": "Chez Rémy",
|
||||
|
|
|
@ -14,7 +14,7 @@ def test_preconnect_for_tilelayer(map, page, live_server, tilelayer):
|
|||
expect(meta).to_have_count(1)
|
||||
expect(meta).to_have_attribute("href", "//a.tile.openstreetmap.fr")
|
||||
# Add custom tilelayer
|
||||
map.settings["properties"]["tilelayer"] = {
|
||||
map.metadata["tilelayer"] = {
|
||||
"name": "OSM Piano FR",
|
||||
"maxZoom": 20,
|
||||
"minZoom": 0,
|
||||
|
@ -25,7 +25,7 @@ def test_preconnect_for_tilelayer(map, page, live_server, tilelayer):
|
|||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(meta).to_have_attribute("href", "//a.piano.tiles.quaidorsay.fr")
|
||||
# Add custom tilelayer with variable in domain, should create a preconnect
|
||||
map.settings["properties"]["tilelayer"] = {
|
||||
map.metadata["tilelayer"] = {
|
||||
"name": "OSM Piano FR",
|
||||
"maxZoom": 20,
|
||||
"minZoom": 0,
|
||||
|
@ -40,7 +40,7 @@ def test_preconnect_for_tilelayer(map, page, live_server, tilelayer):
|
|||
def test_default_view_without_datalayer_should_use_default_center(
|
||||
map, live_server, datalayer, page
|
||||
):
|
||||
datalayer.settings["displayOnLoad"] = False
|
||||
datalayer.metadata["displayOnLoad"] = False
|
||||
datalayer.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
# Hash is defined, so map is initialized
|
||||
|
@ -52,9 +52,9 @@ def test_default_view_without_datalayer_should_use_default_center(
|
|||
def test_default_view_latest_without_datalayer_should_use_default_center(
|
||||
map, live_server, datalayer, page
|
||||
):
|
||||
datalayer.settings["displayOnLoad"] = False
|
||||
datalayer.metadata["displayOnLoad"] = False
|
||||
datalayer.save()
|
||||
map.settings["properties"]["defaultView"] = "latest"
|
||||
map.metadata["defaultView"] = "latest"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
# Hash is defined, so map is initialized
|
||||
|
@ -66,9 +66,9 @@ def test_default_view_latest_without_datalayer_should_use_default_center(
|
|||
def test_default_view_data_without_datalayer_should_use_default_center(
|
||||
map, live_server, datalayer, page
|
||||
):
|
||||
datalayer.settings["displayOnLoad"] = False
|
||||
datalayer.metadata["displayOnLoad"] = False
|
||||
datalayer.save()
|
||||
map.settings["properties"]["defaultView"] = "data"
|
||||
map.metadata["defaultView"] = "data"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
# Hash is defined, so map is initialized
|
||||
|
@ -78,7 +78,7 @@ def test_default_view_data_without_datalayer_should_use_default_center(
|
|||
|
||||
|
||||
def test_default_view_latest_with_marker(map, live_server, datalayer, page):
|
||||
map.settings["properties"]["defaultView"] = "latest"
|
||||
map.metadata["defaultView"] = "latest"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
# Hash is defined, so map is initialized
|
||||
|
@ -108,7 +108,7 @@ def test_default_view_latest_with_line(map, live_server, page):
|
|||
],
|
||||
}
|
||||
DataLayerFactory(map=map, data=data)
|
||||
map.settings["properties"]["defaultView"] = "latest"
|
||||
map.metadata["defaultView"] = "latest"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
expect(page).to_have_url(re.compile(r".*#8/48\..+/2\..+"))
|
||||
|
@ -139,7 +139,7 @@ def test_default_view_latest_with_polygon(map, live_server, page):
|
|||
],
|
||||
}
|
||||
DataLayerFactory(map=map, data=data)
|
||||
map.settings["properties"]["defaultView"] = "latest"
|
||||
map.metadata["defaultView"] = "latest"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=datalayers")
|
||||
expect(page).to_have_url(re.compile(r".*#8/48\..+/2\..+"))
|
||||
|
@ -152,7 +152,7 @@ def test_default_view_locate(browser, live_server, map):
|
|||
geolocation={"longitude": 8.52967, "latitude": 39.16267},
|
||||
permissions=["geolocation"],
|
||||
)
|
||||
map.settings["properties"]["defaultView"] = "locate"
|
||||
map.metadata["defaultView"] = "locate"
|
||||
map.save()
|
||||
page = context.new_page()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
|
@ -162,7 +162,7 @@ def test_default_view_locate(browser, live_server, map):
|
|||
def test_remote_layer_should_not_be_used_as_datalayer_for_created_features(
|
||||
openmap, live_server, datalayer, page
|
||||
):
|
||||
datalayer.settings["remoteData"] = {
|
||||
datalayer.metadata["remoteData"] = {
|
||||
"url": "https://overpass-api.de/api/interpreter?data=[out:xml];node[harbour=yes]({south},{west},{north},{east});out body;",
|
||||
"format": "osm",
|
||||
"from": "10",
|
||||
|
@ -191,7 +191,7 @@ def test_remote_layer_should_not_be_used_as_datalayer_for_created_features(
|
|||
def test_minimap_on_load(map, live_server, datalayer, page):
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.locator(".leaflet-control-minimap")).to_be_hidden()
|
||||
map.settings["properties"]["miniMap"] = True
|
||||
map.metadata["miniMap"] = True
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.locator(".leaflet-control-minimap")).to_be_visible()
|
||||
|
@ -200,7 +200,7 @@ def test_minimap_on_load(map, live_server, datalayer, page):
|
|||
def test_zoom_control_on_load(map, live_server, page):
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.locator(".leaflet-control-zoom")).to_be_visible()
|
||||
map.settings["properties"]["zoomControl"] = False
|
||||
map.metadata["zoomControl"] = False
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.locator(".leaflet-control-zoom")).to_be_hidden()
|
||||
|
@ -209,7 +209,7 @@ def test_zoom_control_on_load(map, live_server, page):
|
|||
def test_feature_in_query_string_has_precedence_over_onloadpanel(
|
||||
map, live_server, page
|
||||
):
|
||||
map.settings["properties"]["onLoadPanel"] = "caption"
|
||||
map.metadata["onLoadPanel"] = "caption"
|
||||
map.name = "This is my map"
|
||||
map.save()
|
||||
data = {
|
||||
|
@ -224,9 +224,8 @@ def test_feature_in_query_string_has_precedence_over_onloadpanel(
|
|||
},
|
||||
}
|
||||
],
|
||||
"_umap_options": {"popupShape": "Panel"},
|
||||
}
|
||||
DataLayerFactory(map=map, data=data)
|
||||
DataLayerFactory(map=map, data=data, metadata={"popupShape": "Panel"})
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?feature=FooBar")
|
||||
expect(page.get_by_role("heading", name="FooBar")).to_be_visible()
|
||||
expect(page.get_by_role("heading", name="This is my map")).to_be_hidden()
|
||||
|
|
|
@ -5,9 +5,9 @@ pytestmark = pytest.mark.django_db
|
|||
|
||||
|
||||
def test_should_not_render_any_control(live_server, tilelayer, page, map):
|
||||
map.settings["properties"]["onLoadPanel"] = "databrowser"
|
||||
map.settings["properties"]["miniMap"] = True
|
||||
map.settings["properties"]["captionBar"] = True
|
||||
map.metadata["onLoadPanel"] = "databrowser"
|
||||
map.metadata["miniMap"] = True
|
||||
map.metadata["captionBar"] = True
|
||||
map.save()
|
||||
# Make sure those controls are visible in normal view
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
|
|
|
@ -43,12 +43,12 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
# Prevent two layers to be saved on the same second, as we compare them based
|
||||
# on time in case of conflict. FIXME do not use time for comparison.
|
||||
sleep(1)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).metadata == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"editMode": "advanced",
|
||||
"inCaption": True,
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
# Now navigate to this map from another tab
|
||||
|
@ -78,12 +78,12 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
sleep(1)
|
||||
# No change after the save
|
||||
expect(marker_pane_p2).to_have_count(2)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).metadata == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
# Now create another marker in the first tab
|
||||
|
@ -94,14 +94,12 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
save_p1.click()
|
||||
# Should now get the other marker too
|
||||
expect(marker_pane_p1).to_have_count(3)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).metadata == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
# And again
|
||||
|
@ -112,14 +110,12 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
save_p1.click()
|
||||
sleep(1)
|
||||
# Should now get the other marker too
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).metadata == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"remoteData": {},
|
||||
}
|
||||
expect(marker_pane_p1).to_have_count(4)
|
||||
|
||||
|
@ -132,14 +128,12 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
save_p2.click()
|
||||
sleep(1)
|
||||
# Should now get the other markers too
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).metadata == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"remoteData": {},
|
||||
}
|
||||
expect(marker_pane_p2).to_have_count(5)
|
||||
|
||||
|
@ -258,14 +252,12 @@ def test_same_second_edit_doesnt_conflict(context, live_server, tilelayer):
|
|||
|
||||
# Should now get the other marker too
|
||||
expect(marker_pane_p1).to_have_count(3)
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).settings == {
|
||||
assert DataLayer.objects.get(pk=datalayer.pk).metadata == {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
|
||||
|
@ -286,11 +278,11 @@ def test_should_display_alert_on_conflict(context, live_server, datalayer, openm
|
|||
with page_two.expect_response(re.compile(r".*/datalayer/update/.*")):
|
||||
page_two.get_by_role("button", name="Save").click()
|
||||
saved = DataLayer.objects.last()
|
||||
data = json.loads(Path(saved.geojson.path).read_text())
|
||||
data = json.loads(Path(saved.data.path).read_text())
|
||||
assert data["features"][0]["properties"]["name"] == "new name"
|
||||
expect(page_two.get_by_text("Whoops! Other contributor(s) changed")).to_be_visible()
|
||||
with page_two.expect_response(re.compile(r".*/datalayer/update/.*")):
|
||||
page_two.get_by_text("Keep your changes and loose theirs").click()
|
||||
saved = DataLayer.objects.last()
|
||||
data = json.loads(Path(saved.geojson.path).read_text())
|
||||
data = json.loads(Path(saved.data.path).read_text())
|
||||
assert data["features"][0]["properties"]["name"] == "custom name"
|
||||
|
|
|
@ -21,10 +21,10 @@ DATALAYER_DATA = {
|
|||
"type": "Point",
|
||||
"coordinates": [13.68896484375, 48.55297816440071],
|
||||
},
|
||||
"properties": {"_umap_options": {"color": "DarkCyan"}, "name": "Here"},
|
||||
"metadata": {"color": "DarkCyan"},
|
||||
"properties": {"name": "Here"},
|
||||
}
|
||||
],
|
||||
"_umap_options": {"displayOnLoad": True, "name": "FooBarFoo"},
|
||||
}
|
||||
FIXTURES = Path(__file__).parent.parent / "fixtures"
|
||||
|
||||
|
@ -69,7 +69,7 @@ def test_can_change_picto_at_map_level(openmap, live_server, page, pictos):
|
|||
|
||||
|
||||
def test_can_change_picto_at_datalayer_level(openmap, live_server, page, pictos):
|
||||
openmap.settings["properties"]["iconUrl"] = "/uploads/pictogram/star.svg"
|
||||
openmap.metadata["iconUrl"] = "/uploads/pictogram/star.svg"
|
||||
openmap.save()
|
||||
DataLayerFactory(map=openmap, data=DATALAYER_DATA)
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||
|
@ -108,7 +108,7 @@ def test_can_change_picto_at_datalayer_level(openmap, live_server, page, pictos)
|
|||
|
||||
|
||||
def test_can_change_picto_at_marker_level(openmap, live_server, page, pictos):
|
||||
openmap.settings["properties"]["iconUrl"] = "/uploads/pictogram/star.svg"
|
||||
openmap.metadata["iconUrl"] = "/uploads/pictogram/star.svg"
|
||||
openmap.save()
|
||||
DataLayerFactory(map=openmap, data=DATALAYER_DATA)
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||
|
|
|
@ -38,7 +38,7 @@ DATALAYER_DATA = {
|
|||
|
||||
|
||||
def test_can_use_slideshow_manually(map, live_server, page):
|
||||
map.settings["properties"]["slideshow"] = {"active": True, "delay": 5000}
|
||||
map.metadata["slideshow"] = {"active": True, "delay": 5000}
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
|
|
|
@ -28,9 +28,9 @@ def staticfiles(settings):
|
|||
def test_javascript_have_been_loaded(
|
||||
map, live_server, datalayer, page, settings, staticfiles
|
||||
):
|
||||
datalayer.settings["displayOnLoad"] = False
|
||||
datalayer.metadata["displayOnLoad"] = False
|
||||
datalayer.save()
|
||||
map.settings["properties"]["defaultView"] = "latest"
|
||||
map.metadata["defaultView"] = "latest"
|
||||
map.save()
|
||||
with override("fr"):
|
||||
url = f"{live_server.url}{map.get_absolute_url()}"
|
||||
|
|
|
@ -59,9 +59,6 @@ DATALAYER_DATA = {
|
|||
"id": "poin3",
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"name": "Calque 2",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -81,7 +78,7 @@ def test_table_editor(live_server, openmap, datalayer, page):
|
|||
with page.expect_response(re.compile(r".*/datalayer/update/.*")):
|
||||
page.get_by_role("button", name="Save").click()
|
||||
saved = DataLayer.objects.last()
|
||||
data = json.loads(Path(saved.geojson.path).read_text())
|
||||
data = json.loads(Path(saved.data.path).read_text())
|
||||
assert data["features"][0]["properties"]["newprop"] == "newvalue"
|
||||
assert "name" not in data["features"][0]["properties"]
|
||||
|
||||
|
|
|
@ -85,8 +85,8 @@ def test_map_should_display_selected_tilelayer(map, live_server, tilelayers, pag
|
|||
url_pattern = re.compile(
|
||||
r"https://[abc]{1}.piano.tiles.quaidorsay.fr/fr/\d+/\d+/\d+.png"
|
||||
)
|
||||
map.settings["properties"]["tilelayer"]["url_template"] = piano.url_template
|
||||
map.settings["properties"]["tilelayersControl"] = True
|
||||
map.metadata["tilelayer"]["url_template"] = piano.url_template
|
||||
map.metadata["tilelayersControl"] = True
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
tiles = page.locator(".leaflet-tile-pane img")
|
||||
|
@ -101,10 +101,10 @@ def test_map_should_display_custom_tilelayer(map, live_server, tilelayers, page)
|
|||
url_pattern = re.compile(
|
||||
r"https://[abc]{1}.basemaps.cartocdn.com/rastertiles/voyager/\d+/\d+/\d+.png"
|
||||
)
|
||||
map.settings["properties"]["tilelayer"]["url_template"] = (
|
||||
map.metadata["tilelayer"]["url_template"] = (
|
||||
"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png"
|
||||
)
|
||||
map.settings["properties"]["tilelayersControl"] = True
|
||||
map.metadata["tilelayersControl"] = True
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
tiles = page.locator(".leaflet-tile-pane img")
|
||||
|
@ -115,7 +115,7 @@ def test_map_should_display_custom_tilelayer(map, live_server, tilelayers, page)
|
|||
|
||||
|
||||
def test_can_have_smart_text_in_attribution(tilelayer, map, live_server, page):
|
||||
map.settings["properties"]["tilelayer"]["attribution"] = (
|
||||
map.metadata["tilelayer"]["attribution"] = (
|
||||
"© [[http://www.openstreetmap.org/copyright|OpenStreetMap]] contributors"
|
||||
)
|
||||
map.save()
|
||||
|
@ -125,7 +125,7 @@ def test_can_have_smart_text_in_attribution(tilelayer, map, live_server, page):
|
|||
|
||||
|
||||
def test_map_should_display_a_more_button(map, live_server, tilelayers, page):
|
||||
map.settings["properties"]["tilelayersControl"] = True
|
||||
map.metadata["tilelayersControl"] = True
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
page.locator(".leaflet-iconLayers").hover()
|
||||
|
|
|
@ -58,15 +58,15 @@ def test_should_handle_locale_var_in_description(live_server, map, page):
|
|||
|
||||
|
||||
def test_should_display_tooltip_with_variable(live_server, map, page, bootstrap):
|
||||
map.settings["properties"]["showLabel"] = True
|
||||
map.settings["properties"]["labelKey"] = "Foo {name}"
|
||||
map.metadata["showLabel"] = True
|
||||
map.metadata["labelKey"] = "Foo {name}"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
expect(page.get_by_text("Foo test marker")).to_be_visible()
|
||||
|
||||
|
||||
def test_should_open_popup_panel_on_click(live_server, map, page, bootstrap):
|
||||
map.settings["properties"]["popupShape"] = "Panel"
|
||||
map.metadata["popupShape"] = "Panel"
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}")
|
||||
panel = page.locator(".panel.left.on")
|
||||
|
@ -82,7 +82,7 @@ def test_should_open_popup_panel_on_click(live_server, map, page, bootstrap):
|
|||
|
||||
|
||||
def test_extended_properties_in_popup(live_server, map, page, bootstrap):
|
||||
map.settings["properties"]["popupContentTemplate"] = """
|
||||
map.metadata["popupContentTemplate"] = """
|
||||
Rank: {rank}
|
||||
Locale: {locale}
|
||||
Lang: {lang}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
from django.contrib.gis.geos import Point
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from ..base import DataLayerFactory
|
||||
|
@ -35,11 +36,8 @@ DATALAYER_DATA = {
|
|||
|
||||
@pytest.fixture
|
||||
def bootstrap(map, live_server):
|
||||
map.settings["properties"]["zoom"] = 6
|
||||
map.settings["geometry"] = {
|
||||
"type": "Point",
|
||||
"coordinates": [8.429, 53.239],
|
||||
}
|
||||
map.zoom = 6
|
||||
map.center = Point(8.429, 53.239)
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
from django.contrib.gis.geos import Point
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from ..base import DataLayerFactory
|
||||
|
@ -27,11 +28,8 @@ DATALAYER_DATA = {
|
|||
|
||||
@pytest.fixture
|
||||
def bootstrap(map, live_server):
|
||||
map.settings["properties"]["zoom"] = 6
|
||||
map.settings["geometry"] = {
|
||||
"type": "Point",
|
||||
"coordinates": [8.429, 53.239],
|
||||
}
|
||||
map.zoom = 6
|
||||
map.center = Point(8.429, 53.239)
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ def test_websocket_connection_can_sync_markers(
|
|||
new_page, live_server, websocket_server, tilelayer
|
||||
):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
map.metadata["syncEnabled"] = True
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data={})
|
||||
|
||||
|
@ -80,7 +80,7 @@ def test_websocket_connection_can_sync_polygons(
|
|||
context, live_server, websocket_server, tilelayer
|
||||
):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
map.metadata["syncEnabled"] = True
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data={})
|
||||
|
||||
|
@ -164,7 +164,7 @@ def test_websocket_connection_can_sync_map_properties(
|
|||
context, live_server, websocket_server, tilelayer
|
||||
):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
map.metadata["syncEnabled"] = True
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data={})
|
||||
|
||||
|
@ -196,7 +196,7 @@ def test_websocket_connection_can_sync_datalayer_properties(
|
|||
context, live_server, websocket_server, tilelayer
|
||||
):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
map.metadata["syncEnabled"] = True
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data={})
|
||||
|
||||
|
@ -225,7 +225,7 @@ def test_websocket_connection_can_sync_cloned_polygons(
|
|||
context, live_server, websocket_server, tilelayer
|
||||
):
|
||||
map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
|
||||
map.settings["properties"]["syncEnabled"] = True
|
||||
map.metadata["syncEnabled"] = True
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data={})
|
||||
|
||||
|
|
|
@ -28,14 +28,14 @@ def test_upload_to(map, datalayer):
|
|||
|
||||
|
||||
def test_save_should_use_pk_as_name(map, datalayer):
|
||||
assert "/{}_".format(datalayer.pk) in datalayer.geojson.name
|
||||
assert "/{}_".format(datalayer.pk) in datalayer.data.name
|
||||
|
||||
|
||||
def test_same_geojson_file_name_will_be_suffixed(map, datalayer):
|
||||
before = datalayer.geojson.name
|
||||
datalayer.geojson.save(before, ContentFile("{}"))
|
||||
assert datalayer.geojson.name != before
|
||||
assert "/{}_".format(datalayer.pk) in datalayer.geojson.name
|
||||
before = datalayer.data.name
|
||||
datalayer.data.save(before, ContentFile("{}"))
|
||||
assert datalayer.data.name != before
|
||||
assert "/{}_".format(datalayer.pk) in datalayer.data.name
|
||||
|
||||
|
||||
def test_clone_should_return_new_instance(map, datalayer):
|
||||
|
@ -57,38 +57,38 @@ def test_clone_should_update_map_if_passed(datalayer, user, licence):
|
|||
def test_clone_should_clone_geojson_too(datalayer):
|
||||
clone = datalayer.clone()
|
||||
assert datalayer.pk != clone.pk
|
||||
assert clone.geojson is not None
|
||||
assert clone.geojson.path != datalayer.geojson.path
|
||||
assert clone.data is not None
|
||||
assert clone.data.path != datalayer.data.path
|
||||
|
||||
|
||||
def test_should_remove_old_versions_on_save(map, settings):
|
||||
datalayer = DataLayerFactory(uuid="0f1161c0-c07f-4ba4-86c5-8d8981d8a813", old_id=17)
|
||||
settings.UMAP_KEEP_VERSIONS = 3
|
||||
root = Path(datalayer.storage_root())
|
||||
before = len(datalayer.geojson.storage.listdir(root)[1])
|
||||
before = len(datalayer.data.storage.listdir(root)[1])
|
||||
newer = f"{datalayer.pk}_1440924889.geojson"
|
||||
medium = f"{datalayer.pk}_1440923687.geojson"
|
||||
older = f"{datalayer.pk}_1440918637.geojson"
|
||||
with_old_id = f"{datalayer.old_id}_1440918537.geojson"
|
||||
other = "123456_1440918637.geojson"
|
||||
for path in [medium, newer, older, with_old_id, other]:
|
||||
datalayer.geojson.storage.save(root / path, ContentFile("{}"))
|
||||
datalayer.geojson.storage.save(root / f"{path}.gz", ContentFile("{}"))
|
||||
assert len(datalayer.geojson.storage.listdir(root)[1]) == 10 + before
|
||||
files = datalayer.geojson.storage.listdir(root)[1]
|
||||
datalayer.data.storage.save(root / path, ContentFile("{}"))
|
||||
datalayer.data.storage.save(root / f"{path}.gz", ContentFile("{}"))
|
||||
assert len(datalayer.data.storage.listdir(root)[1]) == 10 + before
|
||||
files = datalayer.data.storage.listdir(root)[1]
|
||||
# Those files should be present before save, which will purge them
|
||||
assert older in files
|
||||
assert older + ".gz" in files
|
||||
assert with_old_id in files
|
||||
assert with_old_id + ".gz" in files
|
||||
datalayer.save()
|
||||
files = datalayer.geojson.storage.listdir(root)[1]
|
||||
files = datalayer.data.storage.listdir(root)[1]
|
||||
# Flat + gz files, but not latest gz, which is created at first datalayer read.
|
||||
# older and with_old_id should have been removed
|
||||
assert len(files) == 5
|
||||
assert newer in files
|
||||
assert medium in files
|
||||
assert Path(datalayer.geojson.path).name in files
|
||||
assert Path(datalayer.data.path).name in files
|
||||
# File from another datalayer, purge should have impacted it.
|
||||
assert other in files
|
||||
assert other + ".gz" in files
|
||||
|
@ -97,7 +97,7 @@ def test_should_remove_old_versions_on_save(map, settings):
|
|||
assert with_old_id not in files
|
||||
assert with_old_id + ".gz" not in files
|
||||
names = [v["name"] for v in datalayer.versions]
|
||||
assert names == [Path(datalayer.geojson.name).name, newer, medium]
|
||||
assert names == [Path(datalayer.data.name).name, newer, medium]
|
||||
|
||||
|
||||
def test_anonymous_cannot_edit_in_editors_mode(datalayer):
|
||||
|
|
|
@ -21,9 +21,9 @@ def post_data():
|
|||
"display_on_load": True,
|
||||
"settings": '{"displayOnLoad": true, "browsable": true, "name": "name"}',
|
||||
"rank": 0,
|
||||
"geojson": SimpleUploadedFile(
|
||||
"data": SimpleUploadedFile(
|
||||
"name.json",
|
||||
b'{"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"}}',
|
||||
b'{"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":{},{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1.8017578124999998,51.16556659836182],[-0.48339843749999994,49.710272582105695],[-3.1640625,50.0923932109388],[-5.60302734375,51.998410382390325]]},"properties":{"name":"Light line"}},{"type":"Feature","geometry":{"type":"Point","coordinates":[0.63720703125,51.15178610143037]},"properties":{"name":"marker he"}}]}',
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,6 @@ def test_get_with_public_mode(client, settings, datalayer, map):
|
|||
assert response["Cache-Control"] is not None
|
||||
assert "Content-Encoding" not in response
|
||||
j = json.loads(response.content.decode())
|
||||
assert "_umap_options" in j
|
||||
assert "features" in j
|
||||
assert j["type"] == "FeatureCollection"
|
||||
|
||||
|
@ -98,8 +97,8 @@ def test_gzip_should_be_created_if_accepted(client, datalayer, map, post_data):
|
|||
url = reverse("datalayer_view", args=(map.pk, datalayer.pk))
|
||||
response = client.get(url, headers={"ACCEPT_ENCODING": "gzip"})
|
||||
assert response.status_code == 200
|
||||
flat = datalayer.geojson.path
|
||||
gzipped = datalayer.geojson.path + ".gz"
|
||||
flat = datalayer.data.path
|
||||
gzipped = datalayer.data.path + ".gz"
|
||||
assert Path(flat).exists()
|
||||
assert Path(gzipped).exists()
|
||||
assert Path(flat).stat().st_mtime_ns == Path(gzipped).stat().st_mtime_ns
|
||||
|
@ -122,7 +121,7 @@ def test_update(client, datalayer, map, post_data):
|
|||
assert "id" in j
|
||||
assert str(datalayer.pk) == j["id"]
|
||||
assert j["browsable"] is True
|
||||
assert Path(modified_datalayer.geojson.path).exists()
|
||||
assert Path(modified_datalayer.data.path).exists()
|
||||
|
||||
|
||||
def test_should_not_be_possible_to_update_with_wrong_map_id_in_url(
|
||||
|
@ -216,13 +215,13 @@ def test_versions_should_return_versions(client, datalayer, map, settings):
|
|||
map.share_status = Map.PUBLIC
|
||||
map.save()
|
||||
root = datalayer.storage_root()
|
||||
datalayer.geojson.storage.save(
|
||||
datalayer.data.storage.save(
|
||||
"%s/%s_1440924889.geojson" % (root, datalayer.pk), ContentFile("{}")
|
||||
)
|
||||
datalayer.geojson.storage.save(
|
||||
datalayer.data.storage.save(
|
||||
"%s/%s_1440923687.geojson" % (root, datalayer.pk), ContentFile("{}")
|
||||
)
|
||||
datalayer.geojson.storage.save(
|
||||
datalayer.data.storage.save(
|
||||
"%s/%s_1440918637.geojson" % (root, datalayer.pk), ContentFile("{}")
|
||||
)
|
||||
url = reverse("datalayer_versions", args=(map.pk, datalayer.pk))
|
||||
|
@ -243,18 +242,16 @@ def test_versions_can_return_old_format(client, datalayer, map, settings):
|
|||
datalayer.old_id = 123 # old datalayer id (now replaced by uuid)
|
||||
datalayer.save()
|
||||
|
||||
datalayer.geojson.storage.save(
|
||||
datalayer.data.storage.save(
|
||||
"%s/%s_1440924889.geojson" % (root, datalayer.pk), ContentFile("{}")
|
||||
)
|
||||
datalayer.geojson.storage.save(
|
||||
datalayer.data.storage.save(
|
||||
"%s/%s_1440923687.geojson" % (root, datalayer.pk), ContentFile("{}")
|
||||
)
|
||||
|
||||
# store with the id prefix (rather than the uuid)
|
||||
old_format_version = "%s_1440918637.geojson" % datalayer.old_id
|
||||
datalayer.geojson.storage.save(
|
||||
("%s/" % root) + old_format_version, ContentFile("{}")
|
||||
)
|
||||
datalayer.data.storage.save(("%s/" % root) + old_format_version, ContentFile("{}"))
|
||||
|
||||
url = reverse("datalayer_versions", args=(map.pk, datalayer.pk))
|
||||
versions = json.loads(client.get(url).content.decode())
|
||||
|
@ -276,7 +273,7 @@ def test_version_should_return_one_version_geojson(client, datalayer, map):
|
|||
map.save()
|
||||
root = datalayer.storage_root()
|
||||
name = "%s_1440924889.geojson" % datalayer.pk
|
||||
datalayer.geojson.storage.save("%s/%s" % (root, name), ContentFile("{}"))
|
||||
datalayer.data.storage.save("%s/%s" % (root, name), ContentFile("{}"))
|
||||
url = reverse("datalayer_version", args=(map.pk, datalayer.pk, name))
|
||||
assert client.get(url).content.decode() == "{}"
|
||||
|
||||
|
@ -286,7 +283,7 @@ def test_version_should_return_403_if_not_allowed(client, datalayer, map):
|
|||
map.save()
|
||||
root = datalayer.storage_root()
|
||||
name = "%s_1440924889.geojson" % datalayer.pk
|
||||
datalayer.geojson.storage.save("%s/%s" % (root, name), ContentFile("{}"))
|
||||
datalayer.data.storage.save("%s/%s" % (root, name), ContentFile("{}"))
|
||||
url = reverse("datalayer_version", args=(map.pk, datalayer.pk, name))
|
||||
assert client.get(url).status_code == 403
|
||||
|
||||
|
@ -447,27 +444,19 @@ def reference_data():
|
|||
{
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [-1, 2]},
|
||||
"properties": {"_umap_options": {}, "name": "foo"},
|
||||
"properties": {"name": "foo"},
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "LineString", "coordinates": [2, 3]},
|
||||
"properties": {"_umap_options": {}, "name": "bar"},
|
||||
"properties": {"name": "bar"},
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [3, 4]},
|
||||
"properties": {"_umap_options": {}, "name": "marker"},
|
||||
"properties": {"name": "marker"},
|
||||
},
|
||||
],
|
||||
"_umap_options": {
|
||||
"displayOnLoad": True,
|
||||
"name": "new name",
|
||||
"id": 1668,
|
||||
"remoteData": {},
|
||||
"color": "LightSeaGreen",
|
||||
"description": "test",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -482,7 +471,7 @@ def test_optimistic_merge_both_added(client, datalayer, map, reference_data):
|
|||
"name": "name",
|
||||
"display_on_load": True,
|
||||
"rank": 0,
|
||||
"geojson": SimpleUploadedFile(
|
||||
"data": SimpleUploadedFile(
|
||||
"foo.json", json.dumps(reference_data).encode("utf-8")
|
||||
),
|
||||
}
|
||||
|
@ -496,12 +485,12 @@ def test_optimistic_merge_both_added(client, datalayer, map, reference_data):
|
|||
client1_feature = {
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [5, 6]},
|
||||
"properties": {"_umap_options": {}, "name": "marker"},
|
||||
"properties": {"name": "marker"},
|
||||
}
|
||||
client1_data = deepcopy(reference_data)
|
||||
client1_data["features"].append(client1_feature)
|
||||
|
||||
post_data["geojson"] = SimpleUploadedFile(
|
||||
post_data["data"] = SimpleUploadedFile(
|
||||
"foo.json",
|
||||
json.dumps(client1_data).encode("utf-8"),
|
||||
)
|
||||
|
@ -517,12 +506,12 @@ def test_optimistic_merge_both_added(client, datalayer, map, reference_data):
|
|||
client2_feature = {
|
||||
"type": "Feature",
|
||||
"geometry": {"type": "Point", "coordinates": [7, 8]},
|
||||
"properties": {"_umap_options": {}, "name": "marker"},
|
||||
"properties": {"name": "marker"},
|
||||
}
|
||||
client2_data = deepcopy(reference_data)
|
||||
client2_data["features"].append(client2_feature)
|
||||
|
||||
post_data["geojson"] = SimpleUploadedFile(
|
||||
post_data["data"] = SimpleUploadedFile(
|
||||
"foo.json",
|
||||
json.dumps(client2_data).encode("utf-8"),
|
||||
)
|
||||
|
@ -534,7 +523,7 @@ def test_optimistic_merge_both_added(client, datalayer, map, reference_data):
|
|||
)
|
||||
assert response.status_code == 200
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
merged_features = json.load(modified_datalayer.geojson)["features"]
|
||||
merged_features = json.load(modified_datalayer.data)["features"]
|
||||
|
||||
for reference_feature in reference_data["features"]:
|
||||
assert reference_feature in merged_features
|
||||
|
@ -556,7 +545,7 @@ def test_optimistic_merge_conflicting_change_raises(
|
|||
"name": "name",
|
||||
"display_on_load": True,
|
||||
"rank": 0,
|
||||
"geojson": SimpleUploadedFile(
|
||||
"data": SimpleUploadedFile(
|
||||
"foo.json", json.dumps(reference_data).encode("utf-8")
|
||||
),
|
||||
}
|
||||
|
@ -571,7 +560,7 @@ def test_optimistic_merge_conflicting_change_raises(
|
|||
client1_data = deepcopy(reference_data)
|
||||
client1_data["features"][0]["geometry"] = {"type": "Point", "coordinates": [5, 6]}
|
||||
|
||||
post_data["geojson"] = SimpleUploadedFile(
|
||||
post_data["data"] = SimpleUploadedFile(
|
||||
"foo.json",
|
||||
json.dumps(client1_data).encode("utf-8"),
|
||||
)
|
||||
|
@ -587,7 +576,7 @@ def test_optimistic_merge_conflicting_change_raises(
|
|||
client2_data = deepcopy(reference_data)
|
||||
client2_data["features"][0]["geometry"] = {"type": "Point", "coordinates": [7, 8]}
|
||||
|
||||
post_data["geojson"] = SimpleUploadedFile(
|
||||
post_data["data"] = SimpleUploadedFile(
|
||||
"foo.json",
|
||||
json.dumps(client2_data).encode("utf-8"),
|
||||
)
|
||||
|
@ -601,5 +590,5 @@ def test_optimistic_merge_conflicting_change_raises(
|
|||
|
||||
# Check that the server rejected conflicting changes.
|
||||
modified_datalayer = DataLayer.objects.get(pk=datalayer.pk)
|
||||
merged_features = json.load(modified_datalayer.geojson)["features"]
|
||||
merged_features = json.load(modified_datalayer.data)["features"]
|
||||
assert merged_features == client1_data["features"]
|
||||
|
|
|
@ -73,7 +73,7 @@ def test_clone_should_return_new_instance(map, user):
|
|||
clone = map.clone()
|
||||
assert map.pk != clone.pk
|
||||
assert "Clone of " + map.name == clone.name
|
||||
assert map.settings == clone.settings
|
||||
assert map.metadata == clone.metadata
|
||||
assert map.center == clone.center
|
||||
assert map.zoom == clone.zoom
|
||||
assert map.licence == clone.licence
|
||||
|
@ -103,8 +103,8 @@ def test_clone_should_clone_datalayers_and_features_too(map, user, datalayer):
|
|||
assert datalayer in map.datalayer_set.all()
|
||||
assert other.pk != datalayer.pk
|
||||
assert other.name == datalayer.name
|
||||
assert other.geojson is not None
|
||||
assert other.geojson.path != datalayer.geojson.path
|
||||
assert other.data is not None
|
||||
assert other.data.path != datalayer.data.path
|
||||
|
||||
|
||||
def test_publicmanager_should_get_only_public_maps(map, user, licence):
|
||||
|
|
|
@ -310,12 +310,14 @@ def test_non_editor_cannot_access_map_if_share_status_private(client, map, user)
|
|||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_map_geojson_view(client, map):
|
||||
url = reverse("map_geojson", args=(map.pk,))
|
||||
def test_map_metadata_view(client, map):
|
||||
url = reverse("map_metadata", args=(map.pk,))
|
||||
response = client.get(url)
|
||||
j = json.loads(response.content.decode())
|
||||
assert "json" in response["content-type"]
|
||||
assert "type" in j
|
||||
assert "geometry" in j
|
||||
assert "zoom" in j
|
||||
assert "umap_id" in j
|
||||
|
||||
|
||||
def test_only_owner_can_delete(client, map, user):
|
||||
|
@ -640,11 +642,7 @@ def test_download(client, map, datalayer):
|
|||
j = json.loads(response.content.decode())
|
||||
assert j["type"] == "umap"
|
||||
assert j["uri"] == f"http://testserver/en/map/test-map_{map.pk}"
|
||||
assert j["geometry"] == {
|
||||
"coordinates": [13.447265624999998, 48.94415123418794],
|
||||
"type": "Point",
|
||||
}
|
||||
assert j["properties"] == {
|
||||
assert j["metadata"] == {
|
||||
"datalayersControl": True,
|
||||
"description": "Which is just the Danube, at the end",
|
||||
"displayPopupFooter": False,
|
||||
|
@ -662,10 +660,14 @@ def test_download(client, map, datalayer):
|
|||
"tilelayersControl": True,
|
||||
"zoom": 7,
|
||||
"zoomControl": True,
|
||||
"geometry": {
|
||||
"coordinates": [13.447265624999998, 48.94415123418794],
|
||||
"type": "Point",
|
||||
},
|
||||
}
|
||||
assert j["layers"] == [
|
||||
{
|
||||
"_umap_options": {
|
||||
"metadata": {
|
||||
"browsable": True,
|
||||
"displayOnLoad": True,
|
||||
"name": "test datalayer",
|
||||
|
@ -676,8 +678,8 @@ def test_download(client, map, datalayer):
|
|||
"coordinates": [14.68896484375, 48.55297816440071],
|
||||
"type": "Point",
|
||||
},
|
||||
"metadata": {"color": "DarkCyan", "iconClass": "Ball"},
|
||||
"properties": {
|
||||
"_umap_options": {"color": "DarkCyan", "iconClass": "Ball"},
|
||||
"description": "Da place anonymous again 755",
|
||||
"name": "Here",
|
||||
},
|
||||
|
@ -705,13 +707,12 @@ def test_download_multiple_maps(client, map, datalayer):
|
|||
assert f.infolist()[1].filename == f"umap_backup_test-map_{map.id}.umap"
|
||||
with f.open(f.infolist()[1]) as umap_file:
|
||||
umapjson = json.loads(umap_file.read().decode())
|
||||
assert list(umapjson.keys()) == [
|
||||
assert set(umapjson.keys()) == {
|
||||
"type",
|
||||
"geometry",
|
||||
"properties",
|
||||
"metadata",
|
||||
"uri",
|
||||
"layers",
|
||||
]
|
||||
}
|
||||
assert umapjson["type"] == "umap"
|
||||
assert umapjson["uri"] == f"http://testserver/en/map/test-map_{map.id}"
|
||||
|
||||
|
|
|
@ -55,9 +55,9 @@ i18n_urls = [
|
|||
),
|
||||
re_path(r"^logout/$", views.logout, name="logout"),
|
||||
re_path(
|
||||
r"^map/(?P<map_id>\d+)/geojson/$",
|
||||
views.MapViewGeoJSON.as_view(),
|
||||
name="map_geojson",
|
||||
r"^map/(?P<map_id>\d+)/metadata/$",
|
||||
views.MapMetadata.as_view(),
|
||||
name="map_metadata",
|
||||
),
|
||||
re_path(
|
||||
r"^map/anonymous-edit/(?P<signature>.+)$",
|
||||
|
|
123
umap/views.py
123
umap/views.py
|
@ -60,7 +60,7 @@ from .forms import (
|
|||
DataLayerForm,
|
||||
DataLayerPermissionsForm,
|
||||
FlatErrorList,
|
||||
MapSettingsForm,
|
||||
MapMetadataForm,
|
||||
SendLinkForm,
|
||||
UpdateMapPermissionsForm,
|
||||
UserProfileForm,
|
||||
|
@ -321,9 +321,9 @@ class UserDownload(DetailView, SearchMixin):
|
|||
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
|
||||
for map_ in self.get_maps():
|
||||
umapjson = map_.generate_umapjson(self.request)
|
||||
geojson_file = io.StringIO(json_dumps(umapjson))
|
||||
json_file = io.StringIO(json_dumps(umapjson))
|
||||
file_name = f"umap_backup_{map_.slug}_{map_.pk}.umap"
|
||||
zip_file.writestr(file_name, geojson_file.getvalue())
|
||||
zip_file.writestr(file_name, json_file.getvalue())
|
||||
|
||||
response = HttpResponse(zip_buffer.getvalue(), content_type="application/zip")
|
||||
response["Content-Disposition"] = (
|
||||
|
@ -352,10 +352,9 @@ class MapsShowCase(View):
|
|||
description = "{}\n[[{}|{}]]".format(
|
||||
description, m.get_absolute_url(), _("View the map")
|
||||
)
|
||||
geometry = m.settings.get("geometry", json.loads(m.center.geojson))
|
||||
return {
|
||||
"type": "Feature",
|
||||
"geometry": geometry,
|
||||
"geometry": m.geometry,
|
||||
"properties": {"name": m.name, "description": description},
|
||||
}
|
||||
|
||||
|
@ -489,13 +488,13 @@ class MapDetailMixin(SessionMixin):
|
|||
model = Map
|
||||
pk_url_kwarg = "map_id"
|
||||
|
||||
def set_preconnect(self, properties, context):
|
||||
def set_preconnect(self, metadata, context):
|
||||
# Try to extract the tilelayer domain, in order to but a preconnect meta.
|
||||
url_template = properties.get("tilelayer", {}).get("url_template")
|
||||
url_template = metadata.get("tilelayer", {}).get("url_template")
|
||||
# Not explicit tilelayer set, take the first of the list, which will be
|
||||
# used by frontend too.
|
||||
if not url_template:
|
||||
tilelayers = properties.get("tilelayers")
|
||||
tilelayers = metadata.get("tilelayers")
|
||||
if tilelayers:
|
||||
url_template = tilelayers[0].get("url_template")
|
||||
if url_template:
|
||||
|
@ -504,9 +503,9 @@ class MapDetailMixin(SessionMixin):
|
|||
if domain and "{" not in domain:
|
||||
context["preconnect_domains"] = [f"//{domain}"]
|
||||
|
||||
def get_map_properties(self):
|
||||
def get_metadata(self):
|
||||
user = self.request.user
|
||||
properties = {
|
||||
metadata = {
|
||||
"urls": _urls_for_js(),
|
||||
"tilelayers": TileLayer.get_list(),
|
||||
"editMode": self.edit_mode,
|
||||
|
@ -522,6 +521,8 @@ class MapDetailMixin(SessionMixin):
|
|||
"websocketEnabled": settings.WEBSOCKET_ENABLED,
|
||||
"websocketURI": settings.WEBSOCKET_FRONT_URI,
|
||||
"importers": settings.UMAP_IMPORTERS,
|
||||
"zoom": self.get_zoom(),
|
||||
"geometry": self.get_geometry(),
|
||||
}
|
||||
created = bool(getattr(self, "object", None))
|
||||
if (created and self.object.owner) or (not created and not user.is_anonymous):
|
||||
|
@ -530,35 +531,31 @@ class MapDetailMixin(SessionMixin):
|
|||
else:
|
||||
map_statuses = AnonymousMapPermissionsForm.STATUS
|
||||
datalayer_statuses = AnonymousDataLayerPermissionsForm.STATUS
|
||||
properties["edit_statuses"] = [(i, str(label)) for i, label in map_statuses]
|
||||
properties["datalayer_edit_statuses"] = [
|
||||
metadata["edit_statuses"] = [(i, str(label)) for i, label in map_statuses]
|
||||
metadata["datalayer_edit_statuses"] = [
|
||||
(i, str(label)) for i, label in datalayer_statuses
|
||||
]
|
||||
if self.get_short_url():
|
||||
properties["shortUrl"] = self.get_short_url()
|
||||
metadata["shortUrl"] = self.get_short_url()
|
||||
|
||||
properties["user"] = self.get_user_data()
|
||||
return properties
|
||||
metadata["user"] = self.get_user_data()
|
||||
return metadata
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
properties = self.get_map_properties()
|
||||
metadata = self.get_metadata()
|
||||
if settings.USE_I18N:
|
||||
lang = settings.LANGUAGE_CODE
|
||||
# Check attr in case the middleware is not active
|
||||
if hasattr(self.request, "LANGUAGE_CODE"):
|
||||
lang = self.request.LANGUAGE_CODE
|
||||
properties["lang"] = lang
|
||||
metadata["lang"] = lang
|
||||
locale = translation.to_locale(lang)
|
||||
properties["locale"] = locale
|
||||
metadata["locale"] = locale
|
||||
context["locale"] = locale
|
||||
geojson = self.get_geojson()
|
||||
if "properties" not in geojson:
|
||||
geojson["properties"] = {}
|
||||
geojson["properties"].update(properties)
|
||||
geojson["properties"]["datalayers"] = self.get_datalayers()
|
||||
context["map_settings"] = json_dumps(geojson, indent=settings.DEBUG)
|
||||
self.set_preconnect(geojson["properties"], context)
|
||||
metadata["datalayers"] = self.get_datalayers()
|
||||
context["map_metadata"] = json_dumps(metadata, indent=settings.DEBUG)
|
||||
self.set_preconnect(metadata, context)
|
||||
return context
|
||||
|
||||
def get_datalayers(self):
|
||||
|
@ -574,18 +571,24 @@ class MapDetailMixin(SessionMixin):
|
|||
def is_starred(self):
|
||||
return False
|
||||
|
||||
def get_geojson(self):
|
||||
def get_geometry(self):
|
||||
return {
|
||||
"geometry": {
|
||||
"coordinates": [DEFAULT_LONGITUDE, DEFAULT_LATITUDE],
|
||||
"type": "Point",
|
||||
},
|
||||
"properties": {
|
||||
"zoom": getattr(settings, "LEAFLET_ZOOM", 6),
|
||||
"datalayers": [],
|
||||
},
|
||||
"coordinates": [DEFAULT_LONGITUDE, DEFAULT_LATITUDE],
|
||||
"type": "Point",
|
||||
}
|
||||
|
||||
def get_zoom(self):
|
||||
return settings.LEAFLET_ZOOM
|
||||
|
||||
# def get_geojson(self):
|
||||
# return {
|
||||
# "geometry": ,
|
||||
# "properties": {
|
||||
# "zoom": getattr(settings, "LEAFLET_ZOOM", 6),
|
||||
# "datalayers": [],
|
||||
# },
|
||||
# }
|
||||
|
||||
def get_short_url(self):
|
||||
return None
|
||||
|
||||
|
@ -637,7 +640,7 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
|||
|
||||
def get_datalayers(self):
|
||||
return [
|
||||
dl.metadata(self.request.user, self.request)
|
||||
dl.get_metadata(self.request.user, self.request)
|
||||
for dl in self.object.datalayer_set.all()
|
||||
]
|
||||
|
||||
|
@ -663,13 +666,19 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
|||
short_url = "%s%s" % (settings.SHORT_SITE_URL, short_path)
|
||||
return short_url
|
||||
|
||||
def get_geojson(self):
|
||||
map_settings = self.object.settings
|
||||
if "properties" not in map_settings:
|
||||
map_settings["properties"] = {}
|
||||
map_settings["properties"]["name"] = self.object.name
|
||||
map_settings["properties"]["permissions"] = self.get_permissions()
|
||||
return map_settings
|
||||
def get_geometry(self):
|
||||
return self.object.geometry
|
||||
|
||||
def get_zoom(self):
|
||||
return self.object.zoom or self.object.metadata.get(
|
||||
"zoom", settings.LEAFLET_ZOOM
|
||||
)
|
||||
|
||||
def get_metadata(self):
|
||||
metadata = super().get_metadata()
|
||||
metadata["name"] = self.object.name
|
||||
metadata["permissions"] = self.get_permissions()
|
||||
return {**self.object.metadata, **metadata}
|
||||
|
||||
def is_starred(self):
|
||||
user = self.request.user
|
||||
|
@ -744,12 +753,12 @@ class MapOEmbed(View):
|
|||
return response
|
||||
|
||||
|
||||
class MapViewGeoJSON(MapView):
|
||||
class MapMetadata(MapView):
|
||||
def get_canonical_url(self):
|
||||
return reverse("map_geojson", args=(self.object.pk,))
|
||||
return reverse("map_metadata", args=(self.object.pk,))
|
||||
|
||||
def render_to_response(self, context, *args, **kwargs):
|
||||
return HttpResponse(context["map_settings"], content_type="application/json")
|
||||
return HttpResponse(context["map_metadata"], content_type="application/json")
|
||||
|
||||
|
||||
class MapNew(MapDetailMixin, TemplateView):
|
||||
|
@ -759,15 +768,15 @@ class MapNew(MapDetailMixin, TemplateView):
|
|||
class MapPreview(MapDetailMixin, TemplateView):
|
||||
template_name = "umap/map_detail.html"
|
||||
|
||||
def get_map_properties(self):
|
||||
properties = super().get_map_properties()
|
||||
def get_metadata(self):
|
||||
properties = super().get_metadata()
|
||||
properties["preview"] = True
|
||||
return properties
|
||||
|
||||
|
||||
class MapCreate(FormLessEditMixin, PermissionsMixin, SessionMixin, CreateView):
|
||||
model = Map
|
||||
form_class = MapSettingsForm
|
||||
form_class = MapMetadataForm
|
||||
|
||||
def form_valid(self, form):
|
||||
if self.request.user.is_authenticated:
|
||||
|
@ -821,11 +830,11 @@ def get_websocket_auth_token(request, map_id, map_inst):
|
|||
|
||||
class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
||||
model = Map
|
||||
form_class = MapSettingsForm
|
||||
form_class = MapMetadataForm
|
||||
pk_url_kwarg = "map_id"
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object.settings = form.cleaned_data["settings"]
|
||||
self.object.metadata = form.cleaned_data["metadata"]
|
||||
self.object.save()
|
||||
return simple_json_response(
|
||||
id=self.object.pk,
|
||||
|
@ -1016,7 +1025,7 @@ class GZipMixin(object):
|
|||
|
||||
@property
|
||||
def path(self):
|
||||
return Path(self.object.geojson.path)
|
||||
return Path(self.object.data.path)
|
||||
|
||||
@property
|
||||
def gzip_path(self):
|
||||
|
@ -1092,7 +1101,7 @@ class DataLayerCreate(FormLessEditMixin, GZipMixin, CreateView):
|
|||
self.object = form.save()
|
||||
# Simple response with only metadata (including new id)
|
||||
response = simple_json_response(
|
||||
**self.object.metadata(self.request.user, self.request)
|
||||
**self.object.get_metadata(self.request.user, self.request)
|
||||
)
|
||||
response["X-Datalayer-Version"] = self.version
|
||||
return response
|
||||
|
@ -1125,7 +1134,7 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
|
|||
# If the reference document is not found, we can't merge.
|
||||
return None
|
||||
# New data received in the request.
|
||||
incoming = json.loads(self.request.FILES["geojson"].read())
|
||||
incoming = json.loads(self.request.FILES["data"].read())
|
||||
|
||||
# Latest known version of the data.
|
||||
with open(self.path) as f:
|
||||
|
@ -1157,7 +1166,7 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
|
|||
return HttpResponse(status=412)
|
||||
|
||||
# Replace the uploaded file by the merged version.
|
||||
self.request.FILES["geojson"].file = BytesIO(
|
||||
self.request.FILES["data"].file = BytesIO(
|
||||
json_dumps(merged).encode("utf-8")
|
||||
)
|
||||
|
||||
|
@ -1167,11 +1176,11 @@ class DataLayerUpdate(FormLessEditMixin, GZipMixin, UpdateView):
|
|||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save()
|
||||
data = {**self.object.metadata(self.request.user, self.request)}
|
||||
body = {**self.object.get_metadata(self.request.user, self.request)}
|
||||
if self.request.session.get("needs_reload"):
|
||||
data["geojson"] = json.loads(self.object.geojson.read().decode())
|
||||
body["data"] = json.loads(self.object.data.read().decode())
|
||||
self.request.session["needs_reload"] = False
|
||||
response = simple_json_response(**data)
|
||||
response = simple_json_response(**body)
|
||||
response["X-Datalayer-Version"] = self.version
|
||||
return response
|
||||
|
||||
|
|
Loading…
Reference in a new issue