default geomtype, permission fix, name fix, remove ungrouped header, fix id filter
This commit is contained in:
Gwendolyn 2024-12-16 12:53:18 +01:00
parent 13cf207ee6
commit 934aa60be4
10 changed files with 96 additions and 41 deletions

View file

@ -422,6 +422,7 @@ def create_editor_form(editor_model):
'color_ground_fill', 'color_obstacles_default_fill', 'color_obstacles_default_border', 'color_ground_fill', 'color_obstacles_default_fill', 'color_obstacles_default_border',
'stroke_color', 'stroke_width', 'fill_color', 'interactive', 'point_icon', 'extra_data', 'stroke_color', 'stroke_width', 'fill_color', 'interactive', 'point_icon', 'extra_data',
'show_label', 'show_geometry', 'external_url', 'show_label', 'show_geometry', 'external_url',
'show_label', 'show_geometry', 'external_url', 'default_geomtype',
] ]
field_names = [field.name for field in editor_model._meta.get_fields() field_names = [field.name for field in editor_model._meta.get_fields()
if not field.one_to_many and not isinstance(field, ManyToManyRel)] if not field.one_to_many and not isinstance(field, ManyToManyRel)]

View file

@ -1370,6 +1370,7 @@ editor = {
} }
form.addClass('creation-lock'); form.addClass('creation-lock');
const geomtypes = form.attr('data-geomtype').split(','); const geomtypes = form.attr('data-geomtype').split(',');
const default_geomtype = form.attr('data-default-geomtype');
const startGeomEditing = (geomtype) => { const startGeomEditing = (geomtype) => {
editor._creating_type = geomtype; editor._creating_type = geomtype;
@ -1387,6 +1388,8 @@ editor = {
} }
} }
let selected_geomtype = geomtypes[0];
if (geomtypes.length > 1) { if (geomtypes.length > 1) {
const selector = $('<select id="geomtype-selector"></select>'); const selector = $('<select id="geomtype-selector"></select>');
const geomtypeNames = { const geomtypeNames = {
@ -1395,13 +1398,18 @@ editor = {
point: 'Point' point: 'Point'
}; // TODO: translations }; // TODO: translations
for(const geomtype of geomtypes) { for(const geomtype of geomtypes) {
selector.append(`<option value="${geomtype}">${geomtypeNames[geomtype]}</option>`); const option = $(`<option value="${geomtype}">${geomtypeNames[geomtype]}</option>`);
if (geomtype === default_geomtype) {
option.attr('selected', true);
selected_geomtype = geomtype;
}
selector.append(option);
} }
selector.on('change', e => startGeomEditing(e.target.value)); selector.on('change', e => startGeomEditing(e.target.value));
form.prepend(selector); form.prepend(selector);
} }
startGeomEditing(geomtypes[0]); startGeomEditing(selected_geomtype);
} }
} }
}, },

View file

@ -20,7 +20,7 @@
{% endif %} {% endif %}
</h3> </h3>
{% bootstrap_messages %} {% bootstrap_messages %}
<form action="{{ path }}" method="post" {% if nozoom %}data-nozoom {% endif %}data-onbeforeunload {% if new %}data-new="{{ model_name }}" data-geomtype="{{ geomtype }}"{% else %}data-editing="{{ model_name }}-{{ pk }}"{% endif %}{% if access_restriction_select %} data-access-restriction-select{% endif %}> <form action="{{ path }}" method="post" {% if nozoom %}data-nozoom {% endif %}data-onbeforeunload {% if new %}data-new="{{ model_name }}" data-geomtype="{{ geomtype }}" {% if default_geomtype %}data-default-geomtype="{{ default_geomtype }}{% endif %}"{% else %}data-editing="{{ model_name }}-{{ pk }}"{% endif %}{% if access_restriction_select %} data-access-restriction-select{% endif %}>
{% csrf_token %} {% csrf_token %}
{% bootstrap_form form %} {% bootstrap_form form %}
{% buttons %} {% buttons %}

View file

