From 934aa60be42d59d9854f36a62f6a1c28adb66d50 Mon Sep 17 00:00:00 2001 From: Gwendolyn Date: Mon, 16 Dec 2024 12:53:18 +0100 Subject: [PATCH] overlays default geomtype, permission fix, name fix, remove ungrouped header, fix id filter --- src/c3nav/editor/forms.py | 1 + src/c3nav/editor/static/editor/js/editor.js | 12 +++- src/c3nav/editor/templates/editor/edit.html | 2 +- src/c3nav/editor/views/overlays.py | 1 + .../0118_dataoverlay_default_geomtype.py | 18 ++++++ src/c3nav/mapdata/models/overlay.py | 8 +++ src/c3nav/mapdata/schemas/filters.py | 5 +- src/c3nav/mapdata/utils/user.py | 16 +++--- src/c3nav/site/static/site/css/c3nav.scss | 17 +++--- src/c3nav/site/static/site/js/c3nav.js | 57 ++++++++++++------- 10 files changed, 96 insertions(+), 41 deletions(-) create mode 100644 src/c3nav/mapdata/migrations/0118_dataoverlay_default_geomtype.py diff --git a/src/c3nav/editor/forms.py b/src/c3nav/editor/forms.py index c7bb105c..ec9a1c0c 100644 --- a/src/c3nav/editor/forms.py +++ b/src/c3nav/editor/forms.py @@ -422,6 +422,7 @@ def create_editor_form(editor_model): 'color_ground_fill', 'color_obstacles_default_fill', 'color_obstacles_default_border', 'stroke_color', 'stroke_width', 'fill_color', 'interactive', 'point_icon', 'extra_data', '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() if not field.one_to_many and not isinstance(field, ManyToManyRel)] diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index a76f4643..7fedeb2f 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -1370,6 +1370,7 @@ editor = { } form.addClass('creation-lock'); const geomtypes = form.attr('data-geomtype').split(','); + const default_geomtype = form.attr('data-default-geomtype'); const startGeomEditing = (geomtype) => { editor._creating_type = geomtype; @@ -1387,6 +1388,8 @@ editor = { } } + let selected_geomtype = geomtypes[0]; + if (geomtypes.length > 1) { const selector = $(''); const geomtypeNames = { @@ -1395,13 +1398,18 @@ editor = { point: 'Point' }; // TODO: translations for(const geomtype of geomtypes) { - selector.append(``); + const option = $(``); + if (geomtype === default_geomtype) { + option.attr('selected', true); + selected_geomtype = geomtype; + } + selector.append(option); } selector.on('change', e => startGeomEditing(e.target.value)); form.prepend(selector); } - startGeomEditing(geomtypes[0]); + startGeomEditing(selected_geomtype); } } }, diff --git a/src/c3nav/editor/templates/editor/edit.html b/src/c3nav/editor/templates/editor/edit.html index faa0455e..54bae714 100644 --- a/src/c3nav/editor/templates/editor/edit.html +++ b/src/c3nav/editor/templates/editor/edit.html @@ -20,7 +20,7 @@ {% endif %} {% bootstrap_messages %} -
+ {% csrf_token %} {% bootstrap_form form %} {% buttons %} diff --git a/src/c3nav/editor/views/overlays.py b/src/c3nav/editor/views/overlays.py index ba091554..581afffa 100644 --- a/src/c3nav/editor/views/overlays.py +++ b/src/c3nav/editor/views/overlays.py @@ -130,6 +130,7 @@ def overlay_feature_edit(request, level=None, overlay=None, pk=None): 'title': obj.title if obj else None, 'geometry_url': geometry_url, 'geomtype': 'polygon,linestring,point', + 'default_geomtype': overlay.default_geomtype, } space_id = None diff --git a/src/c3nav/mapdata/migrations/0118_dataoverlay_default_geomtype.py b/src/c3nav/mapdata/migrations/0118_dataoverlay_default_geomtype.py new file mode 100644 index 00000000..9a5cfe75 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0118_dataoverlay_default_geomtype.py @@ -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'), + ), + ] diff --git a/src/c3nav/mapdata/models/overlay.py b/src/c3nav/mapdata/models/overlay.py index 8c465e79..19f77eeb 100644 --- a/src/c3nav/mapdata/models/overlay.py +++ b/src/c3nav/mapdata/models/overlay.py @@ -13,10 +13,18 @@ from c3nav.mapdata.utils.json import format_geojson 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')) 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')) 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_headers: dict[str, str] = SchemaField(schema=dict[str, str], null=True, verbose_name=_('headers for pull http request (JSON object)')) diff --git a/src/c3nav/mapdata/schemas/filters.py b/src/c3nav/mapdata/schemas/filters.py index dece22e6..6aed0633 100644 --- a/src/c3nav/mapdata/schemas/filters.py +++ b/src/c3nav/mapdata/schemas/filters.py @@ -6,7 +6,8 @@ from pydantic import Field as APIField from c3nav.api.exceptions import APIRequestValidationFailed 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 @@ -145,7 +146,7 @@ class ByOverlayFilter(FilterSchema): def validate(self, request): super().validate(request) 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: if self.overlay is not None: diff --git a/src/c3nav/mapdata/utils/user.py b/src/c3nav/mapdata/utils/user.py index 21e5722d..05ccaa51 100644 --- a/src/c3nav/mapdata/utils/user.py +++ b/src/c3nav/mapdata/utils/user.py @@ -6,6 +6,7 @@ from django.utils.translation import ngettext_lazy from c3nav.mapdata.models import DataOverlay from c3nav.mapdata.models.access import AccessPermission, AccessRestriction from c3nav.mapdata.models.locations import Position +from c3nav.mapdata.schemas.models import DataOverlaySchema def get_user_data(request): @@ -32,16 +33,13 @@ def get_user_data(request): if request.user.is_authenticated: result['title'] = request.user.username - # TODO: permissions for overlays + result.update({ - 'overlays': [{ - 'id': overlay.pk, - 'name': overlay.title, - 'group': None, # TODO - 'stroke_color': overlay.stroke_color, - 'stroke_width': overlay.stroke_width, - 'fill_color': overlay.fill_color, - } for overlay in DataOverlay.objects.all()] + 'overlays': [ + DataOverlaySchema.model_validate(overlay).model_dump() + for overlay + in DataOverlay.qs_for_request(request) + ] }) return result diff --git a/src/c3nav/site/static/site/css/c3nav.scss b/src/c3nav/site/static/site/css/c3nav.scss index 1173c532..52a30788 100644 --- a/src/c3nav/site/static/site/css/c3nav.scss +++ b/src/c3nav/site/static/site/css/c3nav.scss @@ -1811,14 +1811,7 @@ blink { } label { - cursor: pointer; 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-bottom: 0; } + + label { + cursor: pointer; + margin-bottom: 0; + } + + input[type=checkbox] { + margin-right: 0.5rem; + margin-bottom: 0; + } } &.leaflet-control-overlays-expanded > .content { diff --git a/src/c3nav/site/static/site/js/c3nav.js b/src/c3nav/site/static/site/js/c3nav.js index 72523e9d..bf607ee5 100644 --- a/src/c3nav/site/static/site/js/c3nav.js +++ b/src/c3nav/site/static/site/js/c3nav.js @@ -2615,6 +2615,7 @@ KeyControl = L.Control.extend({ OverlayControl = L.Control.extend({ options: {position: 'topright', addClasses: '', levels: {}}, _overlays: {}, + _ungrouped: [], _groups: {}, _initialActiveOverlays: null, _initialCollapsedGroups: null, @@ -2685,14 +2686,19 @@ OverlayControl = L.Control.extend({ addOverlay: function (overlay) { this._overlays[overlay.id] = overlay; - if (overlay.group in this._groups) { - this._groups[overlay.group].overlays.push(overlay); + if (overlay.group == null) { + this._ungrouped.push(overlay); } else { - this._groups[overlay.group] = { - expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group), - overlays: [overlay], - }; + if (overlay.group in this._groups) { + this._groups[overlay.group].overlays.push(overlay); + } else { + this._groups[overlay.group] = { + expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group), + overlays: [overlay], + }; + } } + this.render(); }, @@ -2709,7 +2715,28 @@ OverlayControl = L.Control.extend({ render: function () { if (!this._content) return; + + const ungrouped = 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) { const group_container = document.createElement('div'); group_container.classList.add('overlay-group'); @@ -2721,20 +2748,10 @@ OverlayControl = L.Control.extend({ const title = document.createElement('h4'); title.innerText = group; group_container.append(title); - for (const overlay of this._groups[group].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.name); - group_container.append(label); - } + render_overlays(this._groups[group].overlays, group_container); groups.append(group_container); } - this._content.replaceChildren(...groups.children); + this._content.replaceChildren(...ungrouped.children, ...groups.children); }, expand: function () { @@ -2832,8 +2849,8 @@ class DataOverlay { constructor(options) { this.id = options.id; - this.name = options.name; - this.group = options.group ?? 'ungrouped'; + this.title = options.title; + this.group = options.group; this.default_stroke_color = options.stroke_color; this.default_stroke_width = options.stroke_width; this.default_fill_color = options.fill_color;