mirror of
https://github.com/umap-project/umap.git
synced 2025-04-28 19:42:36 +02:00
parent
e2f154f62e
commit
39f38a9cdf
9 changed files with 90 additions and 5 deletions
|
@ -4,7 +4,6 @@ from django.contrib.auth import get_user_model
|
||||||
from django.contrib.gis.geos import Point
|
from django.contrib.gis.geos import Point
|
||||||
from django.forms.utils import ErrorList
|
from django.forms.utils import ErrorList
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from .models import DataLayer, Map, Team
|
from .models import DataLayer, Map, Team
|
||||||
|
|
||||||
|
@ -92,7 +91,7 @@ class MapSettingsForm(forms.ModelForm):
|
||||||
return self.cleaned_data["center"]
|
return self.cleaned_data["center"]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ("settings", "name", "center", "slug")
|
fields = ("settings", "name", "center", "slug", "tags")
|
||||||
model = Map
|
model = Map
|
||||||
|
|
||||||
|
|
||||||
|
|
23
umap/migrations/0027_map_tags.py
Normal file
23
umap/migrations/0027_map_tags.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 5.1.6 on 2025-02-26 16:18
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("umap", "0026_datalayer_modified_at_datalayer_share_status"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="map",
|
||||||
|
name="tags",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.CharField(max_length=200),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,7 @@ import uuid
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.gis.db import models
|
from django.contrib.gis.db import models
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.core.files.base import File
|
from django.core.files.base import File
|
||||||
from django.core.files.storage import storages
|
from django.core.files.storage import storages
|
||||||
from django.core.signing import Signer
|
from django.core.signing import Signer
|
||||||
|
@ -236,6 +237,7 @@ class Map(NamedModel):
|
||||||
settings = models.JSONField(
|
settings = models.JSONField(
|
||||||
blank=True, null=True, verbose_name=_("settings"), default=dict
|
blank=True, null=True, verbose_name=_("settings"), default=dict
|
||||||
)
|
)
|
||||||
|
tags = ArrayField(models.CharField(max_length=200), blank=True, default=list)
|
||||||
|
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
public = PublicManager()
|
public = PublicManager()
|
||||||
|
@ -420,7 +422,8 @@ class Map(NamedModel):
|
||||||
return {
|
return {
|
||||||
"iconUrl": {
|
"iconUrl": {
|
||||||
"default": "%sumap/img/marker.svg" % settings.STATIC_URL,
|
"default": "%sumap/img/marker.svg" % settings.STATIC_URL,
|
||||||
}
|
},
|
||||||
|
"tags": {"choices": settings.UMAP_TAGS},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from email.utils import parseaddr
|
||||||
|
|
||||||
import environ
|
import environ
|
||||||
from django.conf.locale import LANG_INFO
|
from django.conf.locale import LANG_INFO
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
import umap as project_module
|
import umap as project_module
|
||||||
|
|
||||||
|
@ -290,6 +291,19 @@ UMAP_HOME_FEED = "latest"
|
||||||
UMAP_IMPORTERS = {}
|
UMAP_IMPORTERS = {}
|
||||||
UMAP_HOST_INFOS = {}
|
UMAP_HOST_INFOS = {}
|
||||||
UMAP_LABEL_KEYS = ["name", "title"]
|
UMAP_LABEL_KEYS = ["name", "title"]
|
||||||
|
UMAP_TAGS = (
|
||||||
|
("art", _("Art and Culture")),
|
||||||
|
("bike", _("Bike")),
|
||||||
|
("environment", _("Environment")),
|
||||||
|
("education", _("Education")),
|
||||||
|
("food", _("Food and Agriculture")),
|
||||||
|
("history", _("History")),
|
||||||
|
("public", _("Public sector")),
|
||||||
|
("sport", _("Sport and Leisure")),
|
||||||
|
("travel", _("Travel")),
|
||||||
|
("trekking", _("Trekking")),
|
||||||
|
("tourism", _("Tourism")),
|
||||||
|
)
|
||||||
|
|
||||||
UMAP_READONLY = env("UMAP_READONLY", default=False)
|
UMAP_READONLY = env("UMAP_READONLY", default=False)
|
||||||
UMAP_GZIP = True
|
UMAP_GZIP = True
|
||||||
|
|
|
@ -138,6 +138,8 @@ export class MutatingForm extends Form {
|
||||||
} else if (properties.type === Number) {
|
} else if (properties.type === Number) {
|
||||||
if (properties.step) properties.handler = 'Range'
|
if (properties.step) properties.handler = 'Range'
|
||||||
else properties.handler = 'IntInput'
|
else properties.handler = 'IntInput'
|
||||||
|
} else if (properties.type === Array) {
|
||||||
|
properties.handler = 'CheckBoxes'
|
||||||
} else if (properties.choices) {
|
} else if (properties.choices) {
|
||||||
const text_length = properties.choices.reduce(
|
const text_length = properties.choices.reduce(
|
||||||
(acc, [_, label]) => acc + label.length,
|
(acc, [_, label]) => acc + label.length,
|
||||||
|
|
|
@ -324,6 +324,24 @@ Fields.CheckBox = class extends BaseElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Fields.CheckBoxes = class extends BaseElement {
|
||||||
|
build() {
|
||||||
|
const initial = this.get() || []
|
||||||
|
for (const [value, label] of this.properties.choices) {
|
||||||
|
const tpl = `<label><input type=checkbox value="${value}" name="${this.name}" data-ref=input />${label}</label>`
|
||||||
|
const [root, { input }] = Utils.loadTemplateWithRefs(tpl)
|
||||||
|
this.container.appendChild(root)
|
||||||
|
input.checked = initial.includes(value)
|
||||||
|
input.addEventListener('change', () => this.sync())
|
||||||
|
}
|
||||||
|
super.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
value() {
|
||||||
|
return Array.from(this.root.querySelectorAll('input:checked')).map((el) => el.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Fields.Select = class extends BaseElement {
|
Fields.Select = class extends BaseElement {
|
||||||
getTemplate() {
|
getTemplate() {
|
||||||
return `<select name="${this.name}" data-ref=select></select>`
|
return `<select name="${this.name}" data-ref=select></select>`
|
||||||
|
@ -1296,13 +1314,14 @@ Fields.ManageEditors = class extends BaseElement {
|
||||||
placeholder: translate("Type editor's username"),
|
placeholder: translate("Type editor's username"),
|
||||||
}
|
}
|
||||||
this.autocomplete = new AjaxAutocompleteMultiple(this.container, options)
|
this.autocomplete = new AjaxAutocompleteMultiple(this.container, options)
|
||||||
this._values = this.toHTML()
|
this._values = this.toHTML() || []
|
||||||
if (this._values)
|
if (this._values) {
|
||||||
for (let i = 0; i < this._values.length; i++)
|
for (let i = 0; i < this._values.length; i++)
|
||||||
this.autocomplete.displaySelected({
|
this.autocomplete.displaySelected({
|
||||||
item: { value: this._values[i].id, label: this._values[i].name },
|
item: { value: this._values[i].id, label: this._values[i].name },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
value() {
|
value() {
|
||||||
return this._values
|
return this._values
|
||||||
|
|
|
@ -516,6 +516,9 @@ export const SCHEMA = {
|
||||||
helpEntries: ['sync'],
|
helpEntries: ['sync'],
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
tags: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
team: {
|
team: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
|
|
@ -755,6 +755,12 @@ export default class Umap {
|
||||||
const form = builder.build()
|
const form = builder.build()
|
||||||
container.appendChild(form)
|
container.appendChild(form)
|
||||||
|
|
||||||
|
const tags = DomUtil.createFieldset(container, translate('Tags'))
|
||||||
|
const tagsFields = ['properties.tags']
|
||||||
|
const tagsBuilder = new MutatingForm(this, tagsFields, {
|
||||||
|
umap: this,
|
||||||
|
})
|
||||||
|
tags.appendChild(tagsBuilder.build())
|
||||||
const credits = DomUtil.createFieldset(container, translate('Credits'))
|
const credits = DomUtil.createFieldset(container, translate('Credits'))
|
||||||
const creditsFields = [
|
const creditsFields = [
|
||||||
'properties.licence',
|
'properties.licence',
|
||||||
|
@ -1168,6 +1174,7 @@ export default class Umap {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('name', this.properties.name)
|
formData.append('name', this.properties.name)
|
||||||
formData.append('center', JSON.stringify(this.geometry()))
|
formData.append('center', JSON.stringify(this.geometry()))
|
||||||
|
formData.append('tags', this.properties.tags || [])
|
||||||
formData.append('settings', JSON.stringify(geojson))
|
formData.append('settings', JSON.stringify(geojson))
|
||||||
const uri = this.urls.get('map_save', { map_id: this.id })
|
const uri = this.urls.get('map_save', { map_id: this.id })
|
||||||
const [data, _, error] = await this.server.post(uri, {}, formData)
|
const [data, _, error] = await this.server.post(uri, {}, formData)
|
||||||
|
|
|
@ -226,3 +226,18 @@ def test_hover_tooltip_setting_should_be_persistent(live_server, map, page):
|
||||||
- text: always never on hover
|
- text: always never on hover
|
||||||
""")
|
""")
|
||||||
expect(page.locator(".umap-field-showLabel input[value=null]")).to_be_checked()
|
expect(page.locator(".umap-field-showLabel input[value=null]")).to_be_checked()
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_edit_map_tags(live_server, map, page):
|
||||||
|
map.settings["properties"]["tags"] = ["art"]
|
||||||
|
map.edit_status = Map.ANONYMOUS
|
||||||
|
map.save()
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||||
|
page.get_by_role("button", name="Edit map name and caption").click()
|
||||||
|
page.get_by_text("Tags").click()
|
||||||
|
expect(page.get_by_label("Art and Culture")).to_be_checked()
|
||||||
|
page.get_by_label("Bike").check()
|
||||||
|
with page.expect_response(re.compile("./update/settings/.*")):
|
||||||
|
page.get_by_role("button", name="Save").click()
|
||||||
|
saved = Map.objects.get(pk=map.pk)
|
||||||
|
assert saved.tags == ["art", "bike"]
|
||||||
|
|
Loading…
Reference in a new issue