@ -130,6 +130,7 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None):
'title': obj.title if obj else None, 'title': obj.title if obj else None,
'geometry_url': geometry_url, 'geometry_url': geometry_url,
'geomtype': 'polygon,linestring,point', 'geomtype': 'polygon,linestring,point',
'default_geomtype': overlay.default_geomtype,
} }
space_id = None space_id = None

View file

@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2024-12-16 11:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mapdata', '0117_alter_dataoverlay_fill_color_and_more'),
]
operations = [
migrations.AddField(
model_name='dataoverlay',
name='default_geomtype',
field=models.CharField(blank=True, choices=[('polygon', 'Polygon'), ('line', 'Line'), ('point', 'Point')], max_length=255, null=True, verbose_name='default geometry type'),
),
]

View file

@ -13,10 +13,18 @@ from c3nav.mapdata.utils.json import format_geojson
class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model): class DataOverlay(TitledMixin, AccessRestrictionMixin, models.Model):
class GeometryType(models.TextChoices):
POLYGON = "polygon", _("Polygon")
LINESTRING = "linestring", _("Line string")
POINT = "point", _("Point")
description = models.TextField(blank=True, verbose_name=_('Description')) description = models.TextField(blank=True, verbose_name=_('Description'))
stroke_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default stroke color')) stroke_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default stroke color'))
stroke_width = models.FloatField(blank=True, null=True, verbose_name=_('default stroke width')) stroke_width = models.FloatField(blank=True, null=True, verbose_name=_('default stroke width'))
fill_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default fill color')) fill_color = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('default fill color'))
default_geomtype = models.CharField(max_length=255, blank=True, null=True, choices=GeometryType, verbose_name=_('default geometry type'))
pull_url = models.URLField(blank=True, null=True, verbose_name=_('pull URL')) pull_url = models.URLField(blank=True, null=True, verbose_name=_('pull URL'))
pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True, pull_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True,
verbose_name=_('headers for pull http request (JSON object)')) verbose_name=_('headers for pull http request (JSON object)'))

View file

@ -6,7 +6,8 @@ from pydantic import Field as APIField
from c3nav.api.exceptions import APIRequestValidationFailed from c3nav.api.exceptions import APIRequestValidationFailed
from c3nav.api.schema import BaseSchema from c3nav.api.schema import BaseSchema
from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space, Door, Building from c3nav.mapdata.models import Level, LocationGroup, LocationGroupCategory, MapUpdate, Space, Door, Building, \
DataOverlay
from c3nav.mapdata.models.access import AccessPermission from c3nav.mapdata.models.access import AccessPermission
@ -145,7 +146,7 @@ class ByOverlayFilter(FilterSchema):
def validate(self, request): def validate(self, request):
super().validate(request) super().validate(request)
if self.overlay is not None: if self.overlay is not None:
assert_valid_value(request, Level, "pk", {self.overlay}) assert_valid_value(request, DataOverlay, "pk", {self.overlay})
def filter_qs(self, request, qs: QuerySet) -> QuerySet: def filter_qs(self, request, qs: QuerySet) -> QuerySet:
if self.overlay is not None: if self.overlay is not None:

View file

@ -6,6 +6,7 @@ from django.utils.translation import ngettext_lazy
from c3nav.mapdata.models import DataOverlay from c3nav.mapdata.models import DataOverlay
from c3nav.mapdata.models.access import AccessPermission, AccessRestriction from c3nav.mapdata.models.access import AccessPermission, AccessRestriction
from c3nav.mapdata.models.locations import Position from c3nav.mapdata.models.locations import Position
from c3nav.mapdata.schemas.models import DataOverlaySchema
def get_user_data(request): def get_user_data(request):
@ -32,16 +33,13 @@ def get_user_data(request):
if request.user.is_authenticated: if request.user.is_authenticated:
result['title'] = request.user.username result['title'] = request.user.username
# TODO: permissions for overlays
result.update({ result.update({
'overlays': [{ 'overlays': [
'id': overlay.pk, DataOverlaySchema.model_validate(overlay).model_dump()
'name': overlay.title, for overlay
'group': None, # TODO in DataOverlay.qs_for_request(request)
'stroke_color': overlay.stroke_color, ]
'stroke_width': overlay.stroke_width,
'fill_color': overlay.fill_color,
} for overlay in DataOverlay.objects.all()]
}) })
return result return result

View file

@ -1811,14 +1811,7 @@ blink {
} }
label { label {
cursor: pointer;
margin-left: 3ch; margin-left: 3ch;
margin-bottom: 0;
}
input[type=checkbox] {
margin-right: 0.5rem;
margin-bottom: 0;
} }
} }
@ -1832,6 +1825,16 @@ blink {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
label {
cursor: pointer;
margin-bottom: 0;
}
input[type=checkbox] {
margin-right: 0.5rem;
margin-bottom: 0;
}
} }
&.leaflet-control-overlays-expanded > .content { &.leaflet-control-overlays-expanded > .content {

View file

@ -2615,6 +2615,7 @@ KeyControl = L.Control.extend({
OverlayControl = L.Control.extend({ OverlayControl = L.Control.extend({
options: {position: 'topright', addClasses: '', levels: {}}, options: {position: 'topright', addClasses: '', levels: {}},
_overlays: {}, _overlays: {},
_ungrouped: [],
_groups: {}, _groups: {},
_initialActiveOverlays: null, _initialActiveOverlays: null,
_initialCollapsedGroups: null, _initialCollapsedGroups: null,
@ -2685,14 +2686,19 @@ OverlayControl = L.Control.extend({
addOverlay: function (overlay) { addOverlay: function (overlay) {
this._overlays[overlay.id] = overlay; this._overlays[overlay.id] = overlay;
if (overlay.group in this._groups) { if (overlay.group == null) {
this._groups[overlay.group].overlays.push(overlay); this._ungrouped.push(overlay);
} else { } else {
this._groups[overlay.group] = { if (overlay.group in this._groups) {
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group), this._groups[overlay.group].overlays.push(overlay);
overlays: [overlay], } else {
}; this._groups[overlay.group] = {
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group),
overlays: [overlay],
};
}
} }
this.render(); this.render();
}, },
@ -2709,7 +2715,28 @@ OverlayControl = L.Control.extend({
render: function () { render: function () {
if (!this._content) return; if (!this._content) return;
const ungrouped = document.createDocumentFragment();
const groups = document.createDocumentFragment(); const groups = document.createDocumentFragment();
const render_overlays = (overlays, container) => {
for (const overlay of overlays) {
const label = document.createElement('label');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.dataset.id = overlay.id;
if (overlay.visible) {
checkbox.checked = true;
}
label.append(checkbox, overlay.title);
container.append(label);
}
};
render_overlays(this._ungrouped, ungrouped);
for (const group in this._groups) { for (const group in this._groups) {
const group_container = document.createElement('div'); const group_container = document.createElement('div');
group_container.classList.add('overlay-group'); group_container.classList.add('overlay-group');
@ -2721,20 +2748,10 @@ OverlayControl = L.Control.extend({
const title = document.createElement('h4'); const title = document.createElement('h4');
title.innerText = group; title.innerText = group;
group_container.append(title); group_container.append(title);
for (const overlay of this._groups[group].overlays) { render_overlays(this._groups[group].overlays, group_container);
const label = document.createElement('label');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.dataset.id = overlay.id;
if (overlay.visible) {
checkbox.checked = true;
}
label.append(checkbox, overlay.name);
group_container.append(label);
}
groups.append(group_container); groups.append(group_container);
} }
this._content.replaceChildren(...groups.children); this._content.replaceChildren(...ungrouped.children, ...groups.children);
}, },
expand: function () { expand: function () {
@ -2832,8 +2849,8 @@ class DataOverlay {
constructor(options) { constructor(options) {
this.id = options.id; this.id = options.id;
this.name = options.name; this.title = options.title;
this.group = options.group ?? 'ungrouped'; this.group = options.group;
this.default_stroke_color = options.stroke_color; this.default_stroke_color = options.stroke_color;
this.default_stroke_width = options.stroke_width; this.default_stroke_width = options.stroke_width;
this.default_fill_color = options.fill_color; this.default_fill_color = options.fill_color;